Handle `Exit` exception in `pytest_sessionfinish` (#6660)

This commit is contained in:
Daniel Hahler 2020-02-07 00:40:10 +01:00 committed by GitHub
commit a8fc056aad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 46 additions and 10 deletions

View File

@ -0,0 +1 @@
:func:`pytest.exit() <_pytest.outcomes.exit>` is handled when emitted from the :func:`pytest_sessionfinish <_pytest.hookspec.pytest_sessionfinish>` hook. This includes quitting from a debugger.

View File

@ -5,6 +5,7 @@ import functools
import importlib
import os
import sys
from typing import Callable
from typing import Dict
from typing import FrozenSet
from typing import List
@ -23,7 +24,7 @@ from _pytest.config import hookimpl
from _pytest.config import UsageError
from _pytest.fixtures import FixtureManager
from _pytest.nodes import Node
from _pytest.outcomes import exit
from _pytest.outcomes import Exit
from _pytest.runner import collect_one_node
from _pytest.runner import SetupState
@ -194,7 +195,9 @@ def pytest_addoption(parser):
)
def wrap_session(config, doit):
def wrap_session(
config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]]
) -> Union[int, ExitCode]:
"""Skeleton command line program"""
session = Session(config)
session.exitstatus = ExitCode.OK
@ -211,10 +214,10 @@ def wrap_session(config, doit):
raise
except Failed:
session.exitstatus = ExitCode.TESTS_FAILED
except (KeyboardInterrupt, exit.Exception):
except (KeyboardInterrupt, Exit):
excinfo = _pytest._code.ExceptionInfo.from_current()
exitstatus = ExitCode.INTERRUPTED
if isinstance(excinfo.value, exit.Exception):
exitstatus = ExitCode.INTERRUPTED # type: Union[int, ExitCode]
if isinstance(excinfo.value, Exit):
if excinfo.value.returncode is not None:
exitstatus = excinfo.value.returncode
if initstate < 2:
@ -228,7 +231,7 @@ def wrap_session(config, doit):
excinfo = _pytest._code.ExceptionInfo.from_current()
try:
config.notify_exception(excinfo, config.option)
except exit.Exception as exc:
except Exit as exc:
if exc.returncode is not None:
session.exitstatus = exc.returncode
sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc))
@ -237,12 +240,18 @@ def wrap_session(config, doit):
sys.stderr.write("mainloop: caught unexpected SystemExit!\n")
finally:
excinfo = None # Explicitly break reference cycle.
# Explicitly break reference cycle.
excinfo = None # type: ignore
session.startdir.chdir()
if initstate >= 2:
config.hook.pytest_sessionfinish(
session=session, exitstatus=session.exitstatus
)
try:
config.hook.pytest_sessionfinish(
session=session, exitstatus=session.exitstatus
)
except Exit as exc:
if exc.returncode is not None:
session.exitstatus = exc.returncode
sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc))
config._ensure_unconfigure()
return session.exitstatus
@ -382,6 +391,7 @@ class Session(nodes.FSCollector):
_setupstate = None # type: SetupState
# Set on the session by fixtures.pytest_sessionstart.
_fixturemanager = None # type: FixtureManager
exitstatus = None # type: Union[int, ExitCode]
def __init__(self, config: Config) -> None:
nodes.FSCollector.__init__(

View File

@ -1,5 +1,8 @@
from typing import Optional
import pytest
from _pytest.main import ExitCode
from _pytest.pytester import Testdir
@pytest.mark.parametrize(
@ -50,3 +53,25 @@ def test_wrap_session_notify_exception(ret_exc, testdir):
assert result.stderr.lines == ["mainloop: caught unexpected SystemExit!"]
else:
assert result.stderr.lines == ["Exit: exiting after {}...".format(exc.__name__)]
@pytest.mark.parametrize("returncode", (None, 42))
def test_wrap_session_exit_sessionfinish(
returncode: Optional[int], testdir: Testdir
) -> None:
testdir.makeconftest(
"""
import pytest
def pytest_sessionfinish():
pytest.exit(msg="exit_pytest_sessionfinish", returncode={returncode})
""".format(
returncode=returncode
)
)
result = testdir.runpytest()
if returncode:
assert result.ret == returncode
else:
assert result.ret == ExitCode.NO_TESTS_COLLECTED
assert result.stdout.lines[-1] == "collected 0 items"
assert result.stderr.lines == ["Exit: exit_pytest_sessionfinish"]