fix(test): handle Windows file locking in integration test teardown#294
Merged
danielmeppiel merged 1 commit intomainfrom Mar 14, 2026
Merged
fix(test): handle Windows file locking in integration test teardown#294danielmeppiel merged 1 commit intomainfrom
danielmeppiel merged 1 commit intomainfrom
Conversation
On Windows, recently-terminated subprocesses may still hold file locks on temp directories (WinError 32). Fix test_auto_install_e2e.py by: - Using shutil.rmtree(ignore_errors=True) in teardown_method - Closing subprocess stdout pipes before waiting - Increasing wait timeout for graceful shutdown Also fix test_ado_e2e.py venv path fallback for Windows (.venv/Scripts vs .venv/bin). Ref #185 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the integration E2E test harness to be more reliable on Windows by reducing teardown failures caused by lingering subprocess file locks, and by fixing the Windows venv executable path fallback for ADO tests.
Changes:
- Make
test_auto_install_e2e.pyteardown resilient to WindowsWinError 32by tolerating temp-dir deletion failures and adjusting subprocess shutdown handling. - Close subprocess stdout pipes and increase wait timeouts to improve odds of clean shutdown.
- Fix Windows venv fallback path in
test_ado_e2e.pyto use.venv/Scripts/apm.exeinstead of.venv/bin/apm.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| tests/integration/test_auto_install_e2e.py | Makes teardown/subprocess shutdown more Windows-tolerant to prevent non-zero pytest exits due to temp directory locks. |
| tests/integration/test_ado_e2e.py | Updates local venv fallback to the correct Windows entrypoint path. |
Comments suppressed due to low confidence (6)
tests/integration/test_auto_install_e2e.py:196
- In this try/except,
process.stdout.close()isn’t executed if an exception is raised before reaching it, and it also isn’t closed in the except path. To reliably release Windows file handles, close stdout in afinallythat runs for both success and error paths.
for line in iter(process.stdout.readline, ''):
if not line:
break
if "Package installed and ready to run" in line:
process.terminate()
break
process.stdout.close()
process.wait(timeout=10)
except:
process.kill()
process.wait()
tests/integration/test_auto_install_e2e.py:230
- Same cleanup issue here: if the read loop raises before
process.stdout.close(), or if the except path is taken, stdout may remain open. Closing stdout in afinally(and then ensuring terminate/kill+wait happens) is more reliable for Windows file locking.
for line in iter(process.stdout.readline, ''):
if not line:
break
output_lines.append(line)
# Terminate once we see execution starting (no need for full run)
if "Executing" in line or "Package installed and ready to run" in line:
process.terminate()
break
process.stdout.close()
process.wait(timeout=10)
except:
process.kill()
process.wait()
tests/integration/test_auto_install_e2e.py:278
- Same pattern:
process.stdout.close()is skipped if an exception occurs before that line, and stdout is not closed in the except path. To prevent lingering Windows locks, close stdout in afinallythat always runs.
for line in iter(process.stdout.readline, ''):
if not line:
break
if "Package installed and ready to run" in line:
process.terminate()
break
process.stdout.close()
process.wait(timeout=10)
except:
process.kill()
process.wait()
tests/integration/test_auto_install_e2e.py:308
- Same issue here: if the output-reading loop errors out, stdout may never be closed, and the except path doesn’t close it either. Consider consolidating the terminate/wait/kill logic and stdout close into a single
finallyblock so handles are always released on Windows.
for line in iter(process.stdout.readline, ''):
if not line:
break
output_lines.append(line)
# Terminate once we see execution starting
if "Executing" in line or "Auto-discovered" in line:
process.terminate()
break
process.stdout.close()
process.wait(timeout=10)
except:
process.kill()
process.wait()
tests/integration/test_auto_install_e2e.py:355
- Same cleanup concern: if an exception is thrown before reaching
process.stdout.close(), the handle stays open and can keep files locked on Windows; additionally the except path doesn’t close stdout. Moving the close into afinallythat always executes will make the teardown fix more robust.
for line in iter(process.stdout.readline, ''):
if not line:
break
# Terminate once installation completes
if "Package installed and ready to run" in line:
process.terminate()
break
process.stdout.close()
process.wait(timeout=10)
except:
process.kill()
process.wait()
tests/integration/test_ado_e2e.py:44
- On Windows,
apm_path(either fromshutil.whichor from the repo’s.venv) can contain spaces; buildingfull_cmd = f"{apm_path} {cmd}"and running it withshell=Truewill break unless the executable path is quoted. Consider either quotingapm_pathexplicitly infull_cmdor (preferably) avoidingshell=Trueand passing an argv list tosubprocess.run.
if sys.platform == "win32":
apm_path = Path(__file__).parent.parent.parent / ".venv" / "Scripts" / "apm.exe"
else:
apm_path = Path(__file__).parent.parent.parent / ".venv" / "bin" / "apm"
full_cmd = f"{apm_path} {cmd}"
result = subprocess.run(
full_cmd,
shell=True,
cwd=cwd,
Comment on lines
131
to
137
| # Wait for graceful shutdown | ||
| process.stdout.close() | ||
| try: | ||
| process.wait(timeout=5) | ||
| process.wait(timeout=10) | ||
| except subprocess.TimeoutExpired: | ||
| process.kill() | ||
| process.wait() |
Comment on lines
71
to
+75
| if os.path.exists(self.test_dir): | ||
| shutil.rmtree(self.test_dir) | ||
| # ignore_errors=True: on Windows, recently-terminated subprocesses | ||
| # may still hold file locks on the temp directory (WinError 32). | ||
| # CI temp dirs are ephemeral — safe to leave behind if needed. | ||
| shutil.rmtree(self.test_dir, ignore_errors=True) |
danielmeppiel
added a commit
that referenced
this pull request
Mar 14, 2026
…e in finally (#295) Move process.stdout.close() into finally blocks so pipe handles are released even when exceptions occur during output reading. Replace blanket ignore_errors=True with targeted PermissionError handling: retry once after 1s on Windows (WinError 32), re-raise on other platforms. Ref #185 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the Windows integration test teardown failure that blocks the v0.7.9 release pipeline.
Root Cause
test_auto_install_e2e.py::teardown_methodcallsshutil.rmtree(self.test_dir)which fails on Windows withPermissionError: [WinError 32]— the recently-terminatedapmsubprocess still holds file locks on the temp directory.All 4 tests PASS, but the teardown error causes pytest to exit non-zero, failing the entire integration test job and blocking the release.
Changes
tests/integration/test_auto_install_e2e.py:shutil.rmtree(ignore_errors=True)inteardown_method— CI temp dirs are ephemeralprocess.stdoutpipes beforeprocess.wait()to release file handles on Windowstests/integration/test_ado_e2e.py:.venv/Scripts/apm.exeon Windows vs.venv/bin/apmon UnixEvidence
Same error reproduced consistently across multiple pipeline runs:
Notes