Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .github/scripts/run_serial_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import argparse
import fnmatch
import os
import subprocess
import sys
from pathlib import Path

Expand Down Expand Up @@ -46,7 +47,14 @@ def main() -> None:
marker_expression = (
"serial and not review_optional" if args.exclude_review_optional else "serial"
)
os.execv(sys.executable, _serial_args(marker_expression=marker_expression))
command = _serial_args(marker_expression=marker_expression)
if sys.platform == "win32":
# Windows has no process replacement: os.execv spawns a child and
# terminates this process with 0, so the caller sees success no matter
# how pytest exits. Mirror run_integration_tests.py and wait instead.
completed = subprocess.run(command, check=False)
raise SystemExit(completed.returncode)
os.execv(sys.executable, command)


if __name__ == "__main__":
Expand Down
55 changes: 54 additions & 1 deletion tests/test_run_serial_tests.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from __future__ import annotations

import importlib.util
import subprocess
import sys
from pathlib import Path
from types import ModuleType
from types import ModuleType, SimpleNamespace

import pytest

Expand Down Expand Up @@ -96,3 +97,55 @@ def test_serial_command_targets_only_discovered_files(
"-m",
"serial",
]


def test_windows_runner_uses_subprocess_and_propagates_exit_code(
serial_test_runner: ModuleType,
monkeypatch: pytest.MonkeyPatch,
) -> None:
captured: list[tuple[list[str], bool]] = []

def capture_run(command: list[str], *, check: bool) -> SimpleNamespace:
captured.append((command, check))
return SimpleNamespace(returncode=23)

monkeypatch.setattr(serial_test_runner.sys, "platform", "win32")
monkeypatch.setattr(subprocess, "run", capture_run)
monkeypatch.setattr(serial_test_runner.os, "chdir", lambda _path: None)
monkeypatch.setattr(
serial_test_runner.os,
"execv",
lambda *_args: pytest.fail("Windows serial runner must not call os.execv"),
)
monkeypatch.setattr(sys, "argv", ["run_serial_tests.py"])

with pytest.raises(SystemExit) as exc_info:
serial_test_runner.main()

assert exc_info.value.code == 23
assert len(captured) == 1
assert captured[0][1] is False


def test_non_windows_runner_still_execs(
serial_test_runner: ModuleType,
monkeypatch: pytest.MonkeyPatch,
) -> None:
captured: list[list[str]] = []

monkeypatch.setattr(serial_test_runner.sys, "platform", "linux")
monkeypatch.setattr(serial_test_runner.os, "chdir", lambda _path: None)
monkeypatch.setattr(
subprocess,
"run",
lambda *_args, **_kwargs: pytest.fail("POSIX serial runner must not call subprocess.run"),
)
monkeypatch.setattr(
serial_test_runner.os, "execv", lambda _executable, command: captured.append(command)
)
monkeypatch.setattr(sys, "argv", ["run_serial_tests.py"])

serial_test_runner.main()

assert len(captured) == 1
assert "serial" in captured[0]
Loading