diff --git a/AUTHORS b/AUTHORS index 4f61c0591..4619cf1bc 100644 --- a/AUTHORS +++ b/AUTHORS @@ -36,6 +36,7 @@ Andrey Paramonov Andrzej Klajnert Andrzej Ostrowski Andy Freeland +Anita Hammer Anthon van der Neut Anthony Shaw Anthony Sottile diff --git a/changelog/12191.bugfix.rst b/changelog/12191.bugfix.rst new file mode 100644 index 000000000..5102d4698 --- /dev/null +++ b/changelog/12191.bugfix.rst @@ -0,0 +1 @@ +Keyboard interrupts and system exits are now properly handled during the test collection. diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index dfefa73b7..e5af60e38 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -393,7 +393,9 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport: return list(collector.collect()) - call = CallInfo.from_call(collect, "collect") + call = CallInfo.from_call( + collect, "collect", reraise=(KeyboardInterrupt, SystemExit) + ) longrepr: Union[None, Tuple[str, int, str], str, TerminalRepr] = None if not call.excinfo: outcome: Literal["passed", "skipped", "failed"] = "passed" diff --git a/testing/test_collection.py b/testing/test_collection.py index 9caca622c..995e2999b 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -7,6 +7,7 @@ import sys import tempfile import textwrap from typing import List +from typing import Type from _pytest.assertion.util import running_on_ci from _pytest.config import ExitCode @@ -1856,3 +1857,33 @@ def test_do_not_collect_symlink_siblings( # Ensure we collect it only once if we pass the symlinked directory. result = pytester.runpytest(symlink_path, "-sv") result.assert_outcomes(passed=1) + + +@pytest.mark.parametrize( + "exception_class, msg", + [ + (KeyboardInterrupt, "*!!! KeyboardInterrupt !!!*"), + (SystemExit, "INTERNALERROR> SystemExit"), + ], +) +def test_respect_system_exceptions( + pytester: Pytester, + exception_class: Type[BaseException], + msg: str, +): + head = "Before exception" + tail = "After exception" + ensure_file(pytester.path / "test_eggs.py").write_text( + f"print('{head}')", encoding="UTF-8" + ) + ensure_file(pytester.path / "test_ham.py").write_text( + f"raise {exception_class.__name__}()", encoding="UTF-8" + ) + ensure_file(pytester.path / "test_spam.py").write_text( + f"print('{tail}')", encoding="UTF-8" + ) + + result = pytester.runpytest_subprocess("-s") + result.stdout.fnmatch_lines([f"*{head}*"]) + result.stdout.fnmatch_lines([msg]) + result.stdout.no_fnmatch_line(f"*{tail}*")