From a152c2cee47a15c4da7c9f0e86dbb8f097383726 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 May 2024 12:18:05 +0000 Subject: [PATCH] [8.2.x] Consider KeyboardInterrupt/SystemExit at collection time (#12282) Co-authored-by: Anita Hammer <166057949+anitahammer@users.noreply.github.com> --- AUTHORS | 1 + changelog/12191.bugfix.rst | 1 + src/_pytest/runner.py | 4 +++- testing/test_collection.py | 31 +++++++++++++++++++++++++++++++ 4 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 changelog/12191.bugfix.rst 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 9bc544ea7..d15a682f9 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -388,7 +388,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 1491ec859..3f7a3d535 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 @@ -1857,3 +1858,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}*")