Handle `Exit` exception in `pytest_sessionfinish` (#6660)
This commit is contained in:
commit
a8fc056aad
|
@ -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.
|
|
@ -5,6 +5,7 @@ import functools
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
from typing import Callable
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from typing import FrozenSet
|
from typing import FrozenSet
|
||||||
from typing import List
|
from typing import List
|
||||||
|
@ -23,7 +24,7 @@ from _pytest.config import hookimpl
|
||||||
from _pytest.config import UsageError
|
from _pytest.config import UsageError
|
||||||
from _pytest.fixtures import FixtureManager
|
from _pytest.fixtures import FixtureManager
|
||||||
from _pytest.nodes import Node
|
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 collect_one_node
|
||||||
from _pytest.runner import SetupState
|
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"""
|
"""Skeleton command line program"""
|
||||||
session = Session(config)
|
session = Session(config)
|
||||||
session.exitstatus = ExitCode.OK
|
session.exitstatus = ExitCode.OK
|
||||||
|
@ -211,10 +214,10 @@ def wrap_session(config, doit):
|
||||||
raise
|
raise
|
||||||
except Failed:
|
except Failed:
|
||||||
session.exitstatus = ExitCode.TESTS_FAILED
|
session.exitstatus = ExitCode.TESTS_FAILED
|
||||||
except (KeyboardInterrupt, exit.Exception):
|
except (KeyboardInterrupt, Exit):
|
||||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||||
exitstatus = ExitCode.INTERRUPTED
|
exitstatus = ExitCode.INTERRUPTED # type: Union[int, ExitCode]
|
||||||
if isinstance(excinfo.value, exit.Exception):
|
if isinstance(excinfo.value, Exit):
|
||||||
if excinfo.value.returncode is not None:
|
if excinfo.value.returncode is not None:
|
||||||
exitstatus = excinfo.value.returncode
|
exitstatus = excinfo.value.returncode
|
||||||
if initstate < 2:
|
if initstate < 2:
|
||||||
|
@ -228,7 +231,7 @@ def wrap_session(config, doit):
|
||||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||||
try:
|
try:
|
||||||
config.notify_exception(excinfo, config.option)
|
config.notify_exception(excinfo, config.option)
|
||||||
except exit.Exception as exc:
|
except Exit as exc:
|
||||||
if exc.returncode is not None:
|
if exc.returncode is not None:
|
||||||
session.exitstatus = exc.returncode
|
session.exitstatus = exc.returncode
|
||||||
sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc))
|
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")
|
sys.stderr.write("mainloop: caught unexpected SystemExit!\n")
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
excinfo = None # Explicitly break reference cycle.
|
# Explicitly break reference cycle.
|
||||||
|
excinfo = None # type: ignore
|
||||||
session.startdir.chdir()
|
session.startdir.chdir()
|
||||||
if initstate >= 2:
|
if initstate >= 2:
|
||||||
config.hook.pytest_sessionfinish(
|
try:
|
||||||
session=session, exitstatus=session.exitstatus
|
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()
|
config._ensure_unconfigure()
|
||||||
return session.exitstatus
|
return session.exitstatus
|
||||||
|
|
||||||
|
@ -382,6 +391,7 @@ class Session(nodes.FSCollector):
|
||||||
_setupstate = None # type: SetupState
|
_setupstate = None # type: SetupState
|
||||||
# Set on the session by fixtures.pytest_sessionstart.
|
# Set on the session by fixtures.pytest_sessionstart.
|
||||||
_fixturemanager = None # type: FixtureManager
|
_fixturemanager = None # type: FixtureManager
|
||||||
|
exitstatus = None # type: Union[int, ExitCode]
|
||||||
|
|
||||||
def __init__(self, config: Config) -> None:
|
def __init__(self, config: Config) -> None:
|
||||||
nodes.FSCollector.__init__(
|
nodes.FSCollector.__init__(
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.main import ExitCode
|
from _pytest.main import ExitCode
|
||||||
|
from _pytest.pytester import Testdir
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -50,3 +53,25 @@ def test_wrap_session_notify_exception(ret_exc, testdir):
|
||||||
assert result.stderr.lines == ["mainloop: caught unexpected SystemExit!"]
|
assert result.stderr.lines == ["mainloop: caught unexpected SystemExit!"]
|
||||||
else:
|
else:
|
||||||
assert result.stderr.lines == ["Exit: exiting after {}...".format(exc.__name__)]
|
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"]
|
||||||
|
|
Loading…
Reference in New Issue