diff --git a/doc/en/usage.rst b/doc/en/usage.rst index a23cf764ace..3b5919363c8 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 41e4e6e94c9..022a96e9835 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -800,7 +800,7 @@ def _printcollecteditems(self, items): 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("") @@ -815,9 +815,13 @@ def pytest_sessionfinish(self, exitstatus): 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 2ab0a80171b..3daf28adb02 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 f94643e16f7..2a085169533 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -1017,7 +1017,31 @@ def test_3(): ) 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):