diff --git a/changelog/6181.improvement.rst b/changelog/6181.improvement.rst new file mode 100644 index 000000000..0960f6203 --- /dev/null +++ b/changelog/6181.improvement.rst @@ -0,0 +1 @@ +The reason for a stopped session, e.g. with ``--maxfail`` / ``-x`` gets reported. diff --git a/doc/en/usage.rst b/doc/en/usage.rst index a23cf764a..3b5919363 100644 --- a/doc/en/usage.rst +++ b/doc/en/usage.rst @@ -66,8 +66,8 @@ To stop the testing process after the first (N) failures: .. code-block:: bash - pytest -x # stop after first failure - pytest --maxfail=2 # stop after two failures + pytest -x # stop after first failure + pytest --maxfail=2 # stop after two failures .. _select-tests: diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index 59f0fe0f3..14267b208 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -676,7 +676,7 @@ class TerminalReporter: self._tw.line("{}{}".format(indent + " ", line.strip())) @pytest.hookimpl(hookwrapper=True) - def pytest_sessionfinish(self, exitstatus): + def pytest_sessionfinish(self, session: Session, exitstatus: ExitCode): outcome = yield outcome.get_result() self._tw.line("") @@ -691,9 +691,13 @@ class TerminalReporter: self.config.hook.pytest_terminal_summary( terminalreporter=self, exitstatus=exitstatus, config=self.config ) + if session.shouldfail: + self.write_sep("!", session.shouldfail, red=True) if exitstatus == ExitCode.INTERRUPTED: self._report_keyboardinterrupt() del self._keyboardinterrupt_memo + elif session.shouldstop: + self.write_sep("!", session.shouldstop, red=True) self.summary_stats() @pytest.hookimpl(hookwrapper=True) diff --git a/testing/test_collection.py b/testing/test_collection.py index 83345d2c6..f18d36d24 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -852,11 +852,15 @@ def test_exit_on_collection_with_maxfail_smaller_than_n_errors(testdir): res = testdir.runpytest("--maxfail=1") assert res.ret == 1 - res.stdout.fnmatch_lines( - ["*ERROR collecting test_02_import_error.py*", "*No module named *asdfa*"] + [ + "collected 1 item / 1 error", + "*ERROR collecting test_02_import_error.py*", + "*No module named *asdfa*", + "*! stopping after 1 failures !*", + "*= 1 error in *", + ] ) - res.stdout.no_fnmatch_line("*test_03*") @@ -869,7 +873,6 @@ def test_exit_on_collection_with_maxfail_bigger_than_n_errors(testdir): res = testdir.runpytest("--maxfail=4") assert res.ret == 2 - res.stdout.fnmatch_lines( [ "collected 2 items / 2 errors", @@ -877,6 +880,8 @@ def test_exit_on_collection_with_maxfail_bigger_than_n_errors(testdir): "*No module named *asdfa*", "*ERROR collecting test_03_import_error.py*", "*No module named *asdfa*", + "*! Interrupted: 2 errors during collection !*", + "*= 2 errors in *", ] ) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 1bec577b8..d31033197 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -963,7 +963,31 @@ class TestGenericReporting: ) result = testdir.runpytest("--maxfail=2", *option.args) result.stdout.fnmatch_lines( - ["*def test_1():*", "*def test_2():*", "*2 failed*"] + [ + "*def test_1():*", + "*def test_2():*", + "*! stopping after 2 failures !*", + "*2 failed*", + ] + ) + + def test_maxfailures_with_interrupted(self, testdir): + testdir.makepyfile( + """ + def test(request): + request.session.shouldstop = "session_interrupted" + assert 0 + """ + ) + result = testdir.runpytest("--maxfail=1", "-ra") + result.stdout.fnmatch_lines( + [ + "*= short test summary info =*", + "FAILED *", + "*! stopping after 1 failures !*", + "*! session_interrupted !*", + "*= 1 failed in*", + ] ) def test_tb_option(self, testdir, option):