Merge remote-tracking branch 'upstream/master' into mm
Conflicts: src/_pytest/main.py src/_pytest/mark/structures.py src/_pytest/python.py testing/test_main.py testing/test_parseopt.py
This commit is contained in:
commit
78baa7b575
|
@ -7,6 +7,7 @@ Here is a quick checklist that should be present in PRs.
|
||||||
- [ ] Target the `features` branch for new features, improvements, and removals/deprecations.
|
- [ ] Target the `features` branch for new features, improvements, and removals/deprecations.
|
||||||
- [ ] Include documentation when adding new features.
|
- [ ] Include documentation when adding new features.
|
||||||
- [ ] Include new tests or update existing tests when applicable.
|
- [ ] Include new tests or update existing tests when applicable.
|
||||||
|
- [X] Allow maintainers to push and squash when merging my commits. Please uncheck this if you prefer to squash the commits yourself.
|
||||||
|
|
||||||
Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please:
|
Unless your change is trivial or a small documentation fix (e.g., a typo or reword of a small section) please:
|
||||||
|
|
||||||
|
|
35
.travis.yml
35
.travis.yml
|
@ -1,6 +1,6 @@
|
||||||
language: python
|
language: python
|
||||||
dist: xenial
|
dist: trusty
|
||||||
python: '3.7'
|
python: '3.5.1'
|
||||||
cache: false
|
cache: false
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
@ -16,36 +16,11 @@ install:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
include:
|
include:
|
||||||
# OSX tests - first (in test stage), since they are the slower ones.
|
|
||||||
# Coverage for:
|
|
||||||
# - osx
|
|
||||||
# - verbose=1
|
|
||||||
- os: osx
|
|
||||||
osx_image: xcode10.1
|
|
||||||
language: generic
|
|
||||||
env: TOXENV=py37-xdist PYTEST_COVERAGE=1 PYTEST_ADDOPTS=-v
|
|
||||||
before_install:
|
|
||||||
- which python3
|
|
||||||
- python3 -V
|
|
||||||
- ln -sfn "$(which python3)" /usr/local/bin/python
|
|
||||||
- python -V
|
|
||||||
- test $(python -c 'import sys; print("%d%d" % sys.version_info[0:2])') = 37
|
|
||||||
|
|
||||||
# Full run of latest supported version, without xdist.
|
|
||||||
# Coverage for:
|
|
||||||
# - pytester's LsofFdLeakChecker
|
|
||||||
# - TestArgComplete (linux only)
|
|
||||||
# - numpy
|
|
||||||
# - old attrs
|
|
||||||
# - verbose=0
|
|
||||||
# - test_sys_breakpoint_interception (via pexpect).
|
|
||||||
- env: TOXENV=py37-lsof-numpy-oldattrs-pexpect-twisted PYTEST_COVERAGE=1 PYTEST_ADDOPTS=
|
|
||||||
python: '3.7'
|
|
||||||
|
|
||||||
# Coverage for Python 3.5.{0,1} specific code, mostly typing related.
|
# Coverage for Python 3.5.{0,1} specific code, mostly typing related.
|
||||||
- env: TOXENV=py35 PYTEST_COVERAGE=1 PYTEST_ADDOPTS="-k test_raises_cyclic_reference"
|
- env: TOXENV=py35 PYTEST_COVERAGE=1 PYTEST_ADDOPTS="-k test_raises_cyclic_reference"
|
||||||
python: '3.5.1'
|
before_install:
|
||||||
dist: trusty
|
# Work around https://github.com/jaraco/zipp/issues/40.
|
||||||
|
- python -m pip install -U pip 'setuptools>=34.4.0' virtualenv
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- |
|
- |
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Assertion rewriting hooks are (re)stored for the current item, which fixes them being still used after e.g. pytester's :func:`testdir.runpytest <_pytest.pytester.Testdir.runpytest>` etc.
|
|
@ -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.
|
|
@ -162,7 +162,7 @@ html_logo = "img/pytest1.png"
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
# pixels large.
|
# pixels large.
|
||||||
html_favicon = "img/pytest1favi.ico"
|
html_favicon = "img/favicon.png"
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.7 KiB |
|
@ -901,8 +901,8 @@ Can be either a ``str`` or ``Sequence[str]``.
|
||||||
pytest_plugins = ("myapp.testsupport.tools", "myapp.testsupport.regression")
|
pytest_plugins = ("myapp.testsupport.tools", "myapp.testsupport.regression")
|
||||||
|
|
||||||
|
|
||||||
pytest_mark
|
pytestmark
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
**Tutorial**: :ref:`scoped-marking`
|
**Tutorial**: :ref:`scoped-marking`
|
||||||
|
|
||||||
|
|
|
@ -72,6 +72,8 @@ class Code:
|
||||||
""" return a path object pointing to source code (or a str in case
|
""" return a path object pointing to source code (or a str in case
|
||||||
of OSError / non-existing file).
|
of OSError / non-existing file).
|
||||||
"""
|
"""
|
||||||
|
if not self.raw.co_filename:
|
||||||
|
return ""
|
||||||
try:
|
try:
|
||||||
p = py.path.local(self.raw.co_filename)
|
p = py.path.local(self.raw.co_filename)
|
||||||
# maybe don't try this checking
|
# maybe don't try this checking
|
||||||
|
|
|
@ -8,6 +8,7 @@ import warnings
|
||||||
from bisect import bisect_right
|
from bisect import bisect_right
|
||||||
from types import CodeType
|
from types import CodeType
|
||||||
from types import FrameType
|
from types import FrameType
|
||||||
|
from typing import Any
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
@ -17,6 +18,7 @@ from typing import Union
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
|
||||||
|
from _pytest.compat import get_real_func
|
||||||
from _pytest.compat import overload
|
from _pytest.compat import overload
|
||||||
from _pytest.compat import TYPE_CHECKING
|
from _pytest.compat import TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -277,7 +279,7 @@ def compile_( # noqa: F811
|
||||||
return s.compile(filename, mode, flags, _genframe=_genframe)
|
return s.compile(filename, mode, flags, _genframe=_genframe)
|
||||||
|
|
||||||
|
|
||||||
def getfslineno(obj) -> Tuple[Optional[Union["Literal['']", py.path.local]], int]:
|
def getfslineno(obj: Any) -> Tuple[Union[str, py.path.local], int]:
|
||||||
""" Return source location (path, lineno) for the given object.
|
""" Return source location (path, lineno) for the given object.
|
||||||
If the source cannot be determined return ("", -1).
|
If the source cannot be determined return ("", -1).
|
||||||
|
|
||||||
|
@ -285,6 +287,13 @@ def getfslineno(obj) -> Tuple[Optional[Union["Literal['']", py.path.local]], int
|
||||||
"""
|
"""
|
||||||
from .code import Code
|
from .code import Code
|
||||||
|
|
||||||
|
# xxx let decorators etc specify a sane ordering
|
||||||
|
# NOTE: this used to be done in _pytest.compat.getfslineno, initially added
|
||||||
|
# in 6ec13a2b9. It ("place_as") appears to be something very custom.
|
||||||
|
obj = get_real_func(obj)
|
||||||
|
if hasattr(obj, "place_as"):
|
||||||
|
obj = obj.place_as
|
||||||
|
|
||||||
try:
|
try:
|
||||||
code = Code(obj)
|
code = Code(obj)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -293,18 +302,16 @@ def getfslineno(obj) -> Tuple[Optional[Union["Literal['']", py.path.local]], int
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return "", -1
|
return "", -1
|
||||||
|
|
||||||
fspath = fn and py.path.local(fn) or None
|
fspath = fn and py.path.local(fn) or ""
|
||||||
lineno = -1
|
lineno = -1
|
||||||
if fspath:
|
if fspath:
|
||||||
try:
|
try:
|
||||||
_, lineno = findsource(obj)
|
_, lineno = findsource(obj)
|
||||||
except IOError:
|
except IOError:
|
||||||
pass
|
pass
|
||||||
|
return fspath, lineno
|
||||||
else:
|
else:
|
||||||
fspath = code.path
|
return code.path, code.firstlineno
|
||||||
lineno = code.firstlineno
|
|
||||||
assert isinstance(lineno, int)
|
|
||||||
return fspath, lineno
|
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
@ -8,6 +8,7 @@ from _pytest.assertion import rewrite
|
||||||
from _pytest.assertion import truncate
|
from _pytest.assertion import truncate
|
||||||
from _pytest.assertion import util
|
from _pytest.assertion import util
|
||||||
from _pytest.compat import TYPE_CHECKING
|
from _pytest.compat import TYPE_CHECKING
|
||||||
|
from _pytest.config import hookimpl
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from _pytest.main import Session
|
from _pytest.main import Session
|
||||||
|
@ -105,7 +106,8 @@ def pytest_collection(session: "Session") -> None:
|
||||||
assertstate.hook.set_session(session)
|
assertstate.hook.set_session(session)
|
||||||
|
|
||||||
|
|
||||||
def pytest_runtest_setup(item):
|
@hookimpl(tryfirst=True, hookwrapper=True)
|
||||||
|
def pytest_runtest_protocol(item):
|
||||||
"""Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks
|
"""Setup the pytest_assertrepr_compare and pytest_assertion_pass hooks
|
||||||
|
|
||||||
The newinterpret and rewrite modules will use util._reprcompare if
|
The newinterpret and rewrite modules will use util._reprcompare if
|
||||||
|
@ -143,6 +145,7 @@ def pytest_runtest_setup(item):
|
||||||
return res
|
return res
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
saved_assert_hooks = util._reprcompare, util._assertion_pass
|
||||||
util._reprcompare = callbinrepr
|
util._reprcompare = callbinrepr
|
||||||
|
|
||||||
if item.ihook.pytest_assertion_pass.get_hookimpls():
|
if item.ihook.pytest_assertion_pass.get_hookimpls():
|
||||||
|
@ -154,10 +157,9 @@ def pytest_runtest_setup(item):
|
||||||
|
|
||||||
util._assertion_pass = call_assertion_pass_hook
|
util._assertion_pass = call_assertion_pass_hook
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
def pytest_runtest_teardown(item):
|
util._reprcompare, util._assertion_pass = saved_assert_hooks
|
||||||
util._reprcompare = None
|
|
||||||
util._assertion_pass = None
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_sessionfinish(session):
|
def pytest_sessionfinish(session):
|
||||||
|
|
|
@ -23,7 +23,6 @@ from typing import Union
|
||||||
import attr
|
import attr
|
||||||
import py
|
import py
|
||||||
|
|
||||||
import _pytest
|
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.outcomes import TEST_OUTCOME
|
from _pytest.outcomes import TEST_OUTCOME
|
||||||
|
@ -308,16 +307,6 @@ def get_real_method(obj, holder):
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
||||||
def getfslineno(obj) -> Tuple[Union[str, py.path.local], int]:
|
|
||||||
# xxx let decorators etc specify a sane ordering
|
|
||||||
obj = get_real_func(obj)
|
|
||||||
if hasattr(obj, "place_as"):
|
|
||||||
obj = obj.place_as
|
|
||||||
fslineno = _pytest._code.getfslineno(obj)
|
|
||||||
assert isinstance(fslineno[1], int), obj
|
|
||||||
return fslineno
|
|
||||||
|
|
||||||
|
|
||||||
def getimfunc(func):
|
def getimfunc(func):
|
||||||
try:
|
try:
|
||||||
return func.__func__
|
return func.__func__
|
||||||
|
|
|
@ -28,7 +28,6 @@ from pluggy import HookspecMarker
|
||||||
from pluggy import PluginManager
|
from pluggy import PluginManager
|
||||||
|
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
import _pytest.assertion
|
|
||||||
import _pytest.deprecated
|
import _pytest.deprecated
|
||||||
import _pytest.hookspec # the extension point definitions
|
import _pytest.hookspec # the extension point definitions
|
||||||
from .exceptions import PrintHelp
|
from .exceptions import PrintHelp
|
||||||
|
@ -284,6 +283,8 @@ class PytestPluginManager(PluginManager):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
import _pytest.assertion
|
||||||
|
|
||||||
super().__init__("pytest")
|
super().__init__("pytest")
|
||||||
# The objects are module objects, only used generically.
|
# The objects are module objects, only used generically.
|
||||||
self._conftest_plugins = set() # type: Set[object]
|
self._conftest_plugins = set() # type: Set[object]
|
||||||
|
@ -917,6 +918,8 @@ class Config:
|
||||||
ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
|
ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
|
||||||
mode = getattr(ns, "assertmode", "plain")
|
mode = getattr(ns, "assertmode", "plain")
|
||||||
if mode == "rewrite":
|
if mode == "rewrite":
|
||||||
|
import _pytest.assertion
|
||||||
|
|
||||||
try:
|
try:
|
||||||
hook = _pytest.assertion.install_importhook(self)
|
hook = _pytest.assertion.install_importhook(self)
|
||||||
except SystemError:
|
except SystemError:
|
||||||
|
|
|
@ -15,12 +15,12 @@ import py
|
||||||
import _pytest
|
import _pytest
|
||||||
from _pytest._code.code import FormattedExcinfo
|
from _pytest._code.code import FormattedExcinfo
|
||||||
from _pytest._code.code import TerminalRepr
|
from _pytest._code.code import TerminalRepr
|
||||||
|
from _pytest._code.source import getfslineno
|
||||||
from _pytest._io import TerminalWriter
|
from _pytest._io import TerminalWriter
|
||||||
from _pytest.compat import _format_args
|
from _pytest.compat import _format_args
|
||||||
from _pytest.compat import _PytestWrapper
|
from _pytest.compat import _PytestWrapper
|
||||||
from _pytest.compat import get_real_func
|
from _pytest.compat import get_real_func
|
||||||
from _pytest.compat import get_real_method
|
from _pytest.compat import get_real_method
|
||||||
from _pytest.compat import getfslineno
|
|
||||||
from _pytest.compat import getfuncargnames
|
from _pytest.compat import getfuncargnames
|
||||||
from _pytest.compat import getimfunc
|
from _pytest.compat import getimfunc
|
||||||
from _pytest.compat import getlocation
|
from _pytest.compat import getlocation
|
||||||
|
|
|
@ -10,6 +10,7 @@ from typing import List
|
||||||
from typing import Mapping
|
from typing import Mapping
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from _pytest import nodes
|
||||||
from _pytest.compat import nullcontext
|
from _pytest.compat import nullcontext
|
||||||
from _pytest.config import _strtobool
|
from _pytest.config import _strtobool
|
||||||
from _pytest.config import create_terminal_writer
|
from _pytest.config import create_terminal_writer
|
||||||
|
@ -326,13 +327,13 @@ class LogCaptureFixture:
|
||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def handler(self):
|
def handler(self) -> LogCaptureHandler:
|
||||||
"""
|
"""
|
||||||
:rtype: LogCaptureHandler
|
:rtype: LogCaptureHandler
|
||||||
"""
|
"""
|
||||||
return self._item.catch_log_handler
|
return self._item.catch_log_handler # type: ignore[no-any-return] # noqa: F723
|
||||||
|
|
||||||
def get_records(self, when):
|
def get_records(self, when: str) -> List[logging.LogRecord]:
|
||||||
"""
|
"""
|
||||||
Get the logging records for one of the possible test phases.
|
Get the logging records for one of the possible test phases.
|
||||||
|
|
||||||
|
@ -346,7 +347,7 @@ class LogCaptureFixture:
|
||||||
"""
|
"""
|
||||||
handler = self._item.catch_log_handlers.get(when)
|
handler = self._item.catch_log_handlers.get(when)
|
||||||
if handler:
|
if handler:
|
||||||
return handler.records
|
return handler.records # type: ignore[no-any-return] # noqa: F723
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -619,7 +620,9 @@ class LoggingPlugin:
|
||||||
yield
|
yield
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def _runtest_for_main(self, item, when):
|
def _runtest_for_main(
|
||||||
|
self, item: nodes.Item, when: str
|
||||||
|
) -> Generator[None, None, None]:
|
||||||
"""Implements the internals of pytest_runtest_xxx() hook."""
|
"""Implements the internals of pytest_runtest_xxx() hook."""
|
||||||
with catching_logs(
|
with catching_logs(
|
||||||
LogCaptureHandler(), formatter=self.formatter, level=self.log_level
|
LogCaptureHandler(), formatter=self.formatter, level=self.log_level
|
||||||
|
@ -632,15 +635,15 @@ class LoggingPlugin:
|
||||||
return
|
return
|
||||||
|
|
||||||
if not hasattr(item, "catch_log_handlers"):
|
if not hasattr(item, "catch_log_handlers"):
|
||||||
item.catch_log_handlers = {}
|
item.catch_log_handlers = {} # type: ignore[attr-defined] # noqa: F821
|
||||||
item.catch_log_handlers[when] = log_handler
|
item.catch_log_handlers[when] = log_handler # type: ignore[attr-defined] # noqa: F821
|
||||||
item.catch_log_handler = log_handler
|
item.catch_log_handler = log_handler # type: ignore[attr-defined] # noqa: F821
|
||||||
try:
|
try:
|
||||||
yield # run test
|
yield # run test
|
||||||
finally:
|
finally:
|
||||||
if when == "teardown":
|
if when == "teardown":
|
||||||
del item.catch_log_handler
|
del item.catch_log_handler # type: ignore[attr-defined] # noqa: F821
|
||||||
del item.catch_log_handlers
|
del item.catch_log_handlers # type: ignore[attr-defined] # noqa: F821
|
||||||
|
|
||||||
if self.print_logs:
|
if self.print_logs:
|
||||||
# Add a captured log section to the report.
|
# Add a captured log section to the report.
|
||||||
|
|
|
@ -4,6 +4,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
|
||||||
|
@ -24,7 +25,7 @@ from _pytest.config import ExitCode
|
||||||
from _pytest.config import hookimpl
|
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.outcomes import exit
|
from _pytest.outcomes import Exit
|
||||||
from _pytest.reports import CollectReport
|
from _pytest.reports import CollectReport
|
||||||
from _pytest.runner import collect_one_node
|
from _pytest.runner import collect_one_node
|
||||||
from _pytest.runner import SetupState
|
from _pytest.runner import SetupState
|
||||||
|
@ -175,7 +176,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.from_config(config)
|
session = Session.from_config(config)
|
||||||
session.exitstatus = ExitCode.OK
|
session.exitstatus = ExitCode.OK
|
||||||
|
@ -192,10 +195,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:
|
||||||
|
@ -209,7 +212,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))
|
||||||
|
@ -218,12 +221,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
|
||||||
|
|
||||||
|
@ -363,6 +372,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__(
|
||||||
|
|
|
@ -2,14 +2,16 @@ import inspect
|
||||||
import warnings
|
import warnings
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from collections.abc import MutableMapping
|
from collections.abc import MutableMapping
|
||||||
|
from typing import Iterable
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Set
|
from typing import Set
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
|
||||||
|
from .._code.source import getfslineno
|
||||||
from ..compat import ascii_escaped
|
from ..compat import ascii_escaped
|
||||||
from ..compat import getfslineno
|
|
||||||
from ..compat import NOTSET
|
from ..compat import NOTSET
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.warning_types import PytestUnknownMarkWarning
|
from _pytest.warning_types import PytestUnknownMarkWarning
|
||||||
|
@ -270,7 +272,7 @@ def get_unpacked_marks(obj):
|
||||||
return normalize_mark_list(mark_list)
|
return normalize_mark_list(mark_list)
|
||||||
|
|
||||||
|
|
||||||
def normalize_mark_list(mark_list):
|
def normalize_mark_list(mark_list: Iterable[Union[Mark, MarkDecorator]]) -> List[Mark]:
|
||||||
"""
|
"""
|
||||||
normalizes marker decorating helpers to mark objects
|
normalizes marker decorating helpers to mark objects
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,8 @@ import _pytest._code
|
||||||
from _pytest._code.code import ExceptionChainRepr
|
from _pytest._code.code import ExceptionChainRepr
|
||||||
from _pytest._code.code import ExceptionInfo
|
from _pytest._code.code import ExceptionInfo
|
||||||
from _pytest._code.code import ReprExceptionInfo
|
from _pytest._code.code import ReprExceptionInfo
|
||||||
|
from _pytest._code.source import getfslineno
|
||||||
from _pytest.compat import cached_property
|
from _pytest.compat import cached_property
|
||||||
from _pytest.compat import getfslineno
|
|
||||||
from _pytest.compat import TYPE_CHECKING
|
from _pytest.compat import TYPE_CHECKING
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
from _pytest.config import PytestPluginManager
|
from _pytest.config import PytestPluginManager
|
||||||
|
@ -361,7 +361,9 @@ class Node(metaclass=NodeMeta):
|
||||||
return self._repr_failure_py(excinfo, style)
|
return self._repr_failure_py(excinfo, style)
|
||||||
|
|
||||||
|
|
||||||
def get_fslocation_from_item(item):
|
def get_fslocation_from_item(
|
||||||
|
item: "Item",
|
||||||
|
) -> Tuple[Union[str, py.path.local], Optional[int]]:
|
||||||
"""Tries to extract the actual location from an item, depending on available attributes:
|
"""Tries to extract the actual location from an item, depending on available attributes:
|
||||||
|
|
||||||
* "fslocation": a pair (path, lineno)
|
* "fslocation": a pair (path, lineno)
|
||||||
|
@ -370,9 +372,10 @@ def get_fslocation_from_item(item):
|
||||||
|
|
||||||
:rtype: a tuple of (str|LocalPath, int) with filename and line number.
|
:rtype: a tuple of (str|LocalPath, int) with filename and line number.
|
||||||
"""
|
"""
|
||||||
result = getattr(item, "location", None)
|
try:
|
||||||
if result is not None:
|
return item.location[:2]
|
||||||
return result[:2]
|
except AttributeError:
|
||||||
|
pass
|
||||||
obj = getattr(item, "obj", None)
|
obj = getattr(item, "obj", None)
|
||||||
if obj is not None:
|
if obj is not None:
|
||||||
return getfslineno(obj)
|
return getfslineno(obj)
|
||||||
|
|
|
@ -610,14 +610,14 @@ class Testdir:
|
||||||
"""
|
"""
|
||||||
self.tmpdir.chdir()
|
self.tmpdir.chdir()
|
||||||
|
|
||||||
def _makefile(self, ext, args, kwargs, encoding="utf-8"):
|
def _makefile(self, ext, lines, files, encoding="utf-8"):
|
||||||
items = list(kwargs.items())
|
items = list(files.items())
|
||||||
|
|
||||||
def to_text(s):
|
def to_text(s):
|
||||||
return s.decode(encoding) if isinstance(s, bytes) else str(s)
|
return s.decode(encoding) if isinstance(s, bytes) else str(s)
|
||||||
|
|
||||||
if args:
|
if lines:
|
||||||
source = "\n".join(to_text(x) for x in args)
|
source = "\n".join(to_text(x) for x in lines)
|
||||||
basename = self.request.function.__name__
|
basename = self.request.function.__name__
|
||||||
items.insert(0, (basename, source))
|
items.insert(0, (basename, source))
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ from collections import Counter
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from typing import Dict
|
||||||
from typing import List
|
from typing import List
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
@ -21,10 +22,10 @@ from _pytest import fixtures
|
||||||
from _pytest import nodes
|
from _pytest import nodes
|
||||||
from _pytest._code import filter_traceback
|
from _pytest._code import filter_traceback
|
||||||
from _pytest._code.code import ExceptionInfo
|
from _pytest._code.code import ExceptionInfo
|
||||||
|
from _pytest._code.source import getfslineno
|
||||||
from _pytest.compat import ascii_escaped
|
from _pytest.compat import ascii_escaped
|
||||||
from _pytest.compat import get_default_arg_names
|
from _pytest.compat import get_default_arg_names
|
||||||
from _pytest.compat import get_real_func
|
from _pytest.compat import get_real_func
|
||||||
from _pytest.compat import getfslineno
|
|
||||||
from _pytest.compat import getimfunc
|
from _pytest.compat import getimfunc
|
||||||
from _pytest.compat import getlocation
|
from _pytest.compat import getlocation
|
||||||
from _pytest.compat import is_generator
|
from _pytest.compat import is_generator
|
||||||
|
@ -37,6 +38,7 @@ from _pytest.compat import STRING_TYPES
|
||||||
from _pytest.config import hookimpl
|
from _pytest.config import hookimpl
|
||||||
from _pytest.deprecated import FUNCARGNAMES
|
from _pytest.deprecated import FUNCARGNAMES
|
||||||
from _pytest.mark import MARK_GEN
|
from _pytest.mark import MARK_GEN
|
||||||
|
from _pytest.mark import ParameterSet
|
||||||
from _pytest.mark.structures import get_unpacked_marks
|
from _pytest.mark.structures import get_unpacked_marks
|
||||||
from _pytest.mark.structures import Mark
|
from _pytest.mark.structures import Mark
|
||||||
from _pytest.mark.structures import normalize_mark_list
|
from _pytest.mark.structures import normalize_mark_list
|
||||||
|
@ -392,7 +394,7 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
||||||
fm = self.session._fixturemanager
|
fm = self.session._fixturemanager
|
||||||
|
|
||||||
definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj)
|
definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj)
|
||||||
fixtureinfo = fm.getfixtureinfo(definition, funcobj, cls)
|
fixtureinfo = definition._fixtureinfo
|
||||||
|
|
||||||
metafunc = Metafunc(
|
metafunc = Metafunc(
|
||||||
definition, fixtureinfo, self.config, cls=cls, module=module
|
definition, fixtureinfo, self.config, cls=cls, module=module
|
||||||
|
@ -931,7 +933,6 @@ class Metafunc:
|
||||||
to set a dynamic scope using test context or configuration.
|
to set a dynamic scope using test context or configuration.
|
||||||
"""
|
"""
|
||||||
from _pytest.fixtures import scope2index
|
from _pytest.fixtures import scope2index
|
||||||
from _pytest.mark import ParameterSet
|
|
||||||
|
|
||||||
argnames, parameters = ParameterSet._for_parametrize(
|
argnames, parameters = ParameterSet._for_parametrize(
|
||||||
argnames,
|
argnames,
|
||||||
|
@ -992,7 +993,9 @@ class Metafunc:
|
||||||
newcalls.append(newcallspec)
|
newcalls.append(newcallspec)
|
||||||
self._calls = newcalls
|
self._calls = newcalls
|
||||||
|
|
||||||
def _resolve_arg_ids(self, argnames, ids, parameters, item):
|
def _resolve_arg_ids(
|
||||||
|
self, argnames: List[str], ids, parameters: List[ParameterSet], item: nodes.Item
|
||||||
|
):
|
||||||
"""Resolves the actual ids for the given argnames, based on the ``ids`` parameter given
|
"""Resolves the actual ids for the given argnames, based on the ``ids`` parameter given
|
||||||
to ``parametrize``.
|
to ``parametrize``.
|
||||||
|
|
||||||
|
@ -1045,7 +1048,7 @@ class Metafunc:
|
||||||
)
|
)
|
||||||
return new_ids
|
return new_ids
|
||||||
|
|
||||||
def _resolve_arg_value_types(self, argnames, indirect):
|
def _resolve_arg_value_types(self, argnames: List[str], indirect) -> Dict[str, str]:
|
||||||
"""Resolves if each parametrized argument must be considered a parameter to a fixture or a "funcarg"
|
"""Resolves if each parametrized argument must be considered a parameter to a fixture or a "funcarg"
|
||||||
to the function, based on the ``indirect`` parameter of the parametrized() call.
|
to the function, based on the ``indirect`` parameter of the parametrized() call.
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,8 @@ class TestGeneralUsage:
|
||||||
|
|
||||||
@pytest.mark.parametrize("load_cov_early", [True, False])
|
@pytest.mark.parametrize("load_cov_early", [True, False])
|
||||||
def test_early_load_setuptools_name(self, testdir, monkeypatch, load_cov_early):
|
def test_early_load_setuptools_name(self, testdir, monkeypatch, load_cov_early):
|
||||||
|
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
|
||||||
|
|
||||||
testdir.makepyfile(mytestplugin1_module="")
|
testdir.makepyfile(mytestplugin1_module="")
|
||||||
testdir.makepyfile(mytestplugin2_module="")
|
testdir.makepyfile(mytestplugin2_module="")
|
||||||
testdir.makepyfile(mycov_module="")
|
testdir.makepyfile(mycov_module="")
|
||||||
|
|
|
@ -524,6 +524,14 @@ def test_getfslineno() -> None:
|
||||||
B.__name__ = "B2"
|
B.__name__ = "B2"
|
||||||
assert getfslineno(B)[1] == -1
|
assert getfslineno(B)[1] == -1
|
||||||
|
|
||||||
|
co = compile("...", "", "eval")
|
||||||
|
assert co.co_filename == ""
|
||||||
|
|
||||||
|
if hasattr(sys, "pypy_version_info"):
|
||||||
|
assert getfslineno(co) == ("", -1)
|
||||||
|
else:
|
||||||
|
assert getfslineno(co) == ("", 0)
|
||||||
|
|
||||||
|
|
||||||
def test_code_of_object_instance_with_call() -> None:
|
def test_code_of_object_instance_with_call() -> None:
|
||||||
class A:
|
class A:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from _pytest.pytester import Testdir
|
||||||
|
|
||||||
if sys.gettrace():
|
if sys.gettrace():
|
||||||
|
|
||||||
|
@ -118,3 +119,9 @@ def dummy_yaml_custom_test(testdir):
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
testdir.makefile(".yaml", test1="")
|
testdir.makefile(".yaml", test1="")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def testdir(testdir: Testdir) -> Testdir:
|
||||||
|
testdir.monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1")
|
||||||
|
return testdir
|
||||||
|
|
|
@ -72,10 +72,19 @@ class TestImportHookInstallation:
|
||||||
result = testdir.runpytest_subprocess()
|
result = testdir.runpytest_subprocess()
|
||||||
result.stdout.fnmatch_lines(
|
result.stdout.fnmatch_lines(
|
||||||
[
|
[
|
||||||
"E * AssertionError: ([[][]], [[][]], [[]<TestReport *>[]])*",
|
"> r.assertoutcome(passed=1)",
|
||||||
"E * assert"
|
"E AssertionError: ([[][]], [[][]], [[]<TestReport *>[]])*",
|
||||||
" {'failed': 1, 'passed': 0, 'skipped': 0} =="
|
"E assert {'failed': 1,... 'skipped': 0} == {'failed': 0,... 'skipped': 0}",
|
||||||
" {'failed': 0, 'passed': 1, 'skipped': 0}",
|
"E Omitting 1 identical items, use -vv to show",
|
||||||
|
"E Differing items:",
|
||||||
|
"E Use -v to get the full diff",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# XXX: unstable output.
|
||||||
|
result.stdout.fnmatch_lines_random(
|
||||||
|
[
|
||||||
|
"E {'failed': 1} != {'failed': 0}",
|
||||||
|
"E {'passed': 0} != {'passed': 1}",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ from _pytest.config import ExitCode
|
||||||
|
|
||||||
|
|
||||||
def test_version(testdir, pytestconfig):
|
def test_version(testdir, pytestconfig):
|
||||||
|
testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
|
||||||
result = testdir.runpytest("--version")
|
result = testdir.runpytest("--version")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
# p = py.path.local(py.__file__).dirpath()
|
# p = py.path.local(py.__file__).dirpath()
|
||||||
|
|
|
@ -1300,6 +1300,7 @@ def test_runs_twice(testdir, run_and_parse):
|
||||||
|
|
||||||
def test_runs_twice_xdist(testdir, run_and_parse):
|
def test_runs_twice_xdist(testdir, run_and_parse):
|
||||||
pytest.importorskip("xdist")
|
pytest.importorskip("xdist")
|
||||||
|
testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
|
||||||
f = testdir.makepyfile(
|
f = testdir.makepyfile(
|
||||||
"""
|
"""
|
||||||
def test_pass():
|
def test_pass():
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config 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"]
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
"""
|
||||||
|
Test importing of all internal packages and modules.
|
||||||
|
|
||||||
|
This ensures all internal packages can be imported without needing the pytest
|
||||||
|
namespace being set, which is critical for the initialization of xdist.
|
||||||
|
"""
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import py
|
|
||||||
|
|
||||||
import _pytest
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
pytestmark = pytest.mark.slow
|
|
||||||
|
|
||||||
MODSET = [
|
|
||||||
x
|
|
||||||
for x in py.path.local(_pytest.__file__).dirpath().visit("*.py")
|
|
||||||
if x.purebasename != "__init__"
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("modfile", MODSET, ids=lambda x: x.purebasename)
|
|
||||||
def test_fileimport(modfile):
|
|
||||||
# this test ensures all internal packages can import
|
|
||||||
# without needing the pytest namespace being set
|
|
||||||
# this is critical for the initialization of xdist
|
|
||||||
|
|
||||||
p = subprocess.Popen(
|
|
||||||
[
|
|
||||||
sys.executable,
|
|
||||||
"-c",
|
|
||||||
"import sys, py; py.path.local(sys.argv[1]).pyimport()",
|
|
||||||
modfile.strpath,
|
|
||||||
],
|
|
||||||
stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.PIPE,
|
|
||||||
)
|
|
||||||
(out, err) = p.communicate()
|
|
||||||
assert p.returncode == 0, "importing %s failed (exitcode %d): out=%r, err=%r" % (
|
|
||||||
modfile,
|
|
||||||
p.returncode,
|
|
||||||
out,
|
|
||||||
err,
|
|
||||||
)
|
|
|
@ -1,7 +1,7 @@
|
||||||
import argparse
|
import argparse
|
||||||
import distutils.spawn
|
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import py
|
import py
|
||||||
|
@ -288,7 +288,7 @@ class TestParser:
|
||||||
|
|
||||||
|
|
||||||
def test_argcomplete(testdir, monkeypatch) -> None:
|
def test_argcomplete(testdir, monkeypatch) -> None:
|
||||||
if not distutils.spawn.find_executable("bash"):
|
if not shutil.which("bash"):
|
||||||
pytest.skip("bash not available")
|
pytest.skip("bash not available")
|
||||||
script = str(testdir.tmpdir.join("test_argcomplete"))
|
script = str(testdir.tmpdir.join("test_argcomplete"))
|
||||||
|
|
||||||
|
|
|
@ -606,6 +606,7 @@ class TestTerminalFunctional:
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
def test_header_trailer_info(self, testdir, request):
|
def test_header_trailer_info(self, testdir, request):
|
||||||
|
testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
"""
|
"""
|
||||||
def test_passes():
|
def test_passes():
|
||||||
|
@ -736,6 +737,7 @@ class TestTerminalFunctional:
|
||||||
if not pytestconfig.pluginmanager.get_plugin("xdist"):
|
if not pytestconfig.pluginmanager.get_plugin("xdist"):
|
||||||
pytest.skip("xdist plugin not installed")
|
pytest.skip("xdist plugin not installed")
|
||||||
|
|
||||||
|
testdir.monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD")
|
||||||
result = testdir.runpytest(
|
result = testdir.runpytest(
|
||||||
verbose_testfile, "-v", "-n 1", "-Walways::pytest.PytestWarning"
|
verbose_testfile, "-v", "-n 1", "-Walways::pytest.PytestWarning"
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in New Issue