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:
Bruno Oliveira 2020-02-11 10:32:18 -03:00 committed by Bruno Oliveira
commit 78baa7b575
31 changed files with 161 additions and 138 deletions

View File

@ -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:

View File

@ -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:
- | - |

View File

@ -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.

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

@ -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,

BIN
doc/en/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -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`

View File

@ -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

View File

@ -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
# #

View File

@ -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):

View File

@ -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__

View File

@ -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:

View File

@ -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

View File

@ -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.

View File

@ -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__(

View File

@ -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

View File

@ -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)

View File

@ -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))

View File

@ -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.

View File

@ -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="")

View File

@ -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:

View File

@ -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

View File

@ -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}",
] ]
) )

View File

@ -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()

View File

@ -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():

View File

@ -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"]

View File

@ -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

View File

@ -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,
)

View File

@ -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"))

View File

@ -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"
) )