Fix integration test worker crashes on Python 3.13

Three changes to prevent pytest-xdist workers from crashing during
Azure Functions integration tests:

1. Add `start_new_session=True` to subprocess on Linux so signals
   (e.g. from test-timeout) cannot propagate between the func host
   and the xdist worker process.

2. Add an overall 100-second budget to the fixture setup loop so
   the retry logic never exceeds the 120-second test timeout. When
   pytest-timeout's thread method fires during fixture setup and the
   thread doesn't respond, it calls os._exit() which kills the
   xdist worker – this is the root cause of the "Not properly
   terminated" crashes.

3. Remove the `UV_PYTHON: "3.10"` workaround from both workflow
   files so integration tests actually run on Python 3.13.

Co-authored-by: larohra <41490930+larohra@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-25 17:22:39 +00:00
Unverified
parent e3ef6c1340
commit 443aa5d2a5
3 changed files with 27 additions and 6 deletions
@@ -371,8 +371,15 @@ def _start_function_app(sample_path: Path, port: int) -> subprocess.Popen[Any]:
shell=True,
env=env,
)
# On Unix, don't use shell=True to avoid shell wrapper issues
return subprocess.Popen(["func", "start", "--port", str(port)], cwd=str(sample_path), env=env)
# On Unix, use start_new_session=True to isolate the process group from the
# pytest-xdist worker. Without this, signals (e.g. from test-timeout) can
# propagate to the func host and vice-versa, potentially killing the worker.
return subprocess.Popen(
["func", "start", "--port", str(port)],
cwd=str(sample_path),
env=env,
start_new_session=True,
)
def _wait_for_function_app_ready(func_process: subprocess.Popen[Any], port: int, max_wait: int = 60) -> None:
@@ -529,18 +536,33 @@ def function_app_for_test(request: pytest.FixtureRequest) -> Iterator[dict[str,
_load_and_validate_env()
max_attempts = 3
# The overall budget MUST be shorter than the pytest-timeout value
# (--timeout=120 by default) so that the fixture finishes cleanly instead
# of being killed by os._exit() which crashes the xdist worker.
overall_budget = 100 # seconds leaves headroom below the 120 s test timeout
last_error: Exception | None = None
func_process: subprocess.Popen[Any] | None = None
base_url = ""
port = 0
overall_start = time.time()
attempts_made = 0
for _ in range(max_attempts):
remaining = overall_budget - (time.time() - overall_start)
if remaining < 10:
# Not enough time for another attempt; bail out.
break
attempts_made += 1
port = _find_available_port()
base_url = _build_base_url(port)
func_process = _start_function_app(sample_path, port)
try:
_wait_for_function_app_ready(func_process, port)
# Cap each attempt's wait to the remaining budget minus a small
# buffer for cleanup.
per_attempt_wait = min(60, int(remaining) - 5)
_wait_for_function_app_ready(func_process, port, max_wait=max(per_attempt_wait, 10))
last_error = None
break
except FunctionAppStartupError as exc:
@@ -549,7 +571,8 @@ def function_app_for_test(request: pytest.FixtureRequest) -> Iterator[dict[str,
func_process = None
if func_process is None:
error_message = f"Function app failed to start after {max_attempts} attempt(s)."
elapsed = int(time.time() - overall_start)
error_message = f"Function app failed to start after {attempts_made} attempt(s) ({elapsed}s elapsed)."
if last_error is not None:
error_message += f" Last error: {last_error}"
pytest.fail(error_message)