Skip to content

Commit 4bcb1dd

Browse files
committed
Harden Nox aggregate validation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 61acbc5e-7c07-42a7-8e38-31329c4befce
1 parent 82fe0cc commit 4bcb1dd

2 files changed

Lines changed: 58 additions & 20 deletions

File tree

docs/development.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ nox -s ci -- 3.14
2626

2727
Run the versioned test sessions directly when a change needs complete matrix
2828
coverage, for example `nox -s core_tests` or `nox -s azuremanaged_tests`.
29+
Nox fails rather than silently skipping a session when its required Python
30+
interpreter is unavailable.
2931

3032
Nox starts Azurite automatically for the core and Azure Functions tests. The
3133
Azure Managed tests start a disposable DTS emulator Docker container. Start

noxfile.py

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import nox
3636

3737
nox.options.reuse_existing_virtualenvs = True
38+
nox.options.error_on_missing_interpreters = True
3839

3940
REPO_ROOT = Path(__file__).parent
4041
AZUREMANAGED = REPO_ROOT / "durabletask-azuremanaged"
@@ -118,6 +119,21 @@ def _wait_for_ports(ports: Sequence[int], timeout: int = 30) -> bool:
118119
return False
119120

120121

122+
def _pytest_arguments(
123+
default_target: str,
124+
posargs: Sequence[str],
125+
) -> tuple[str, ...]:
126+
"""Keep the default target when positional arguments are only pytest options."""
127+
if not posargs:
128+
return (default_target,)
129+
130+
has_target = any(
131+
"::" in argument or Path(argument).exists()
132+
for argument in posargs
133+
)
134+
return tuple(posargs) if has_target else (default_target, *posargs)
135+
136+
121137
def _start_azurite(
122138
session: nox.Session,
123139
ports: Sequence[int],
@@ -162,18 +178,32 @@ def _start_azurite(
162178
session.log("Started Azurite for this session.")
163179
return process
164180

165-
if process.poll() is None:
166-
process.terminate()
167-
process.wait()
181+
_stop_process(process)
168182
session.error("Azurite did not become ready within 30 seconds.")
169183
return None
170184

171185

172186
def _stop_process(process: subprocess.Popen[bytes] | None) -> None:
173187
"""Stop a service process that this Nox session started."""
174-
if process is not None and process.poll() is None:
188+
if process is None:
189+
return
190+
191+
if os.name == "nt":
192+
script = f"""
193+
function Stop-ProcessTree([int] $processId) {{
194+
Get-CimInstance Win32_Process -Filter "ParentProcessId = $processId" |
195+
ForEach-Object {{ Stop-ProcessTree $_.ProcessId }}
196+
Stop-Process -Id $processId -Force -ErrorAction SilentlyContinue
197+
}}
198+
Stop-ProcessTree {process.pid}
199+
"""
200+
subprocess.run(
201+
["powershell", "-NoProfile", "-Command", script],
202+
check=True,
203+
)
204+
elif process.poll() is None:
175205
process.terminate()
176-
process.wait()
206+
process.wait()
177207

178208

179209
def _new_test_namespace(prefix: str) -> str:
@@ -304,10 +334,10 @@ def core_tests(session: nox.Session) -> None:
304334
try:
305335
session.install("-r", "requirements.txt")
306336
session.install("-e", f"{REPO_ROOT}[azure-blob-payloads]", "aiohttp")
307-
targets = session.posargs or ("tests/durabletask",)
337+
arguments = _pytest_arguments("tests/durabletask", session.posargs)
308338
session.run(
309339
"pytest",
310-
*targets,
340+
*arguments,
311341
"-m",
312342
"not dts",
313343
"--verbose",
@@ -331,8 +361,11 @@ def azuremanaged_tests(session: nox.Session) -> None:
331361
"-e",
332362
str(AZUREMANAGED),
333363
)
334-
targets = session.posargs or ("tests/durabletask-azuremanaged",)
335-
session.run("pytest", *targets, "-m", "dts", "--verbose")
364+
arguments = _pytest_arguments(
365+
"tests/durabletask-azuremanaged",
366+
session.posargs,
367+
)
368+
session.run("pytest", *arguments, "-m", "dts", "--verbose")
336369
finally:
337370
_stop_dts_emulator(container_name)
338371

@@ -343,9 +376,9 @@ def functions_unit(session: nox.Session) -> None:
343376
session.install("-r", "requirements.txt")
344377
_install_packages(session, editable=True)
345378
session.install("pytest")
346-
targets = session.posargs or ("tests/azure-functions-durable",)
379+
arguments = _pytest_arguments("tests/azure-functions-durable", session.posargs)
347380
session.run(
348-
"pytest", *targets,
381+
"pytest", *arguments,
349382
"-m", "not dts and not azurite and not functions_e2e",
350383
)
351384

@@ -391,9 +424,12 @@ def functions_e2e(session: nox.Session) -> None:
391424
session.install("pytest")
392425
for app in E2E_APPS:
393426
_link_app_venv(session, E2E_APPS_DIR / app)
394-
targets = session.posargs or ("tests/azure-functions-durable/e2e",)
427+
arguments = _pytest_arguments(
428+
"tests/azure-functions-durable/e2e",
429+
session.posargs,
430+
)
395431
session.run(
396-
"pytest", *targets,
432+
"pytest", *arguments,
397433
"-m", "functions_e2e",
398434
)
399435
finally:
@@ -419,10 +455,10 @@ def ci(session: nox.Session) -> None:
419455
f"{', '.join(PYTHON_VERSIONS)}.")
420456

421457
python_version = session.posargs[0] if session.posargs else DEFAULT_CI_PYTHON
422-
session.notify("lint")
423-
session.notify("typecheck_core")
424-
session.notify("typecheck_functions")
425-
session.notify(f"core_tests-{python_version}")
426-
session.notify(f"azuremanaged_tests-{python_version}")
427-
session.notify("functions_unit-3.13")
428-
session.notify("functions_e2e")
458+
session.notify("lint", ())
459+
session.notify("typecheck_core", ())
460+
session.notify("typecheck_functions", ())
461+
session.notify(f"core_tests-{python_version}", ())
462+
session.notify(f"azuremanaged_tests-{python_version}", ())
463+
session.notify("functions_unit-3.13", ())
464+
session.notify("functions_e2e", ())

0 commit comments

Comments
 (0)