Merge branch 'main' into Feature-consider-nonparametrized-tests-in-reordering
This commit is contained in:
commit
2336ea53db
|
@ -27,7 +27,7 @@ jobs:
|
||||||
- name: Setup Python
|
- name: Setup Python
|
||||||
uses: actions/setup-python@v4
|
uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.8"
|
python-version: "3.11"
|
||||||
cache: pip
|
cache: pip
|
||||||
- name: requests-cache
|
- name: requests-cache
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
|
|
|
@ -5,7 +5,7 @@ repos:
|
||||||
- id: black
|
- id: black
|
||||||
args: [--safe, --quiet]
|
args: [--safe, --quiet]
|
||||||
- repo: https://github.com/asottile/blacken-docs
|
- repo: https://github.com/asottile/blacken-docs
|
||||||
rev: 1.15.0
|
rev: 1.16.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: blacken-docs
|
- id: blacken-docs
|
||||||
additional_dependencies: [black==23.7.0]
|
additional_dependencies: [black==23.7.0]
|
||||||
|
@ -56,7 +56,7 @@ repos:
|
||||||
hooks:
|
hooks:
|
||||||
- id: python-use-type-annotations
|
- id: python-use-type-annotations
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v1.4.1
|
rev: v1.5.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
files: ^(src/|testing/)
|
files: ^(src/|testing/)
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
(This entry is meant to assist plugins which access private pytest internals to instantiate ``FixtureRequest`` objects.)
|
||||||
|
|
||||||
|
:class:`~pytest.FixtureRequest` is now an abstract class which can't be instantiated directly.
|
||||||
|
A new concrete ``TopRequest`` subclass of ``FixtureRequest`` has been added for the ``request`` fixture in test functions,
|
||||||
|
as counterpart to the existing ``SubRequest`` subclass for the ``request`` fixture in fixture functions.
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fixed a bug that when there are multiple fixtures for an indirect parameter,
|
||||||
|
the scope of the highest-scope fixture is picked for the parameter set, instead of that of the one with the narrowest scope.
|
|
@ -0,0 +1,2 @@
|
||||||
|
Corrected the spelling of ``Config.ArgsSource.INVOCATION_DIR``.
|
||||||
|
The previous spelling ``INCOVATION_DIR`` remains as an alias.
|
|
@ -0,0 +1 @@
|
||||||
|
Removes unhelpful error message from assertion rewrite mechanism when exceptions raised in __iter__ methods, and instead treats them as un-iterable.
|
|
@ -34,7 +34,7 @@ a function/method call.
|
||||||
|
|
||||||
**Assert** is where we look at that resulting state and check if it looks how
|
**Assert** is where we look at that resulting state and check if it looks how
|
||||||
we'd expect after the dust has settled. It's where we gather evidence to say the
|
we'd expect after the dust has settled. It's where we gather evidence to say the
|
||||||
behavior does or does not aligns with what we expect. The ``assert`` in our test
|
behavior does or does not align with what we expect. The ``assert`` in our test
|
||||||
is where we take that measurement/observation and apply our judgement to it. If
|
is where we take that measurement/observation and apply our judgement to it. If
|
||||||
something should be green, we'd say ``assert thing == "green"``.
|
something should be green, we'd say ``assert thing == "green"``.
|
||||||
|
|
||||||
|
|
|
@ -54,14 +54,13 @@ operators. (See :ref:`tbreportdemo`). This allows you to use the
|
||||||
idiomatic python constructs without boilerplate code while not losing
|
idiomatic python constructs without boilerplate code while not losing
|
||||||
introspection information.
|
introspection information.
|
||||||
|
|
||||||
However, if you specify a message with the assertion like this:
|
If a message is specified with the assertion like this:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
assert a % 2 == 0, "value was odd, should be even"
|
assert a % 2 == 0, "value was odd, should be even"
|
||||||
|
|
||||||
then no assertion introspection takes places at all and the message
|
it is printed alongside the assertion introspection in the traceback.
|
||||||
will be simply shown in the traceback.
|
|
||||||
|
|
||||||
See :ref:`assert-details` for more information on assertion introspection.
|
See :ref:`assert-details` for more information on assertion introspection.
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -82,6 +82,8 @@ pytest.exit
|
||||||
pytest.main
|
pytest.main
|
||||||
~~~~~~~~~~~
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
**Tutorial**: :ref:`pytest.main-usage`
|
||||||
|
|
||||||
.. autofunction:: pytest.main
|
.. autofunction:: pytest.main
|
||||||
|
|
||||||
pytest.param
|
pytest.param
|
||||||
|
@ -235,7 +237,7 @@ pytest.mark.xfail
|
||||||
|
|
||||||
Marks a test function as *expected to fail*.
|
Marks a test function as *expected to fail*.
|
||||||
|
|
||||||
.. py:function:: pytest.mark.xfail(condition=None, *, reason=None, raises=None, run=True, strict=False)
|
.. py:function:: pytest.mark.xfail(condition=None, *, reason=None, raises=None, run=True, strict=xfail_strict)
|
||||||
|
|
||||||
:type condition: bool or str
|
:type condition: bool or str
|
||||||
:param condition:
|
:param condition:
|
||||||
|
@ -247,10 +249,10 @@ Marks a test function as *expected to fail*.
|
||||||
:keyword Type[Exception] raises:
|
:keyword Type[Exception] raises:
|
||||||
Exception subclass (or tuple of subclasses) expected to be raised by the test function; other exceptions will fail the test.
|
Exception subclass (or tuple of subclasses) expected to be raised by the test function; other exceptions will fail the test.
|
||||||
:keyword bool run:
|
:keyword bool run:
|
||||||
If the test function should actually be executed. If ``False``, the function will always xfail and will
|
Whether the test function should actually be executed. If ``False``, the function will always xfail and will
|
||||||
not be executed (useful if a function is segfaulting).
|
not be executed (useful if a function is segfaulting).
|
||||||
:keyword bool strict:
|
:keyword bool strict:
|
||||||
* If ``False`` (the default) the function will be shown in the terminal output as ``xfailed`` if it fails
|
* If ``False`` the function will be shown in the terminal output as ``xfailed`` if it fails
|
||||||
and as ``xpass`` if it passes. In both cases this will not cause the test suite to fail as a whole. This
|
and as ``xpass`` if it passes. In both cases this will not cause the test suite to fail as a whole. This
|
||||||
is particularly useful to mark *flaky* tests (tests that fail at random) to be tackled later.
|
is particularly useful to mark *flaky* tests (tests that fail at random) to be tackled later.
|
||||||
* If ``True``, the function will be shown in the terminal output as ``xfailed`` if it fails, but if it
|
* If ``True``, the function will be shown in the terminal output as ``xfailed`` if it fails, but if it
|
||||||
|
@ -258,6 +260,8 @@ Marks a test function as *expected to fail*.
|
||||||
that are always failing and there should be a clear indication if they unexpectedly start to pass (for example
|
that are always failing and there should be a clear indication if they unexpectedly start to pass (for example
|
||||||
a new release of a library fixes a known bug).
|
a new release of a library fixes a known bug).
|
||||||
|
|
||||||
|
Defaults to :confval:`xfail_strict`, which is ``False`` by default.
|
||||||
|
|
||||||
|
|
||||||
Custom marks
|
Custom marks
|
||||||
~~~~~~~~~~~~
|
~~~~~~~~~~~~
|
||||||
|
@ -1636,11 +1640,11 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||||
Additionally, ``pytest`` will attempt to intelligently identify and ignore a
|
Additionally, ``pytest`` will attempt to intelligently identify and ignore a
|
||||||
virtualenv by the presence of an activation script. Any directory deemed to
|
virtualenv by the presence of an activation script. Any directory deemed to
|
||||||
be the root of a virtual environment will not be considered during test
|
be the root of a virtual environment will not be considered during test
|
||||||
collection unless ``‑‑collect‑in‑virtualenv`` is given. Note also that
|
collection unless ``--collect-in-virtualenv`` is given. Note also that
|
||||||
``norecursedirs`` takes precedence over ``‑‑collect‑in‑virtualenv``; e.g. if
|
``norecursedirs`` takes precedence over ``--collect-in-virtualenv``; e.g. if
|
||||||
you intend to run tests in a virtualenv with a base directory that matches
|
you intend to run tests in a virtualenv with a base directory that matches
|
||||||
``'.*'`` you *must* override ``norecursedirs`` in addition to using the
|
``'.*'`` you *must* override ``norecursedirs`` in addition to using the
|
||||||
``‑‑collect‑in‑virtualenv`` flag.
|
``--collect-in-virtualenv`` flag.
|
||||||
|
|
||||||
|
|
||||||
.. confval:: python_classes
|
.. confval:: python_classes
|
||||||
|
|
|
@ -17,7 +17,12 @@ python_classes = ["Test", "Acceptance"]
|
||||||
python_functions = ["test"]
|
python_functions = ["test"]
|
||||||
# NOTE: "doc" is not included here, but gets tested explicitly via "doctesting".
|
# NOTE: "doc" is not included here, but gets tested explicitly via "doctesting".
|
||||||
testpaths = ["testing"]
|
testpaths = ["testing"]
|
||||||
norecursedirs = ["testing/example_scripts"]
|
norecursedirs = [
|
||||||
|
"testing/example_scripts",
|
||||||
|
".*",
|
||||||
|
"build",
|
||||||
|
"dist",
|
||||||
|
]
|
||||||
xfail_strict = true
|
xfail_strict = true
|
||||||
filterwarnings = [
|
filterwarnings = [
|
||||||
"error",
|
"error",
|
||||||
|
|
|
@ -44,6 +44,7 @@ DEVELOPMENT_STATUS_CLASSIFIERS = (
|
||||||
)
|
)
|
||||||
ADDITIONAL_PROJECTS = { # set of additional projects to consider as plugins
|
ADDITIONAL_PROJECTS = { # set of additional projects to consider as plugins
|
||||||
"logassert",
|
"logassert",
|
||||||
|
"nuts",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -132,7 +132,7 @@ def isiterable(obj: Any) -> bool:
|
||||||
try:
|
try:
|
||||||
iter(obj)
|
iter(obj)
|
||||||
return not istext(obj)
|
return not istext(obj)
|
||||||
except TypeError:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -581,26 +581,25 @@ class PytestPluginManager(PluginManager):
|
||||||
def _try_load_conftest(
|
def _try_load_conftest(
|
||||||
self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
|
self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path
|
||||||
) -> None:
|
) -> None:
|
||||||
self._getconftestmodules(anchor, importmode, rootpath)
|
self._loadconftestmodules(anchor, importmode, rootpath)
|
||||||
# let's also consider test* subdirs
|
# let's also consider test* subdirs
|
||||||
if anchor.is_dir():
|
if anchor.is_dir():
|
||||||
for x in anchor.glob("test*"):
|
for x in anchor.glob("test*"):
|
||||||
if x.is_dir():
|
if x.is_dir():
|
||||||
self._getconftestmodules(x, importmode, rootpath)
|
self._loadconftestmodules(x, importmode, rootpath)
|
||||||
|
|
||||||
def _getconftestmodules(
|
def _loadconftestmodules(
|
||||||
self, path: Path, importmode: Union[str, ImportMode], rootpath: Path
|
self, path: Path, importmode: Union[str, ImportMode], rootpath: Path
|
||||||
) -> Sequence[types.ModuleType]:
|
) -> None:
|
||||||
if self._noconftest:
|
if self._noconftest:
|
||||||
return []
|
return
|
||||||
|
|
||||||
directory = self._get_directory(path)
|
directory = self._get_directory(path)
|
||||||
|
|
||||||
# Optimization: avoid repeated searches in the same directory.
|
# Optimization: avoid repeated searches in the same directory.
|
||||||
# Assumes always called with same importmode and rootpath.
|
# Assumes always called with same importmode and rootpath.
|
||||||
existing_clist = self._dirpath2confmods.get(directory)
|
if directory in self._dirpath2confmods:
|
||||||
if existing_clist is not None:
|
return
|
||||||
return existing_clist
|
|
||||||
|
|
||||||
# XXX these days we may rather want to use config.rootpath
|
# XXX these days we may rather want to use config.rootpath
|
||||||
# and allow users to opt into looking into the rootdir parent
|
# and allow users to opt into looking into the rootdir parent
|
||||||
|
@ -613,16 +612,17 @@ class PytestPluginManager(PluginManager):
|
||||||
mod = self._importconftest(conftestpath, importmode, rootpath)
|
mod = self._importconftest(conftestpath, importmode, rootpath)
|
||||||
clist.append(mod)
|
clist.append(mod)
|
||||||
self._dirpath2confmods[directory] = clist
|
self._dirpath2confmods[directory] = clist
|
||||||
return clist
|
|
||||||
|
def _getconftestmodules(self, path: Path) -> Sequence[types.ModuleType]:
|
||||||
|
directory = self._get_directory(path)
|
||||||
|
return self._dirpath2confmods.get(directory, ())
|
||||||
|
|
||||||
def _rget_with_confmod(
|
def _rget_with_confmod(
|
||||||
self,
|
self,
|
||||||
name: str,
|
name: str,
|
||||||
path: Path,
|
path: Path,
|
||||||
importmode: Union[str, ImportMode],
|
|
||||||
rootpath: Path,
|
|
||||||
) -> Tuple[types.ModuleType, Any]:
|
) -> Tuple[types.ModuleType, Any]:
|
||||||
modules = self._getconftestmodules(path, importmode, rootpath=rootpath)
|
modules = self._getconftestmodules(path)
|
||||||
for mod in reversed(modules):
|
for mod in reversed(modules):
|
||||||
try:
|
try:
|
||||||
return mod, getattr(mod, name)
|
return mod, getattr(mod, name)
|
||||||
|
@ -953,7 +953,8 @@ class Config:
|
||||||
#: Command line arguments.
|
#: Command line arguments.
|
||||||
ARGS = enum.auto()
|
ARGS = enum.auto()
|
||||||
#: Invocation directory.
|
#: Invocation directory.
|
||||||
INCOVATION_DIR = enum.auto()
|
INVOCATION_DIR = enum.auto()
|
||||||
|
INCOVATION_DIR = INVOCATION_DIR # backwards compatibility alias
|
||||||
#: 'testpaths' configuration value.
|
#: 'testpaths' configuration value.
|
||||||
TESTPATHS = enum.auto()
|
TESTPATHS = enum.auto()
|
||||||
|
|
||||||
|
@ -1278,7 +1279,7 @@ class Config:
|
||||||
else:
|
else:
|
||||||
result = []
|
result = []
|
||||||
if not result:
|
if not result:
|
||||||
source = Config.ArgsSource.INCOVATION_DIR
|
source = Config.ArgsSource.INVOCATION_DIR
|
||||||
result = [str(invocation_dir)]
|
result = [str(invocation_dir)]
|
||||||
return result, source
|
return result, source
|
||||||
|
|
||||||
|
@ -1562,13 +1563,9 @@ class Config:
|
||||||
else:
|
else:
|
||||||
return self._getini_unknown_type(name, type, value)
|
return self._getini_unknown_type(name, type, value)
|
||||||
|
|
||||||
def _getconftest_pathlist(
|
def _getconftest_pathlist(self, name: str, path: Path) -> Optional[List[Path]]:
|
||||||
self, name: str, path: Path, rootpath: Path
|
|
||||||
) -> Optional[List[Path]]:
|
|
||||||
try:
|
try:
|
||||||
mod, relroots = self.pluginmanager._rget_with_confmod(
|
mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
|
||||||
name, path, self.getoption("importmode"), rootpath
|
|
||||||
)
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
assert mod.__file__ is not None
|
assert mod.__file__ is not None
|
||||||
|
|
|
@ -32,7 +32,7 @@ from _pytest.compat import safe_getattr
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
from _pytest.config.argparsing import Parser
|
from _pytest.config.argparsing import Parser
|
||||||
from _pytest.fixtures import fixture
|
from _pytest.fixtures import fixture
|
||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import TopRequest
|
||||||
from _pytest.nodes import Collector
|
from _pytest.nodes import Collector
|
||||||
from _pytest.nodes import Item
|
from _pytest.nodes import Item
|
||||||
from _pytest.outcomes import OutcomeException
|
from _pytest.outcomes import OutcomeException
|
||||||
|
@ -261,7 +261,7 @@ class DoctestItem(Item):
|
||||||
self.runner = runner
|
self.runner = runner
|
||||||
self.dtest = dtest
|
self.dtest = dtest
|
||||||
self.obj = None
|
self.obj = None
|
||||||
self.fixture_request: Optional[FixtureRequest] = None
|
self.fixture_request: Optional[TopRequest] = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_parent( # type: ignore
|
def from_parent( # type: ignore
|
||||||
|
@ -571,7 +571,7 @@ class DoctestModule(Module):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest:
|
def _setup_fixtures(doctest_item: DoctestItem) -> TopRequest:
|
||||||
"""Used by DoctestTextfile and DoctestItem to setup fixture information."""
|
"""Used by DoctestTextfile and DoctestItem to setup fixture information."""
|
||||||
|
|
||||||
def func() -> None:
|
def func() -> None:
|
||||||
|
@ -582,7 +582,7 @@ def _setup_fixtures(doctest_item: DoctestItem) -> FixtureRequest:
|
||||||
doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined]
|
doctest_item._fixtureinfo = fm.getfixtureinfo( # type: ignore[attr-defined]
|
||||||
node=doctest_item, func=func, cls=None, funcargs=False
|
node=doctest_item, func=func, cls=None, funcargs=False
|
||||||
)
|
)
|
||||||
fixture_request = FixtureRequest(doctest_item, _ispytest=True) # type: ignore[arg-type]
|
fixture_request = TopRequest(doctest_item, _ispytest=True) # type: ignore[arg-type]
|
||||||
fixture_request._fillfixtures()
|
fixture_request._fillfixtures()
|
||||||
return fixture_request
|
return fixture_request
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import abc
|
||||||
import dataclasses
|
import dataclasses
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
|
@ -63,7 +64,6 @@ from _pytest.pathlib import bestrelpath
|
||||||
from _pytest.scope import _ScopeName
|
from _pytest.scope import _ScopeName
|
||||||
from _pytest.scope import HIGH_SCOPES
|
from _pytest.scope import HIGH_SCOPES
|
||||||
from _pytest.scope import Scope
|
from _pytest.scope import Scope
|
||||||
from _pytest.stash import StashKey
|
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -146,89 +146,6 @@ def get_scope_node(
|
||||||
assert_never(scope)
|
assert_never(scope)
|
||||||
|
|
||||||
|
|
||||||
# Used for storing artificial fixturedefs for direct parametrization.
|
|
||||||
name2pseudofixturedef_key = StashKey[Dict[str, "FixtureDef[Any]"]]()
|
|
||||||
|
|
||||||
|
|
||||||
def add_funcarg_pseudo_fixture_def(
|
|
||||||
collector: nodes.Collector, metafunc: "Metafunc", fixturemanager: "FixtureManager"
|
|
||||||
) -> None:
|
|
||||||
import _pytest.python
|
|
||||||
|
|
||||||
# This function will transform all collected calls to functions
|
|
||||||
# if they use direct funcargs (i.e. direct parametrization)
|
|
||||||
# because we want later test execution to be able to rely on
|
|
||||||
# an existing FixtureDef structure for all arguments.
|
|
||||||
# XXX we can probably avoid this algorithm if we modify CallSpec2
|
|
||||||
# to directly care for creating the fixturedefs within its methods.
|
|
||||||
if not metafunc._calls[0].funcargs:
|
|
||||||
# This function call does not have direct parametrization.
|
|
||||||
return
|
|
||||||
# Collect funcargs of all callspecs into a list of values.
|
|
||||||
arg2params: Dict[str, List[object]] = {}
|
|
||||||
arg2scope: Dict[str, Scope] = {}
|
|
||||||
for callspec in metafunc._calls:
|
|
||||||
for argname, argvalue in callspec.funcargs.items():
|
|
||||||
assert argname not in callspec.params
|
|
||||||
callspec.params[argname] = argvalue
|
|
||||||
arg2params_list = arg2params.setdefault(argname, [])
|
|
||||||
callspec.indices[argname] = len(arg2params_list)
|
|
||||||
arg2params_list.append(argvalue)
|
|
||||||
if argname not in arg2scope:
|
|
||||||
scope = callspec._arg2scope.get(argname, Scope.Function)
|
|
||||||
arg2scope[argname] = scope
|
|
||||||
callspec.funcargs.clear()
|
|
||||||
|
|
||||||
# Register artificial FixtureDef's so that later at test execution
|
|
||||||
# time we can rely on a proper FixtureDef to exist for fixture setup.
|
|
||||||
arg2fixturedefs = metafunc._arg2fixturedefs
|
|
||||||
for argname, valuelist in arg2params.items():
|
|
||||||
# If we have a scope that is higher than function, we need
|
|
||||||
# to make sure we only ever create an according fixturedef on
|
|
||||||
# a per-scope basis. We thus store and cache the fixturedef on the
|
|
||||||
# node related to the scope.
|
|
||||||
scope = arg2scope[argname]
|
|
||||||
node = None
|
|
||||||
if scope is not Scope.Function:
|
|
||||||
node = get_scope_node(collector, scope)
|
|
||||||
if node is None:
|
|
||||||
# If used class scope and there is no class, use module-level
|
|
||||||
# collector (for now).
|
|
||||||
if scope is Scope.Class:
|
|
||||||
assert isinstance(collector, _pytest.python.Module)
|
|
||||||
node = collector
|
|
||||||
# If used package scope and there is no package, use session
|
|
||||||
# (for now).
|
|
||||||
elif scope is Scope.Package:
|
|
||||||
node = collector.session
|
|
||||||
else:
|
|
||||||
assert False, f"Unhandled missing scope: {scope}"
|
|
||||||
if node is None:
|
|
||||||
name2pseudofixturedef = None
|
|
||||||
else:
|
|
||||||
default: Dict[str, FixtureDef[Any]] = {}
|
|
||||||
name2pseudofixturedef = node.stash.setdefault(
|
|
||||||
name2pseudofixturedef_key, default
|
|
||||||
)
|
|
||||||
if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
|
|
||||||
arg2fixturedefs[argname] = [name2pseudofixturedef[argname]]
|
|
||||||
else:
|
|
||||||
fixturedef = FixtureDef(
|
|
||||||
fixturemanager=fixturemanager,
|
|
||||||
baseid="",
|
|
||||||
argname=argname,
|
|
||||||
func=get_direct_param_fixture_func,
|
|
||||||
scope=arg2scope[argname],
|
|
||||||
params=valuelist,
|
|
||||||
unittest=False,
|
|
||||||
ids=None,
|
|
||||||
_ispytest=True,
|
|
||||||
)
|
|
||||||
arg2fixturedefs[argname] = [fixturedef]
|
|
||||||
if name2pseudofixturedef is not None:
|
|
||||||
name2pseudofixturedef[argname] = fixturedef
|
|
||||||
|
|
||||||
|
|
||||||
def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
|
def getfixturemarker(obj: object) -> Optional["FixtureFunctionMarker"]:
|
||||||
"""Return fixturemarker or None if it doesn't exist or raised
|
"""Return fixturemarker or None if it doesn't exist or raised
|
||||||
exceptions."""
|
exceptions."""
|
||||||
|
@ -367,10 +284,6 @@ def reorder_items_atscope(
|
||||||
return items_done
|
return items_done
|
||||||
|
|
||||||
|
|
||||||
def get_direct_param_fixture_func(request: "FixtureRequest") -> Any:
|
|
||||||
return request.param
|
|
||||||
|
|
||||||
|
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
class FuncFixtureInfo:
|
class FuncFixtureInfo:
|
||||||
"""Fixture-related information for a fixture-requesting item (e.g. test
|
"""Fixture-related information for a fixture-requesting item (e.g. test
|
||||||
|
@ -430,26 +343,32 @@ class FuncFixtureInfo:
|
||||||
self.names_closure[:] = sorted(closure, key=self.names_closure.index)
|
self.names_closure[:] = sorted(closure, key=self.names_closure.index)
|
||||||
|
|
||||||
|
|
||||||
class FixtureRequest:
|
class FixtureRequest(abc.ABC):
|
||||||
"""A request for a fixture from a test or fixture function.
|
"""The type of the ``request`` fixture.
|
||||||
|
|
||||||
A request object gives access to the requesting test context and has
|
A request object gives access to the requesting test context and has a
|
||||||
an optional ``param`` attribute in case the fixture is parametrized
|
``param`` attribute in case the fixture is parametrized.
|
||||||
indirectly.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None:
|
def __init__(
|
||||||
|
self,
|
||||||
|
pyfuncitem: "Function",
|
||||||
|
fixturename: Optional[str],
|
||||||
|
arg2fixturedefs: Dict[str, Sequence["FixtureDef[Any]"]],
|
||||||
|
arg2index: Dict[str, int],
|
||||||
|
fixture_defs: Dict[str, "FixtureDef[Any]"],
|
||||||
|
*,
|
||||||
|
_ispytest: bool = False,
|
||||||
|
) -> None:
|
||||||
check_ispytest(_ispytest)
|
check_ispytest(_ispytest)
|
||||||
#: Fixture for which this request is being performed.
|
#: Fixture for which this request is being performed.
|
||||||
self.fixturename: Optional[str] = None
|
self.fixturename: Final = fixturename
|
||||||
self._pyfuncitem = pyfuncitem
|
self._pyfuncitem: Final = pyfuncitem
|
||||||
self._fixturemanager = pyfuncitem.session._fixturemanager
|
|
||||||
self._scope = Scope.Function
|
|
||||||
# The FixtureDefs for each fixture name requested by this item.
|
# The FixtureDefs for each fixture name requested by this item.
|
||||||
# Starts from the statically-known fixturedefs resolved during
|
# Starts from the statically-known fixturedefs resolved during
|
||||||
# collection. Dynamically requested fixtures (using
|
# collection. Dynamically requested fixtures (using
|
||||||
# `request.getfixturevalue("foo")`) are added dynamically.
|
# `request.getfixturevalue("foo")`) are added dynamically.
|
||||||
self._arg2fixturedefs = pyfuncitem._fixtureinfo.name2fixturedefs.copy()
|
self._arg2fixturedefs: Final = arg2fixturedefs
|
||||||
# A fixture may override another fixture with the same name, e.g. a fixture
|
# A fixture may override another fixture with the same name, e.g. a fixture
|
||||||
# in a module can override a fixture in a conftest, a fixture in a class can
|
# in a module can override a fixture in a conftest, a fixture in a class can
|
||||||
# override a fixture in the module, and so on.
|
# override a fixture in the module, and so on.
|
||||||
|
@ -459,10 +378,10 @@ class FixtureRequest:
|
||||||
# The fixturedefs list in _arg2fixturedefs for a given name is ordered from
|
# The fixturedefs list in _arg2fixturedefs for a given name is ordered from
|
||||||
# furthest to closest, so we use negative indexing -1, -2, ... to go from
|
# furthest to closest, so we use negative indexing -1, -2, ... to go from
|
||||||
# last to first.
|
# last to first.
|
||||||
self._arg2index: Dict[str, int] = {}
|
self._arg2index: Final = arg2index
|
||||||
# The evaluated argnames so far, mapping to the FixtureDef they resolved
|
# The evaluated argnames so far, mapping to the FixtureDef they resolved
|
||||||
# to.
|
# to.
|
||||||
self._fixture_defs: Dict[str, FixtureDef[Any]] = {}
|
self._fixture_defs: Final = fixture_defs
|
||||||
# Notes on the type of `param`:
|
# Notes on the type of `param`:
|
||||||
# -`request.param` is only defined in parametrized fixtures, and will raise
|
# -`request.param` is only defined in parametrized fixtures, and will raise
|
||||||
# AttributeError otherwise. Python typing has no notion of "undefined", so
|
# AttributeError otherwise. Python typing has no notion of "undefined", so
|
||||||
|
@ -473,6 +392,15 @@ class FixtureRequest:
|
||||||
# for now just using Any.
|
# for now just using Any.
|
||||||
self.param: Any
|
self.param: Any
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _fixturemanager(self) -> "FixtureManager":
|
||||||
|
return self._pyfuncitem.session._fixturemanager
|
||||||
|
|
||||||
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _scope(self) -> Scope:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def scope(self) -> _ScopeName:
|
def scope(self) -> _ScopeName:
|
||||||
"""Scope string, one of "function", "class", "module", "package", "session"."""
|
"""Scope string, one of "function", "class", "module", "package", "session"."""
|
||||||
|
@ -486,25 +414,10 @@ class FixtureRequest:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@abc.abstractmethod
|
||||||
def node(self):
|
def node(self):
|
||||||
"""Underlying collection node (depends on current request scope)."""
|
"""Underlying collection node (depends on current request scope)."""
|
||||||
scope = self._scope
|
raise NotImplementedError()
|
||||||
if scope is Scope.Function:
|
|
||||||
# This might also be a non-function Item despite its attribute name.
|
|
||||||
node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem
|
|
||||||
elif scope is Scope.Package:
|
|
||||||
# FIXME: _fixturedef is not defined on FixtureRequest (this class),
|
|
||||||
# but on FixtureRequest (a subclass).
|
|
||||||
node = get_scope_package(self._pyfuncitem, self._fixturedef) # type: ignore[attr-defined]
|
|
||||||
else:
|
|
||||||
node = get_scope_node(self._pyfuncitem, scope)
|
|
||||||
if node is None and scope is Scope.Class:
|
|
||||||
# Fallback to function item itself.
|
|
||||||
node = self._pyfuncitem
|
|
||||||
assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format(
|
|
||||||
scope, self._pyfuncitem
|
|
||||||
)
|
|
||||||
return node
|
|
||||||
|
|
||||||
def _getnextfixturedef(self, argname: str) -> "FixtureDef[Any]":
|
def _getnextfixturedef(self, argname: str) -> "FixtureDef[Any]":
|
||||||
fixturedefs = self._arg2fixturedefs.get(argname, None)
|
fixturedefs = self._arg2fixturedefs.get(argname, None)
|
||||||
|
@ -590,11 +503,11 @@ class FixtureRequest:
|
||||||
"""Pytest session object."""
|
"""Pytest session object."""
|
||||||
return self._pyfuncitem.session # type: ignore[no-any-return]
|
return self._pyfuncitem.session # type: ignore[no-any-return]
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
def addfinalizer(self, finalizer: Callable[[], object]) -> None:
|
def addfinalizer(self, finalizer: Callable[[], object]) -> None:
|
||||||
"""Add finalizer/teardown function to be called without arguments after
|
"""Add finalizer/teardown function to be called without arguments after
|
||||||
the last test within the requesting test context finished execution."""
|
the last test within the requesting test context finished execution."""
|
||||||
# XXX usually this method is shadowed by fixturedef specific ones.
|
raise NotImplementedError()
|
||||||
self.node.addfinalizer(finalizer)
|
|
||||||
|
|
||||||
def applymarker(self, marker: Union[str, MarkDecorator]) -> None:
|
def applymarker(self, marker: Union[str, MarkDecorator]) -> None:
|
||||||
"""Apply a marker to a single test function invocation.
|
"""Apply a marker to a single test function invocation.
|
||||||
|
@ -615,13 +528,6 @@ class FixtureRequest:
|
||||||
"""
|
"""
|
||||||
raise self._fixturemanager.FixtureLookupError(None, self, msg)
|
raise self._fixturemanager.FixtureLookupError(None, self, msg)
|
||||||
|
|
||||||
def _fillfixtures(self) -> None:
|
|
||||||
item = self._pyfuncitem
|
|
||||||
fixturenames = getattr(item, "fixturenames", self.fixturenames)
|
|
||||||
for argname in fixturenames:
|
|
||||||
if argname not in item.funcargs:
|
|
||||||
item.funcargs[argname] = self.getfixturevalue(argname)
|
|
||||||
|
|
||||||
def getfixturevalue(self, argname: str) -> Any:
|
def getfixturevalue(self, argname: str) -> Any:
|
||||||
"""Dynamically run a named fixture function.
|
"""Dynamically run a named fixture function.
|
||||||
|
|
||||||
|
@ -755,6 +661,98 @@ class FixtureRequest:
|
||||||
finalizer = functools.partial(fixturedef.finish, request=subrequest)
|
finalizer = functools.partial(fixturedef.finish, request=subrequest)
|
||||||
subrequest.node.addfinalizer(finalizer)
|
subrequest.node.addfinalizer(finalizer)
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class TopRequest(FixtureRequest):
|
||||||
|
"""The type of the ``request`` fixture in a test function."""
|
||||||
|
|
||||||
|
def __init__(self, pyfuncitem: "Function", *, _ispytest: bool = False) -> None:
|
||||||
|
super().__init__(
|
||||||
|
fixturename=None,
|
||||||
|
pyfuncitem=pyfuncitem,
|
||||||
|
arg2fixturedefs=pyfuncitem._fixtureinfo.name2fixturedefs.copy(),
|
||||||
|
arg2index={},
|
||||||
|
fixture_defs={},
|
||||||
|
_ispytest=_ispytest,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _scope(self) -> Scope:
|
||||||
|
return Scope.Function
|
||||||
|
|
||||||
|
@property
|
||||||
|
def node(self):
|
||||||
|
return self._pyfuncitem
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return "<FixtureRequest for %r>" % (self.node)
|
||||||
|
|
||||||
|
def _fillfixtures(self) -> None:
|
||||||
|
item = self._pyfuncitem
|
||||||
|
fixturenames = getattr(item, "fixturenames", self.fixturenames)
|
||||||
|
for argname in fixturenames:
|
||||||
|
if argname not in item.funcargs:
|
||||||
|
item.funcargs[argname] = self.getfixturevalue(argname)
|
||||||
|
|
||||||
|
def addfinalizer(self, finalizer: Callable[[], object]) -> None:
|
||||||
|
self.node.addfinalizer(finalizer)
|
||||||
|
|
||||||
|
|
||||||
|
@final
|
||||||
|
class SubRequest(FixtureRequest):
|
||||||
|
"""The type of the ``request`` fixture in a fixture function requested
|
||||||
|
(transitively) by a test function."""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
request: FixtureRequest,
|
||||||
|
scope: Scope,
|
||||||
|
param: Any,
|
||||||
|
param_index: int,
|
||||||
|
fixturedef: "FixtureDef[object]",
|
||||||
|
*,
|
||||||
|
_ispytest: bool = False,
|
||||||
|
) -> None:
|
||||||
|
super().__init__(
|
||||||
|
pyfuncitem=request._pyfuncitem,
|
||||||
|
fixturename=fixturedef.argname,
|
||||||
|
fixture_defs=request._fixture_defs,
|
||||||
|
arg2fixturedefs=request._arg2fixturedefs,
|
||||||
|
arg2index=request._arg2index,
|
||||||
|
_ispytest=_ispytest,
|
||||||
|
)
|
||||||
|
self._parent_request: Final[FixtureRequest] = request
|
||||||
|
self._scope_field: Final = scope
|
||||||
|
self._fixturedef: Final = fixturedef
|
||||||
|
if param is not NOTSET:
|
||||||
|
self.param = param
|
||||||
|
self.param_index: Final = param_index
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"<SubRequest {self.fixturename!r} for {self._pyfuncitem!r}>"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _scope(self) -> Scope:
|
||||||
|
return self._scope_field
|
||||||
|
|
||||||
|
@property
|
||||||
|
def node(self):
|
||||||
|
scope = self._scope
|
||||||
|
if scope is Scope.Function:
|
||||||
|
# This might also be a non-function Item despite its attribute name.
|
||||||
|
node: Optional[Union[nodes.Item, nodes.Collector]] = self._pyfuncitem
|
||||||
|
elif scope is Scope.Package:
|
||||||
|
node = get_scope_package(self._pyfuncitem, self._fixturedef)
|
||||||
|
else:
|
||||||
|
node = get_scope_node(self._pyfuncitem, scope)
|
||||||
|
if node is None and scope is Scope.Class:
|
||||||
|
# Fallback to function item itself.
|
||||||
|
node = self._pyfuncitem
|
||||||
|
assert node, 'Could not obtain a node for scope "{}" for function {!r}'.format(
|
||||||
|
scope, self._pyfuncitem
|
||||||
|
)
|
||||||
|
return node
|
||||||
|
|
||||||
def _check_scope(
|
def _check_scope(
|
||||||
self,
|
self,
|
||||||
argname: str,
|
argname: str,
|
||||||
|
@ -789,44 +787,7 @@ class FixtureRequest:
|
||||||
)
|
)
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return "<FixtureRequest for %r>" % (self.node)
|
|
||||||
|
|
||||||
|
|
||||||
@final
|
|
||||||
class SubRequest(FixtureRequest):
|
|
||||||
"""A sub request for handling getting a fixture from a test function/fixture."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
request: "FixtureRequest",
|
|
||||||
scope: Scope,
|
|
||||||
param: Any,
|
|
||||||
param_index: int,
|
|
||||||
fixturedef: "FixtureDef[object]",
|
|
||||||
*,
|
|
||||||
_ispytest: bool = False,
|
|
||||||
) -> None:
|
|
||||||
check_ispytest(_ispytest)
|
|
||||||
self._parent_request = request
|
|
||||||
self.fixturename = fixturedef.argname
|
|
||||||
if param is not NOTSET:
|
|
||||||
self.param = param
|
|
||||||
self.param_index = param_index
|
|
||||||
self._scope = scope
|
|
||||||
self._fixturedef = fixturedef
|
|
||||||
self._pyfuncitem = request._pyfuncitem
|
|
||||||
self._fixture_defs = request._fixture_defs
|
|
||||||
self._arg2fixturedefs = request._arg2fixturedefs
|
|
||||||
self._arg2index = request._arg2index
|
|
||||||
self._fixturemanager = request._fixturemanager
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"<SubRequest {self.fixturename!r} for {self._pyfuncitem!r}>"
|
|
||||||
|
|
||||||
def addfinalizer(self, finalizer: Callable[[], object]) -> None:
|
def addfinalizer(self, finalizer: Callable[[], object]) -> None:
|
||||||
"""Add finalizer/teardown function to be called without arguments after
|
|
||||||
the last test within the requesting test context finished execution."""
|
|
||||||
self._fixturedef.addfinalizer(finalizer)
|
self._fixturedef.addfinalizer(finalizer)
|
||||||
|
|
||||||
def _schedule_finalizers(
|
def _schedule_finalizers(
|
||||||
|
|
|
@ -376,7 +376,7 @@ def _in_venv(path: Path) -> bool:
|
||||||
|
|
||||||
def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]:
|
def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[bool]:
|
||||||
ignore_paths = config._getconftest_pathlist(
|
ignore_paths = config._getconftest_pathlist(
|
||||||
"collect_ignore", path=collection_path.parent, rootpath=config.rootpath
|
"collect_ignore", path=collection_path.parent
|
||||||
)
|
)
|
||||||
ignore_paths = ignore_paths or []
|
ignore_paths = ignore_paths or []
|
||||||
excludeopt = config.getoption("ignore")
|
excludeopt = config.getoption("ignore")
|
||||||
|
@ -387,7 +387,7 @@ def pytest_ignore_collect(collection_path: Path, config: Config) -> Optional[boo
|
||||||
return True
|
return True
|
||||||
|
|
||||||
ignore_globs = config._getconftest_pathlist(
|
ignore_globs = config._getconftest_pathlist(
|
||||||
"collect_ignore_glob", path=collection_path.parent, rootpath=config.rootpath
|
"collect_ignore_glob", path=collection_path.parent
|
||||||
)
|
)
|
||||||
ignore_globs = ignore_globs or []
|
ignore_globs = ignore_globs or []
|
||||||
excludeglobopt = config.getoption("ignore_glob")
|
excludeglobopt = config.getoption("ignore_glob")
|
||||||
|
@ -551,11 +551,16 @@ class Session(nodes.FSCollector):
|
||||||
pm = self.config.pluginmanager
|
pm = self.config.pluginmanager
|
||||||
# Check if we have the common case of running
|
# Check if we have the common case of running
|
||||||
# hooks with all conftest.py files.
|
# hooks with all conftest.py files.
|
||||||
my_conftestmodules = pm._getconftestmodules(
|
#
|
||||||
|
# TODO: pytest relies on this call to load non-initial conftests. This
|
||||||
|
# is incidental. It will be better to load conftests at a more
|
||||||
|
# well-defined place.
|
||||||
|
pm._loadconftestmodules(
|
||||||
path,
|
path,
|
||||||
self.config.getoption("importmode"),
|
self.config.getoption("importmode"),
|
||||||
rootpath=self.config.rootpath,
|
rootpath=self.config.rootpath,
|
||||||
)
|
)
|
||||||
|
my_conftestmodules = pm._getconftestmodules(path)
|
||||||
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
|
remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
|
||||||
if remove_mods:
|
if remove_mods:
|
||||||
# One or more conftests are not in use at this fspath.
|
# One or more conftests are not in use at this fspath.
|
||||||
|
|
|
@ -461,7 +461,9 @@ if TYPE_CHECKING:
|
||||||
*conditions: Union[str, bool],
|
*conditions: Union[str, bool],
|
||||||
reason: str = ...,
|
reason: str = ...,
|
||||||
run: bool = ...,
|
run: bool = ...,
|
||||||
raises: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = ...,
|
raises: Union[
|
||||||
|
None, Type[BaseException], Tuple[Type[BaseException], ...]
|
||||||
|
] = ...,
|
||||||
strict: bool = ...,
|
strict: bool = ...,
|
||||||
) -> MarkDecorator:
|
) -> MarkDecorator:
|
||||||
...
|
...
|
||||||
|
|
|
@ -233,6 +233,9 @@ def xfail(reason: str = "") -> NoReturn:
|
||||||
|
|
||||||
This function should be called only during testing (setup, call or teardown).
|
This function should be called only during testing (setup, call or teardown).
|
||||||
|
|
||||||
|
No other code is executed after using ``xfail()`` (it is implemented
|
||||||
|
internally by raising an exception).
|
||||||
|
|
||||||
:param reason:
|
:param reason:
|
||||||
The message to show the user as reason for the xfail.
|
The message to show the user as reason for the xfail.
|
||||||
|
|
||||||
|
|
|
@ -829,7 +829,7 @@ class Pytester:
|
||||||
return self._makefile(ext, args, kwargs)
|
return self._makefile(ext, args, kwargs)
|
||||||
|
|
||||||
def makeconftest(self, source: str) -> Path:
|
def makeconftest(self, source: str) -> Path:
|
||||||
"""Write a contest.py file.
|
"""Write a conftest.py file.
|
||||||
|
|
||||||
:param source: The contents.
|
:param source: The contents.
|
||||||
:returns: The conftest.py file.
|
:returns: The conftest.py file.
|
||||||
|
|
|
@ -40,7 +40,6 @@ from _pytest._code.code import Traceback
|
||||||
from _pytest._io import TerminalWriter
|
from _pytest._io import TerminalWriter
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.compat import ascii_escaped
|
from _pytest.compat import ascii_escaped
|
||||||
from _pytest.compat import assert_never
|
|
||||||
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 getimfunc
|
from _pytest.compat import getimfunc
|
||||||
|
@ -59,7 +58,10 @@ from _pytest.config.argparsing import Parser
|
||||||
from _pytest.deprecated import check_ispytest
|
from _pytest.deprecated import check_ispytest
|
||||||
from _pytest.deprecated import INSTANCE_COLLECTOR
|
from _pytest.deprecated import INSTANCE_COLLECTOR
|
||||||
from _pytest.deprecated import NOSE_SUPPORT_METHOD
|
from _pytest.deprecated import NOSE_SUPPORT_METHOD
|
||||||
|
from _pytest.fixtures import FixtureDef
|
||||||
|
from _pytest.fixtures import FixtureRequest
|
||||||
from _pytest.fixtures import FuncFixtureInfo
|
from _pytest.fixtures import FuncFixtureInfo
|
||||||
|
from _pytest.fixtures import get_scope_node
|
||||||
from _pytest.main import Session
|
from _pytest.main import Session
|
||||||
from _pytest.mark import MARK_GEN
|
from _pytest.mark import MARK_GEN
|
||||||
from _pytest.mark import ParameterSet
|
from _pytest.mark import ParameterSet
|
||||||
|
@ -77,6 +79,7 @@ from _pytest.pathlib import parts
|
||||||
from _pytest.pathlib import visit
|
from _pytest.pathlib import visit
|
||||||
from _pytest.scope import _ScopeName
|
from _pytest.scope import _ScopeName
|
||||||
from _pytest.scope import Scope
|
from _pytest.scope import Scope
|
||||||
|
from _pytest.stash import StashKey
|
||||||
from _pytest.warning_types import PytestCollectionWarning
|
from _pytest.warning_types import PytestCollectionWarning
|
||||||
from _pytest.warning_types import PytestReturnNotNoneWarning
|
from _pytest.warning_types import PytestReturnNotNoneWarning
|
||||||
from _pytest.warning_types import PytestUnhandledCoroutineWarning
|
from _pytest.warning_types import PytestUnhandledCoroutineWarning
|
||||||
|
@ -493,13 +496,11 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
||||||
if not metafunc._calls:
|
if not metafunc._calls:
|
||||||
yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
|
yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
|
||||||
else:
|
else:
|
||||||
# Add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs.
|
# Direct parametrizations taking place in module/class-specific
|
||||||
fm = self.session._fixturemanager
|
# `metafunc.parametrize` calls may have shadowed some fixtures, so make sure
|
||||||
fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
|
# we update what the function really needs a.k.a its fixture closure. Note that
|
||||||
|
# direct parametrizations using `@pytest.mark.parametrize` have already been considered
|
||||||
# Add_funcarg_pseudo_fixture_def may have shadowed some fixtures
|
# into making the closure using `ignore_args` arg to `getfixtureclosure`.
|
||||||
# with direct parametrization, so make sure we update what the
|
|
||||||
# function really needs.
|
|
||||||
fixtureinfo.prune_dependency_tree()
|
fixtureinfo.prune_dependency_tree()
|
||||||
|
|
||||||
for callspec in metafunc._calls:
|
for callspec in metafunc._calls:
|
||||||
|
@ -1116,11 +1117,8 @@ class CallSpec2:
|
||||||
and stored in item.callspec.
|
and stored in item.callspec.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# arg name -> arg value which will be passed to the parametrized test
|
# arg name -> arg value which will be passed to a fixture or pseudo-fixture
|
||||||
# function (direct parameterization).
|
# of the same name. (indirect or direct parametrization respectively)
|
||||||
funcargs: Dict[str, object] = dataclasses.field(default_factory=dict)
|
|
||||||
# arg name -> arg value which will be passed to a fixture of the same name
|
|
||||||
# (indirect parametrization).
|
|
||||||
params: Dict[str, object] = dataclasses.field(default_factory=dict)
|
params: Dict[str, object] = dataclasses.field(default_factory=dict)
|
||||||
# arg name -> arg index.
|
# arg name -> arg index.
|
||||||
indices: Dict[str, int] = dataclasses.field(default_factory=dict)
|
indices: Dict[str, int] = dataclasses.field(default_factory=dict)
|
||||||
|
@ -1134,7 +1132,6 @@ class CallSpec2:
|
||||||
def setmulti(
|
def setmulti(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
valtypes: Mapping[str, "Literal['params', 'funcargs']"],
|
|
||||||
argnames: Iterable[str],
|
argnames: Iterable[str],
|
||||||
valset: Iterable[object],
|
valset: Iterable[object],
|
||||||
id: str,
|
id: str,
|
||||||
|
@ -1142,24 +1139,16 @@ class CallSpec2:
|
||||||
scope: Scope,
|
scope: Scope,
|
||||||
param_index: int,
|
param_index: int,
|
||||||
) -> "CallSpec2":
|
) -> "CallSpec2":
|
||||||
funcargs = self.funcargs.copy()
|
|
||||||
params = self.params.copy()
|
params = self.params.copy()
|
||||||
indices = self.indices.copy()
|
indices = self.indices.copy()
|
||||||
arg2scope = self._arg2scope.copy()
|
arg2scope = self._arg2scope.copy()
|
||||||
for arg, val in zip(argnames, valset):
|
for arg, val in zip(argnames, valset):
|
||||||
if arg in params or arg in funcargs:
|
if arg in params:
|
||||||
raise ValueError(f"duplicate {arg!r}")
|
raise ValueError(f"duplicate parametrization of {arg!r}")
|
||||||
valtype_for_arg = valtypes[arg]
|
|
||||||
if valtype_for_arg == "params":
|
|
||||||
params[arg] = val
|
params[arg] = val
|
||||||
elif valtype_for_arg == "funcargs":
|
|
||||||
funcargs[arg] = val
|
|
||||||
else:
|
|
||||||
assert_never(valtype_for_arg)
|
|
||||||
indices[arg] = param_index
|
indices[arg] = param_index
|
||||||
arg2scope[arg] = scope
|
arg2scope[arg] = scope
|
||||||
return CallSpec2(
|
return CallSpec2(
|
||||||
funcargs=funcargs,
|
|
||||||
params=params,
|
params=params,
|
||||||
indices=indices,
|
indices=indices,
|
||||||
_arg2scope=arg2scope,
|
_arg2scope=arg2scope,
|
||||||
|
@ -1178,6 +1167,14 @@ class CallSpec2:
|
||||||
return "-".join(self._idlist)
|
return "-".join(self._idlist)
|
||||||
|
|
||||||
|
|
||||||
|
def get_direct_param_fixture_func(request: FixtureRequest) -> Any:
|
||||||
|
return request.param
|
||||||
|
|
||||||
|
|
||||||
|
# Used for storing pseudo fixturedefs for direct parametrization.
|
||||||
|
name2pseudofixturedef_key = StashKey[Dict[str, FixtureDef[Any]]]()
|
||||||
|
|
||||||
|
|
||||||
@final
|
@final
|
||||||
class Metafunc:
|
class Metafunc:
|
||||||
"""Objects passed to the :hook:`pytest_generate_tests` hook.
|
"""Objects passed to the :hook:`pytest_generate_tests` hook.
|
||||||
|
@ -1239,8 +1236,9 @@ class Metafunc:
|
||||||
during the collection phase. If you need to setup expensive resources
|
during the collection phase. If you need to setup expensive resources
|
||||||
see about setting indirect to do it rather than at test setup time.
|
see about setting indirect to do it rather than at test setup time.
|
||||||
|
|
||||||
Can be called multiple times, in which case each call parametrizes all
|
Can be called multiple times per test function (but only on different
|
||||||
previous parametrizations, e.g.
|
argument names), in which case each call parametrizes all previous
|
||||||
|
parametrizations, e.g.
|
||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
|
@ -1319,8 +1317,6 @@ class Metafunc:
|
||||||
|
|
||||||
self._validate_if_using_arg_names(argnames, indirect)
|
self._validate_if_using_arg_names(argnames, indirect)
|
||||||
|
|
||||||
arg_values_types = self._resolve_arg_value_types(argnames, indirect)
|
|
||||||
|
|
||||||
# Use any already (possibly) generated ids with parametrize Marks.
|
# Use any already (possibly) generated ids with parametrize Marks.
|
||||||
if _param_mark and _param_mark._param_ids_from:
|
if _param_mark and _param_mark._param_ids_from:
|
||||||
generated_ids = _param_mark._param_ids_from._param_ids_generated
|
generated_ids = _param_mark._param_ids_from._param_ids_generated
|
||||||
|
@ -1335,6 +1331,60 @@ class Metafunc:
|
||||||
if _param_mark and _param_mark._param_ids_from and generated_ids is None:
|
if _param_mark and _param_mark._param_ids_from and generated_ids is None:
|
||||||
object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids)
|
object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids)
|
||||||
|
|
||||||
|
# Add funcargs as fixturedefs to fixtureinfo.arg2fixturedefs by registering
|
||||||
|
# artificial "pseudo" FixtureDef's so that later at test execution time we can
|
||||||
|
# rely on a proper FixtureDef to exist for fixture setup.
|
||||||
|
arg2fixturedefs = self._arg2fixturedefs
|
||||||
|
node = None
|
||||||
|
# If we have a scope that is higher than function, we need
|
||||||
|
# to make sure we only ever create an according fixturedef on
|
||||||
|
# a per-scope basis. We thus store and cache the fixturedef on the
|
||||||
|
# node related to the scope.
|
||||||
|
if scope_ is not Scope.Function:
|
||||||
|
collector = self.definition.parent
|
||||||
|
assert collector is not None
|
||||||
|
node = get_scope_node(collector, scope_)
|
||||||
|
if node is None:
|
||||||
|
# If used class scope and there is no class, use module-level
|
||||||
|
# collector (for now).
|
||||||
|
if scope_ is Scope.Class:
|
||||||
|
assert isinstance(collector, _pytest.python.Module)
|
||||||
|
node = collector
|
||||||
|
# If used package scope and there is no package, use session
|
||||||
|
# (for now).
|
||||||
|
elif scope_ is Scope.Package:
|
||||||
|
node = collector.session
|
||||||
|
else:
|
||||||
|
assert False, f"Unhandled missing scope: {scope}"
|
||||||
|
if node is None:
|
||||||
|
name2pseudofixturedef = None
|
||||||
|
else:
|
||||||
|
default: Dict[str, FixtureDef[Any]] = {}
|
||||||
|
name2pseudofixturedef = node.stash.setdefault(
|
||||||
|
name2pseudofixturedef_key, default
|
||||||
|
)
|
||||||
|
arg_directness = self._resolve_args_directness(argnames, indirect)
|
||||||
|
for argname in argnames:
|
||||||
|
if arg_directness[argname] == "indirect":
|
||||||
|
continue
|
||||||
|
if name2pseudofixturedef is not None and argname in name2pseudofixturedef:
|
||||||
|
fixturedef = name2pseudofixturedef[argname]
|
||||||
|
else:
|
||||||
|
fixturedef = FixtureDef(
|
||||||
|
fixturemanager=self.definition.session._fixturemanager,
|
||||||
|
baseid="",
|
||||||
|
argname=argname,
|
||||||
|
func=get_direct_param_fixture_func,
|
||||||
|
scope=scope_,
|
||||||
|
params=None,
|
||||||
|
unittest=False,
|
||||||
|
ids=None,
|
||||||
|
_ispytest=True,
|
||||||
|
)
|
||||||
|
if name2pseudofixturedef is not None:
|
||||||
|
name2pseudofixturedef[argname] = fixturedef
|
||||||
|
arg2fixturedefs[argname] = [fixturedef]
|
||||||
|
|
||||||
# Create the new calls: if we are parametrize() multiple times (by applying the decorator
|
# Create the new calls: if we are parametrize() multiple times (by applying the decorator
|
||||||
# more than once) then we accumulate those calls generating the cartesian product
|
# more than once) then we accumulate those calls generating the cartesian product
|
||||||
# of all calls.
|
# of all calls.
|
||||||
|
@ -1344,7 +1394,6 @@ class Metafunc:
|
||||||
zip(ids, parametersets)
|
zip(ids, parametersets)
|
||||||
):
|
):
|
||||||
newcallspec = callspec.setmulti(
|
newcallspec = callspec.setmulti(
|
||||||
valtypes=arg_values_types,
|
|
||||||
argnames=argnames,
|
argnames=argnames,
|
||||||
valset=param_set.values,
|
valset=param_set.values,
|
||||||
id=param_id,
|
id=param_id,
|
||||||
|
@ -1421,28 +1470,30 @@ class Metafunc:
|
||||||
|
|
||||||
return list(itertools.islice(ids, num_ids))
|
return list(itertools.islice(ids, num_ids))
|
||||||
|
|
||||||
def _resolve_arg_value_types(
|
def _resolve_args_directness(
|
||||||
self,
|
self,
|
||||||
argnames: Sequence[str],
|
argnames: Sequence[str],
|
||||||
indirect: Union[bool, Sequence[str]],
|
indirect: Union[bool, Sequence[str]],
|
||||||
) -> Dict[str, "Literal['params', 'funcargs']"]:
|
) -> Dict[str, Literal["indirect", "direct"]]:
|
||||||
"""Resolve if each parametrized argument must be considered a
|
"""Resolve if each parametrized argument must be considered an indirect
|
||||||
parameter to a fixture or a "funcarg" to the function, based on the
|
parameter to a fixture of the same name, or a direct parameter to the
|
||||||
``indirect`` parameter of the parametrized() call.
|
parametrized function, based on the ``indirect`` parameter of the
|
||||||
|
parametrized() call.
|
||||||
|
|
||||||
:param List[str] argnames: List of argument names passed to ``parametrize()``.
|
:param argnames:
|
||||||
:param indirect: Same as the ``indirect`` parameter of ``parametrize()``.
|
List of argument names passed to ``parametrize()``.
|
||||||
:rtype: Dict[str, str]
|
:param indirect:
|
||||||
A dict mapping each arg name to either:
|
Same as the ``indirect`` parameter of ``parametrize()``.
|
||||||
* "params" if the argname should be the parameter of a fixture of the same name.
|
:returns
|
||||||
* "funcargs" if the argname should be a parameter to the parametrized test function.
|
A dict mapping each arg name to either "indirect" or "direct".
|
||||||
"""
|
"""
|
||||||
|
arg_directness: Dict[str, Literal["indirect", "direct"]]
|
||||||
if isinstance(indirect, bool):
|
if isinstance(indirect, bool):
|
||||||
valtypes: Dict[str, Literal["params", "funcargs"]] = dict.fromkeys(
|
arg_directness = dict.fromkeys(
|
||||||
argnames, "params" if indirect else "funcargs"
|
argnames, "indirect" if indirect else "direct"
|
||||||
)
|
)
|
||||||
elif isinstance(indirect, Sequence):
|
elif isinstance(indirect, Sequence):
|
||||||
valtypes = dict.fromkeys(argnames, "funcargs")
|
arg_directness = dict.fromkeys(argnames, "direct")
|
||||||
for arg in indirect:
|
for arg in indirect:
|
||||||
if arg not in argnames:
|
if arg not in argnames:
|
||||||
fail(
|
fail(
|
||||||
|
@ -1451,7 +1502,7 @@ class Metafunc:
|
||||||
),
|
),
|
||||||
pytrace=False,
|
pytrace=False,
|
||||||
)
|
)
|
||||||
valtypes[arg] = "params"
|
arg_directness[arg] = "indirect"
|
||||||
else:
|
else:
|
||||||
fail(
|
fail(
|
||||||
"In {func}: expected Sequence or boolean for indirect, got {type}".format(
|
"In {func}: expected Sequence or boolean for indirect, got {type}".format(
|
||||||
|
@ -1459,7 +1510,7 @@ class Metafunc:
|
||||||
),
|
),
|
||||||
pytrace=False,
|
pytrace=False,
|
||||||
)
|
)
|
||||||
return valtypes
|
return arg_directness
|
||||||
|
|
||||||
def _validate_if_using_arg_names(
|
def _validate_if_using_arg_names(
|
||||||
self,
|
self,
|
||||||
|
@ -1516,7 +1567,7 @@ def _find_parametrized_scope(
|
||||||
if all_arguments_are_fixtures:
|
if all_arguments_are_fixtures:
|
||||||
fixturedefs = arg2fixturedefs or {}
|
fixturedefs = arg2fixturedefs or {}
|
||||||
used_scopes = [
|
used_scopes = [
|
||||||
fixturedef[0]._scope
|
fixturedef[-1]._scope
|
||||||
for name, fixturedef in fixturedefs.items()
|
for name, fixturedef in fixturedefs.items()
|
||||||
if name in argnames
|
if name in argnames
|
||||||
]
|
]
|
||||||
|
@ -1682,7 +1733,7 @@ class Function(PyobjMixin, nodes.Item):
|
||||||
:param config:
|
:param config:
|
||||||
The pytest Config object.
|
The pytest Config object.
|
||||||
:param callspec:
|
:param callspec:
|
||||||
If given, this is function has been parametrized and the callspec contains
|
If given, this function has been parametrized and the callspec contains
|
||||||
meta information about the parametrization.
|
meta information about the parametrization.
|
||||||
:param callobj:
|
:param callobj:
|
||||||
If given, the object which will be called when the Function is invoked,
|
If given, the object which will be called when the Function is invoked,
|
||||||
|
@ -1761,7 +1812,7 @@ class Function(PyobjMixin, nodes.Item):
|
||||||
|
|
||||||
def _initrequest(self) -> None:
|
def _initrequest(self) -> None:
|
||||||
self.funcargs: Dict[str, object] = {}
|
self.funcargs: Dict[str, object] = {}
|
||||||
self._request = fixtures.FixtureRequest(self, _ispytest=True)
|
self._request = fixtures.TopRequest(self, _ispytest=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def function(self):
|
def function(self):
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# PYTHON_ARGCOMPLETE_OK
|
# PYTHON_ARGCOMPLETE_OK
|
||||||
"""pytest: unit and functional testing with Python."""
|
"""pytest: unit and functional testing with Python."""
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from _pytest import __version__
|
from _pytest import __version__
|
||||||
from _pytest import version_tuple
|
from _pytest import version_tuple
|
||||||
from _pytest._code import ExceptionInfo
|
from _pytest._code import ExceptionInfo
|
||||||
|
@ -165,6 +167,7 @@ __all__ = [
|
||||||
"yield_fixture",
|
"yield_fixture",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if not TYPE_CHECKING:
|
||||||
|
|
||||||
def __getattr__(name: str) -> object:
|
def __getattr__(name: str) -> object:
|
||||||
if name == "Instance":
|
if name == "Instance":
|
||||||
|
|
|
@ -272,7 +272,7 @@ def test_importing_instance_is_deprecated(pytester: Pytester) -> None:
|
||||||
pytest.PytestDeprecationWarning,
|
pytest.PytestDeprecationWarning,
|
||||||
match=re.escape("The pytest.Instance collector type is deprecated"),
|
match=re.escape("The pytest.Instance collector type is deprecated"),
|
||||||
):
|
):
|
||||||
pytest.Instance
|
pytest.Instance # type:ignore[attr-defined]
|
||||||
|
|
||||||
with pytest.warns(
|
with pytest.warns(
|
||||||
pytest.PytestDeprecationWarning,
|
pytest.PytestDeprecationWarning,
|
||||||
|
|
|
@ -22,13 +22,13 @@ def checked_order():
|
||||||
assert order == [
|
assert order == [
|
||||||
("issue_519.py", "fix1", "arg1v1"),
|
("issue_519.py", "fix1", "arg1v1"),
|
||||||
("test_one[arg1v1-arg2v1]", "fix2", "arg2v1"),
|
("test_one[arg1v1-arg2v1]", "fix2", "arg2v1"),
|
||||||
("test_two[arg1v1-arg2v1]", "fix2", "arg2v1"),
|
|
||||||
("test_one[arg1v1-arg2v2]", "fix2", "arg2v2"),
|
("test_one[arg1v1-arg2v2]", "fix2", "arg2v2"),
|
||||||
|
("test_two[arg1v1-arg2v1]", "fix2", "arg2v1"),
|
||||||
("test_two[arg1v1-arg2v2]", "fix2", "arg2v2"),
|
("test_two[arg1v1-arg2v2]", "fix2", "arg2v2"),
|
||||||
("issue_519.py", "fix1", "arg1v2"),
|
("issue_519.py", "fix1", "arg1v2"),
|
||||||
("test_one[arg1v2-arg2v1]", "fix2", "arg2v1"),
|
("test_one[arg1v2-arg2v1]", "fix2", "arg2v1"),
|
||||||
("test_two[arg1v2-arg2v1]", "fix2", "arg2v1"),
|
|
||||||
("test_one[arg1v2-arg2v2]", "fix2", "arg2v2"),
|
("test_one[arg1v2-arg2v2]", "fix2", "arg2v2"),
|
||||||
|
("test_two[arg1v2-arg2v1]", "fix2", "arg2v1"),
|
||||||
("test_two[arg1v2-arg2v2]", "fix2", "arg2v2"),
|
("test_two[arg1v2-arg2v2]", "fix2", "arg2v2"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
anyio[curio,trio]==3.7.1
|
anyio[curio,trio]==3.7.1
|
||||||
django==4.2.3
|
django==4.2.4
|
||||||
pytest-asyncio==0.21.1
|
pytest-asyncio==0.21.1
|
||||||
pytest-bdd==6.1.1
|
pytest-bdd==6.1.1
|
||||||
pytest-cov==4.1.0
|
pytest-cov==4.1.0
|
||||||
|
|
|
@ -4,10 +4,9 @@ import textwrap
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest import fixtures
|
|
||||||
from _pytest.compat import getfuncargnames
|
from _pytest.compat import getfuncargnames
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
from _pytest.fixtures import FixtureRequest
|
from _pytest.fixtures import TopRequest
|
||||||
from _pytest.monkeypatch import MonkeyPatch
|
from _pytest.monkeypatch import MonkeyPatch
|
||||||
from _pytest.pytester import get_public_names
|
from _pytest.pytester import get_public_names
|
||||||
from _pytest.pytester import Pytester
|
from _pytest.pytester import Pytester
|
||||||
|
@ -659,7 +658,7 @@ class TestRequestBasic:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
assert isinstance(item, Function)
|
assert isinstance(item, Function)
|
||||||
req = fixtures.FixtureRequest(item, _ispytest=True)
|
req = TopRequest(item, _ispytest=True)
|
||||||
assert req.function == item.obj
|
assert req.function == item.obj
|
||||||
assert req.keywords == item.keywords
|
assert req.keywords == item.keywords
|
||||||
assert hasattr(req.module, "test_func")
|
assert hasattr(req.module, "test_func")
|
||||||
|
@ -701,9 +700,7 @@ class TestRequestBasic:
|
||||||
(item1,) = pytester.genitems([modcol])
|
(item1,) = pytester.genitems([modcol])
|
||||||
assert isinstance(item1, Function)
|
assert isinstance(item1, Function)
|
||||||
assert item1.name == "test_method"
|
assert item1.name == "test_method"
|
||||||
arg2fixturedefs = fixtures.FixtureRequest(
|
arg2fixturedefs = TopRequest(item1, _ispytest=True)._arg2fixturedefs
|
||||||
item1, _ispytest=True
|
|
||||||
)._arg2fixturedefs
|
|
||||||
assert len(arg2fixturedefs) == 1
|
assert len(arg2fixturedefs) == 1
|
||||||
assert arg2fixturedefs["something"][0].argname == "something"
|
assert arg2fixturedefs["something"][0].argname == "something"
|
||||||
|
|
||||||
|
@ -969,7 +966,7 @@ class TestRequestBasic:
|
||||||
modcol = pytester.getmodulecol("def test_somefunc(): pass")
|
modcol = pytester.getmodulecol("def test_somefunc(): pass")
|
||||||
(item,) = pytester.genitems([modcol])
|
(item,) = pytester.genitems([modcol])
|
||||||
assert isinstance(item, Function)
|
assert isinstance(item, Function)
|
||||||
req = fixtures.FixtureRequest(item, _ispytest=True)
|
req = TopRequest(item, _ispytest=True)
|
||||||
assert req.path == modcol.path
|
assert req.path == modcol.path
|
||||||
|
|
||||||
def test_request_fixturenames(self, pytester: Pytester) -> None:
|
def test_request_fixturenames(self, pytester: Pytester) -> None:
|
||||||
|
@ -1128,7 +1125,7 @@ class TestRequestMarking:
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
assert isinstance(item1, Function)
|
assert isinstance(item1, Function)
|
||||||
req1 = fixtures.FixtureRequest(item1, _ispytest=True)
|
req1 = TopRequest(item1, _ispytest=True)
|
||||||
assert "xfail" not in item1.keywords
|
assert "xfail" not in item1.keywords
|
||||||
req1.applymarker(pytest.mark.xfail)
|
req1.applymarker(pytest.mark.xfail)
|
||||||
assert "xfail" in item1.keywords
|
assert "xfail" in item1.keywords
|
||||||
|
@ -2103,9 +2100,7 @@ class TestAutouseManagement:
|
||||||
reprec = pytester.inline_run("-v", "-s", "--confcutdir", pytester.path)
|
reprec = pytester.inline_run("-v", "-s", "--confcutdir", pytester.path)
|
||||||
reprec.assertoutcome(passed=8)
|
reprec.assertoutcome(passed=8)
|
||||||
config = reprec.getcalls("pytest_unconfigure")[0].config
|
config = reprec.getcalls("pytest_unconfigure")[0].config
|
||||||
values = config.pluginmanager._getconftestmodules(
|
values = config.pluginmanager._getconftestmodules(p)[0].values
|
||||||
p, importmode="prepend", rootpath=pytester.path
|
|
||||||
)[0].values
|
|
||||||
assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
|
assert values == ["fin_a1", "fin_a2", "fin_b1", "fin_b2"] * 2
|
||||||
|
|
||||||
def test_scope_ordering(self, pytester: Pytester) -> None:
|
def test_scope_ordering(self, pytester: Pytester) -> None:
|
||||||
|
@ -4041,7 +4036,7 @@ class TestScopeOrdering:
|
||||||
)
|
)
|
||||||
items, _ = pytester.inline_genitems()
|
items, _ = pytester.inline_genitems()
|
||||||
assert isinstance(items[0], Function)
|
assert isinstance(items[0], Function)
|
||||||
request = FixtureRequest(items[0], _ispytest=True)
|
request = TopRequest(items[0], _ispytest=True)
|
||||||
assert request.fixturenames == "m1 f1".split()
|
assert request.fixturenames == "m1 f1".split()
|
||||||
|
|
||||||
def test_func_closure_with_native_fixtures(
|
def test_func_closure_with_native_fixtures(
|
||||||
|
@ -4090,7 +4085,7 @@ class TestScopeOrdering:
|
||||||
)
|
)
|
||||||
items, _ = pytester.inline_genitems()
|
items, _ = pytester.inline_genitems()
|
||||||
assert isinstance(items[0], Function)
|
assert isinstance(items[0], Function)
|
||||||
request = FixtureRequest(items[0], _ispytest=True)
|
request = TopRequest(items[0], _ispytest=True)
|
||||||
# order of fixtures based on their scope and position in the parameter list
|
# order of fixtures based on their scope and position in the parameter list
|
||||||
assert (
|
assert (
|
||||||
request.fixturenames
|
request.fixturenames
|
||||||
|
@ -4118,7 +4113,7 @@ class TestScopeOrdering:
|
||||||
)
|
)
|
||||||
items, _ = pytester.inline_genitems()
|
items, _ = pytester.inline_genitems()
|
||||||
assert isinstance(items[0], Function)
|
assert isinstance(items[0], Function)
|
||||||
request = FixtureRequest(items[0], _ispytest=True)
|
request = TopRequest(items[0], _ispytest=True)
|
||||||
assert request.fixturenames == "m1 f1".split()
|
assert request.fixturenames == "m1 f1".split()
|
||||||
|
|
||||||
def test_func_closure_scopes_reordered(self, pytester: Pytester) -> None:
|
def test_func_closure_scopes_reordered(self, pytester: Pytester) -> None:
|
||||||
|
@ -4152,7 +4147,7 @@ class TestScopeOrdering:
|
||||||
)
|
)
|
||||||
items, _ = pytester.inline_genitems()
|
items, _ = pytester.inline_genitems()
|
||||||
assert isinstance(items[0], Function)
|
assert isinstance(items[0], Function)
|
||||||
request = FixtureRequest(items[0], _ispytest=True)
|
request = TopRequest(items[0], _ispytest=True)
|
||||||
assert request.fixturenames == "s1 m1 c1 f2 f1".split()
|
assert request.fixturenames == "s1 m1 c1 f2 f1".split()
|
||||||
|
|
||||||
def test_func_closure_same_scope_closer_root_first(
|
def test_func_closure_same_scope_closer_root_first(
|
||||||
|
@ -4195,7 +4190,7 @@ class TestScopeOrdering:
|
||||||
)
|
)
|
||||||
items, _ = pytester.inline_genitems()
|
items, _ = pytester.inline_genitems()
|
||||||
assert isinstance(items[0], Function)
|
assert isinstance(items[0], Function)
|
||||||
request = FixtureRequest(items[0], _ispytest=True)
|
request = TopRequest(items[0], _ispytest=True)
|
||||||
assert request.fixturenames == "p_sub m_conf m_sub m_test f1".split()
|
assert request.fixturenames == "p_sub m_conf m_sub m_test f1".split()
|
||||||
|
|
||||||
def test_func_closure_all_scopes_complex(self, pytester: Pytester) -> None:
|
def test_func_closure_all_scopes_complex(self, pytester: Pytester) -> None:
|
||||||
|
@ -4240,7 +4235,7 @@ class TestScopeOrdering:
|
||||||
)
|
)
|
||||||
items, _ = pytester.inline_genitems()
|
items, _ = pytester.inline_genitems()
|
||||||
assert isinstance(items[0], Function)
|
assert isinstance(items[0], Function)
|
||||||
request = FixtureRequest(items[0], _ispytest=True)
|
request = TopRequest(items[0], _ispytest=True)
|
||||||
assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split()
|
assert request.fixturenames == "s1 p1 m1 m2 c1 f2 f1".split()
|
||||||
|
|
||||||
def test_multiple_packages(self, pytester: Pytester) -> None:
|
def test_multiple_packages(self, pytester: Pytester) -> None:
|
||||||
|
|
|
@ -23,6 +23,7 @@ from _pytest.compat import getfuncargnames
|
||||||
from _pytest.compat import NOTSET
|
from _pytest.compat import NOTSET
|
||||||
from _pytest.outcomes import fail
|
from _pytest.outcomes import fail
|
||||||
from _pytest.pytester import Pytester
|
from _pytest.pytester import Pytester
|
||||||
|
from _pytest.python import Function
|
||||||
from _pytest.python import IdMaker
|
from _pytest.python import IdMaker
|
||||||
from _pytest.scope import Scope
|
from _pytest.scope import Scope
|
||||||
|
|
||||||
|
@ -33,11 +34,19 @@ class TestMetafunc:
|
||||||
# on the funcarg level, so we don't need a full blown
|
# on the funcarg level, so we don't need a full blown
|
||||||
# initialization.
|
# initialization.
|
||||||
class FuncFixtureInfoMock:
|
class FuncFixtureInfoMock:
|
||||||
name2fixturedefs = None
|
name2fixturedefs: Dict[str, List[fixtures.FixtureDef[object]]] = {}
|
||||||
|
|
||||||
def __init__(self, names):
|
def __init__(self, names):
|
||||||
self.names_closure = names
|
self.names_closure = names
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class FixtureManagerMock:
|
||||||
|
config: Any
|
||||||
|
|
||||||
|
@dataclasses.dataclass
|
||||||
|
class SessionMock:
|
||||||
|
_fixturemanager: FixtureManagerMock
|
||||||
|
|
||||||
@dataclasses.dataclass
|
@dataclasses.dataclass
|
||||||
class DefinitionMock(python.FunctionDefinition):
|
class DefinitionMock(python.FunctionDefinition):
|
||||||
_nodeid: str
|
_nodeid: str
|
||||||
|
@ -46,6 +55,8 @@ class TestMetafunc:
|
||||||
names = getfuncargnames(func)
|
names = getfuncargnames(func)
|
||||||
fixtureinfo: Any = FuncFixtureInfoMock(names)
|
fixtureinfo: Any = FuncFixtureInfoMock(names)
|
||||||
definition: Any = DefinitionMock._create(obj=func, _nodeid="mock::nodeid")
|
definition: Any = DefinitionMock._create(obj=func, _nodeid="mock::nodeid")
|
||||||
|
definition._fixtureinfo = fixtureinfo
|
||||||
|
definition.session = SessionMock(FixtureManagerMock({}))
|
||||||
return python.Metafunc(definition, fixtureinfo, config, _ispytest=True)
|
return python.Metafunc(definition, fixtureinfo, config, _ispytest=True)
|
||||||
|
|
||||||
def test_no_funcargs(self) -> None:
|
def test_no_funcargs(self) -> None:
|
||||||
|
@ -98,7 +109,7 @@ class TestMetafunc:
|
||||||
# When the input is an iterator, only len(args) are taken,
|
# When the input is an iterator, only len(args) are taken,
|
||||||
# so the bad Exc isn't reached.
|
# so the bad Exc isn't reached.
|
||||||
metafunc.parametrize("x", [1, 2], ids=gen()) # type: ignore[arg-type]
|
metafunc.parametrize("x", [1, 2], ids=gen()) # type: ignore[arg-type]
|
||||||
assert [(x.funcargs, x.id) for x in metafunc._calls] == [
|
assert [(x.params, x.id) for x in metafunc._calls] == [
|
||||||
({"x": 1}, "0"),
|
({"x": 1}, "0"),
|
||||||
({"x": 2}, "2"),
|
({"x": 2}, "2"),
|
||||||
]
|
]
|
||||||
|
@ -151,6 +162,7 @@ class TestMetafunc:
|
||||||
module_fix=[DummyFixtureDef(Scope.Module)],
|
module_fix=[DummyFixtureDef(Scope.Module)],
|
||||||
class_fix=[DummyFixtureDef(Scope.Class)],
|
class_fix=[DummyFixtureDef(Scope.Class)],
|
||||||
func_fix=[DummyFixtureDef(Scope.Function)],
|
func_fix=[DummyFixtureDef(Scope.Function)],
|
||||||
|
mixed_fix=[DummyFixtureDef(Scope.Module), DummyFixtureDef(Scope.Class)],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -187,6 +199,7 @@ class TestMetafunc:
|
||||||
)
|
)
|
||||||
== Scope.Module
|
== Scope.Module
|
||||||
)
|
)
|
||||||
|
assert find_scope(["mixed_fix"], indirect=True) == Scope.Class
|
||||||
|
|
||||||
def test_parametrize_and_id(self) -> None:
|
def test_parametrize_and_id(self) -> None:
|
||||||
def func(x, y):
|
def func(x, y):
|
||||||
|
@ -712,8 +725,6 @@ class TestMetafunc:
|
||||||
metafunc.parametrize("x", [1], indirect=True)
|
metafunc.parametrize("x", [1], indirect=True)
|
||||||
metafunc.parametrize("y", [2, 3], indirect=True)
|
metafunc.parametrize("y", [2, 3], indirect=True)
|
||||||
assert len(metafunc._calls) == 2
|
assert len(metafunc._calls) == 2
|
||||||
assert metafunc._calls[0].funcargs == {}
|
|
||||||
assert metafunc._calls[1].funcargs == {}
|
|
||||||
assert metafunc._calls[0].params == dict(x=1, y=2)
|
assert metafunc._calls[0].params == dict(x=1, y=2)
|
||||||
assert metafunc._calls[1].params == dict(x=1, y=3)
|
assert metafunc._calls[1].params == dict(x=1, y=3)
|
||||||
|
|
||||||
|
@ -725,8 +736,10 @@ class TestMetafunc:
|
||||||
|
|
||||||
metafunc = self.Metafunc(func)
|
metafunc = self.Metafunc(func)
|
||||||
metafunc.parametrize("x, y", [("a", "b")], indirect=["x"])
|
metafunc.parametrize("x, y", [("a", "b")], indirect=["x"])
|
||||||
assert metafunc._calls[0].funcargs == dict(y="b")
|
assert metafunc._calls[0].params == dict(x="a", y="b")
|
||||||
assert metafunc._calls[0].params == dict(x="a")
|
# Since `y` is a direct parameter, its pseudo-fixture would
|
||||||
|
# be registered.
|
||||||
|
assert list(metafunc._arg2fixturedefs.keys()) == ["y"]
|
||||||
|
|
||||||
def test_parametrize_indirect_list_all(self) -> None:
|
def test_parametrize_indirect_list_all(self) -> None:
|
||||||
"""#714"""
|
"""#714"""
|
||||||
|
@ -736,8 +749,8 @@ class TestMetafunc:
|
||||||
|
|
||||||
metafunc = self.Metafunc(func)
|
metafunc = self.Metafunc(func)
|
||||||
metafunc.parametrize("x, y", [("a", "b")], indirect=["x", "y"])
|
metafunc.parametrize("x, y", [("a", "b")], indirect=["x", "y"])
|
||||||
assert metafunc._calls[0].funcargs == {}
|
|
||||||
assert metafunc._calls[0].params == dict(x="a", y="b")
|
assert metafunc._calls[0].params == dict(x="a", y="b")
|
||||||
|
assert list(metafunc._arg2fixturedefs.keys()) == []
|
||||||
|
|
||||||
def test_parametrize_indirect_list_empty(self) -> None:
|
def test_parametrize_indirect_list_empty(self) -> None:
|
||||||
"""#714"""
|
"""#714"""
|
||||||
|
@ -747,8 +760,8 @@ class TestMetafunc:
|
||||||
|
|
||||||
metafunc = self.Metafunc(func)
|
metafunc = self.Metafunc(func)
|
||||||
metafunc.parametrize("x, y", [("a", "b")], indirect=[])
|
metafunc.parametrize("x, y", [("a", "b")], indirect=[])
|
||||||
assert metafunc._calls[0].funcargs == dict(x="a", y="b")
|
assert metafunc._calls[0].params == dict(x="a", y="b")
|
||||||
assert metafunc._calls[0].params == {}
|
assert list(metafunc._arg2fixturedefs.keys()) == ["x", "y"]
|
||||||
|
|
||||||
def test_parametrize_indirect_wrong_type(self) -> None:
|
def test_parametrize_indirect_wrong_type(self) -> None:
|
||||||
def func(x, y):
|
def func(x, y):
|
||||||
|
@ -942,9 +955,9 @@ class TestMetafunc:
|
||||||
metafunc = self.Metafunc(lambda x: None)
|
metafunc = self.Metafunc(lambda x: None)
|
||||||
metafunc.parametrize("x", [1, 2])
|
metafunc.parametrize("x", [1, 2])
|
||||||
assert len(metafunc._calls) == 2
|
assert len(metafunc._calls) == 2
|
||||||
assert metafunc._calls[0].funcargs == dict(x=1)
|
assert metafunc._calls[0].params == dict(x=1)
|
||||||
assert metafunc._calls[0].id == "1"
|
assert metafunc._calls[0].id == "1"
|
||||||
assert metafunc._calls[1].funcargs == dict(x=2)
|
assert metafunc._calls[1].params == dict(x=2)
|
||||||
assert metafunc._calls[1].id == "2"
|
assert metafunc._calls[1].id == "2"
|
||||||
|
|
||||||
def test_parametrize_onearg_indirect(self) -> None:
|
def test_parametrize_onearg_indirect(self) -> None:
|
||||||
|
@ -959,11 +972,45 @@ class TestMetafunc:
|
||||||
metafunc = self.Metafunc(lambda x, y: None)
|
metafunc = self.Metafunc(lambda x, y: None)
|
||||||
metafunc.parametrize(("x", "y"), [(1, 2), (3, 4)])
|
metafunc.parametrize(("x", "y"), [(1, 2), (3, 4)])
|
||||||
assert len(metafunc._calls) == 2
|
assert len(metafunc._calls) == 2
|
||||||
assert metafunc._calls[0].funcargs == dict(x=1, y=2)
|
assert metafunc._calls[0].params == dict(x=1, y=2)
|
||||||
assert metafunc._calls[0].id == "1-2"
|
assert metafunc._calls[0].id == "1-2"
|
||||||
assert metafunc._calls[1].funcargs == dict(x=3, y=4)
|
assert metafunc._calls[1].params == dict(x=3, y=4)
|
||||||
assert metafunc._calls[1].id == "3-4"
|
assert metafunc._calls[1].id == "3-4"
|
||||||
|
|
||||||
|
def test_high_scoped_parametrize_reordering(self, pytester: Pytester) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("arg2", [3, 4])
|
||||||
|
@pytest.mark.parametrize("arg1", [0, 1, 2], scope='module')
|
||||||
|
def test1(arg1, arg2):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test2():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("arg1", [0, 1, 2], scope='module')
|
||||||
|
def test3(arg1):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest("--collect-only")
|
||||||
|
result.stdout.re_match_lines(
|
||||||
|
[
|
||||||
|
r" <Function test1\[0-3\]>",
|
||||||
|
r" <Function test1\[0-4\]>",
|
||||||
|
r" <Function test3\[0\]>",
|
||||||
|
r" <Function test1\[1-3\]>",
|
||||||
|
r" <Function test1\[1-4\]>",
|
||||||
|
r" <Function test3\[1\]>",
|
||||||
|
r" <Function test1\[2-3\]>",
|
||||||
|
r" <Function test1\[2-4\]>",
|
||||||
|
r" <Function test3\[2\]>",
|
||||||
|
r" <Function test2>",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def test_parametrize_multiple_times(self, pytester: Pytester) -> None:
|
def test_parametrize_multiple_times(self, pytester: Pytester) -> None:
|
||||||
pytester.makepyfile(
|
pytester.makepyfile(
|
||||||
"""
|
"""
|
||||||
|
@ -1503,6 +1550,98 @@ class TestMetafuncFunctional:
|
||||||
result = pytester.runpytest()
|
result = pytester.runpytest()
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
|
def test_parametrize_module_level_test_with_class_scope(
|
||||||
|
self, pytester: Pytester
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Test that a class-scoped parametrization without a corresponding `Class`
|
||||||
|
gets module scope, i.e. we only create a single FixtureDef for it per module.
|
||||||
|
"""
|
||||||
|
module = pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("x", [0, 1], scope="class")
|
||||||
|
def test_1(x):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("x", [1, 2], scope="module")
|
||||||
|
def test_2(x):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
test_1_0, _, test_2_0, _ = pytester.genitems((pytester.getmodulecol(module),))
|
||||||
|
|
||||||
|
assert isinstance(test_1_0, Function)
|
||||||
|
assert test_1_0.name == "test_1[0]"
|
||||||
|
test_1_fixture_x = test_1_0._fixtureinfo.name2fixturedefs["x"][-1]
|
||||||
|
|
||||||
|
assert isinstance(test_2_0, Function)
|
||||||
|
assert test_2_0.name == "test_2[1]"
|
||||||
|
test_2_fixture_x = test_2_0._fixtureinfo.name2fixturedefs["x"][-1]
|
||||||
|
|
||||||
|
assert test_1_fixture_x is test_2_fixture_x
|
||||||
|
|
||||||
|
def test_reordering_with_scopeless_and_just_indirect_parametrization(
|
||||||
|
self, pytester: Pytester
|
||||||
|
) -> None:
|
||||||
|
pytester.makeconftest(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture(scope="package")
|
||||||
|
def fixture1():
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def fixture0():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def fixture1(fixture0):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("fixture1", [0], indirect=True)
|
||||||
|
def test_0(fixture1):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.fixture(scope="module")
|
||||||
|
def fixture():
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("fixture", [0], indirect=True)
|
||||||
|
def test_1(fixture):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_2():
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Test:
|
||||||
|
@pytest.fixture(scope="class")
|
||||||
|
def fixture(self, fixture):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("fixture", [0], indirect=True)
|
||||||
|
def test_3(self, fixture):
|
||||||
|
pass
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest("-v")
|
||||||
|
assert result.ret == 0
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"*test_0*",
|
||||||
|
"*test_1*",
|
||||||
|
"*test_2*",
|
||||||
|
"*test_3*",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestMetafuncFunctionalAuto:
|
class TestMetafuncFunctionalAuto:
|
||||||
"""Tests related to automatically find out the correct scope for
|
"""Tests related to automatically find out the correct scope for
|
||||||
|
|
|
@ -685,6 +685,25 @@ class TestAssertionRewrite:
|
||||||
assert msg is not None
|
assert msg is not None
|
||||||
assert "<MY42 object> < 0" in msg
|
assert "<MY42 object> < 0" in msg
|
||||||
|
|
||||||
|
def test_assert_handling_raise_in__iter__(self, pytester: Pytester) -> None:
|
||||||
|
pytester.makepyfile(
|
||||||
|
"""\
|
||||||
|
class A:
|
||||||
|
def __iter__(self):
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
def __eq__(self, o: object) -> bool:
|
||||||
|
return self is o
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<A object>"
|
||||||
|
|
||||||
|
assert A() == A()
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest()
|
||||||
|
result.stdout.fnmatch_lines(["*E*assert <A object> == <A object>"])
|
||||||
|
|
||||||
def test_formatchar(self) -> None:
|
def test_formatchar(self) -> None:
|
||||||
def f() -> None:
|
def f() -> None:
|
||||||
assert "%test" == "test" # type: ignore[comparison-overlap]
|
assert "%test" == "test" # type: ignore[comparison-overlap]
|
||||||
|
|
|
@ -507,6 +507,24 @@ class TestParseIni:
|
||||||
result = pytester.runpytest("--foo=1")
|
result = pytester.runpytest("--foo=1")
|
||||||
result.stdout.fnmatch_lines("* no tests ran in *")
|
result.stdout.fnmatch_lines("* no tests ran in *")
|
||||||
|
|
||||||
|
def test_args_source_args(self, pytester: Pytester):
|
||||||
|
config = pytester.parseconfig("--", "test_filename.py")
|
||||||
|
assert config.args_source == Config.ArgsSource.ARGS
|
||||||
|
|
||||||
|
def test_args_source_invocation_dir(self, pytester: Pytester):
|
||||||
|
config = pytester.parseconfig()
|
||||||
|
assert config.args_source == Config.ArgsSource.INVOCATION_DIR
|
||||||
|
|
||||||
|
def test_args_source_testpaths(self, pytester: Pytester):
|
||||||
|
pytester.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
testpaths=*
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
config = pytester.parseconfig()
|
||||||
|
assert config.args_source == Config.ArgsSource.TESTPATHS
|
||||||
|
|
||||||
|
|
||||||
class TestConfigCmdlineParsing:
|
class TestConfigCmdlineParsing:
|
||||||
def test_parsing_again_fails(self, pytester: Pytester) -> None:
|
def test_parsing_again_fails(self, pytester: Pytester) -> None:
|
||||||
|
@ -642,18 +660,11 @@ class TestConfigAPI:
|
||||||
p = tmp_path.joinpath("conftest.py")
|
p = tmp_path.joinpath("conftest.py")
|
||||||
p.write_text(f"mylist = {['.', str(somepath)]}", encoding="utf-8")
|
p.write_text(f"mylist = {['.', str(somepath)]}", encoding="utf-8")
|
||||||
config = pytester.parseconfigure(p)
|
config = pytester.parseconfigure(p)
|
||||||
assert (
|
assert config._getconftest_pathlist("notexist", path=tmp_path) is None
|
||||||
config._getconftest_pathlist("notexist", path=tmp_path, rootpath=tmp_path)
|
assert config._getconftest_pathlist("mylist", path=tmp_path) == [
|
||||||
is None
|
tmp_path,
|
||||||
)
|
somepath,
|
||||||
pl = (
|
]
|
||||||
config._getconftest_pathlist("mylist", path=tmp_path, rootpath=tmp_path)
|
|
||||||
or []
|
|
||||||
)
|
|
||||||
print(pl)
|
|
||||||
assert len(pl) == 2
|
|
||||||
assert pl[0] == tmp_path
|
|
||||||
assert pl[1] == somepath
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("maybe_type", ["not passed", "None", '"string"'])
|
@pytest.mark.parametrize("maybe_type", ["not passed", "None", '"string"'])
|
||||||
def test_addini(self, pytester: Pytester, maybe_type: str) -> None:
|
def test_addini(self, pytester: Pytester, maybe_type: str) -> None:
|
||||||
|
|
|
@ -62,28 +62,22 @@ class TestConftestValueAccessGlobal:
|
||||||
def test_basic_init(self, basedir: Path) -> None:
|
def test_basic_init(self, basedir: Path) -> None:
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
p = basedir / "adir"
|
p = basedir / "adir"
|
||||||
assert (
|
conftest._loadconftestmodules(p, importmode="prepend", rootpath=basedir)
|
||||||
conftest._rget_with_confmod("a", p, importmode="prepend", rootpath=basedir)[
|
assert conftest._rget_with_confmod("a", p)[1] == 1
|
||||||
1
|
|
||||||
]
|
|
||||||
== 1
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_immediate_initialiation_and_incremental_are_the_same(
|
def test_immediate_initialiation_and_incremental_are_the_same(
|
||||||
self, basedir: Path
|
self, basedir: Path
|
||||||
) -> None:
|
) -> None:
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
assert not len(conftest._dirpath2confmods)
|
assert not len(conftest._dirpath2confmods)
|
||||||
conftest._getconftestmodules(
|
conftest._loadconftestmodules(basedir, importmode="prepend", rootpath=basedir)
|
||||||
basedir, importmode="prepend", rootpath=Path(basedir)
|
|
||||||
)
|
|
||||||
snap1 = len(conftest._dirpath2confmods)
|
snap1 = len(conftest._dirpath2confmods)
|
||||||
assert snap1 == 1
|
assert snap1 == 1
|
||||||
conftest._getconftestmodules(
|
conftest._loadconftestmodules(
|
||||||
basedir / "adir", importmode="prepend", rootpath=basedir
|
basedir / "adir", importmode="prepend", rootpath=basedir
|
||||||
)
|
)
|
||||||
assert len(conftest._dirpath2confmods) == snap1 + 1
|
assert len(conftest._dirpath2confmods) == snap1 + 1
|
||||||
conftest._getconftestmodules(
|
conftest._loadconftestmodules(
|
||||||
basedir / "b", importmode="prepend", rootpath=basedir
|
basedir / "b", importmode="prepend", rootpath=basedir
|
||||||
)
|
)
|
||||||
assert len(conftest._dirpath2confmods) == snap1 + 2
|
assert len(conftest._dirpath2confmods) == snap1 + 2
|
||||||
|
@ -91,33 +85,23 @@ class TestConftestValueAccessGlobal:
|
||||||
def test_value_access_not_existing(self, basedir: Path) -> None:
|
def test_value_access_not_existing(self, basedir: Path) -> None:
|
||||||
conftest = ConftestWithSetinitial(basedir)
|
conftest = ConftestWithSetinitial(basedir)
|
||||||
with pytest.raises(KeyError):
|
with pytest.raises(KeyError):
|
||||||
conftest._rget_with_confmod(
|
conftest._rget_with_confmod("a", basedir)
|
||||||
"a", basedir, importmode="prepend", rootpath=Path(basedir)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_value_access_by_path(self, basedir: Path) -> None:
|
def test_value_access_by_path(self, basedir: Path) -> None:
|
||||||
conftest = ConftestWithSetinitial(basedir)
|
conftest = ConftestWithSetinitial(basedir)
|
||||||
adir = basedir / "adir"
|
adir = basedir / "adir"
|
||||||
assert (
|
conftest._loadconftestmodules(adir, importmode="prepend", rootpath=basedir)
|
||||||
conftest._rget_with_confmod(
|
assert conftest._rget_with_confmod("a", adir)[1] == 1
|
||||||
"a", adir, importmode="prepend", rootpath=basedir
|
conftest._loadconftestmodules(
|
||||||
)[1]
|
adir / "b", importmode="prepend", rootpath=basedir
|
||||||
== 1
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
conftest._rget_with_confmod(
|
|
||||||
"a", adir / "b", importmode="prepend", rootpath=basedir
|
|
||||||
)[1]
|
|
||||||
== 1.5
|
|
||||||
)
|
)
|
||||||
|
assert conftest._rget_with_confmod("a", adir / "b")[1] == 1.5
|
||||||
|
|
||||||
def test_value_access_with_confmod(self, basedir: Path) -> None:
|
def test_value_access_with_confmod(self, basedir: Path) -> None:
|
||||||
startdir = basedir / "adir" / "b"
|
startdir = basedir / "adir" / "b"
|
||||||
startdir.joinpath("xx").mkdir()
|
startdir.joinpath("xx").mkdir()
|
||||||
conftest = ConftestWithSetinitial(startdir)
|
conftest = ConftestWithSetinitial(startdir)
|
||||||
mod, value = conftest._rget_with_confmod(
|
mod, value = conftest._rget_with_confmod("a", startdir)
|
||||||
"a", startdir, importmode="prepend", rootpath=Path(basedir)
|
|
||||||
)
|
|
||||||
assert value == 1.5
|
assert value == 1.5
|
||||||
assert mod.__file__ is not None
|
assert mod.__file__ is not None
|
||||||
path = Path(mod.__file__)
|
path = Path(mod.__file__)
|
||||||
|
@ -143,9 +127,7 @@ def test_doubledash_considered(pytester: Pytester) -> None:
|
||||||
conf.joinpath("conftest.py").touch()
|
conf.joinpath("conftest.py").touch()
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
conftest_setinitial(conftest, [conf.name, conf.name])
|
conftest_setinitial(conftest, [conf.name, conf.name])
|
||||||
values = conftest._getconftestmodules(
|
values = conftest._getconftestmodules(conf)
|
||||||
conf, importmode="prepend", rootpath=pytester.path
|
|
||||||
)
|
|
||||||
assert len(values) == 1
|
assert len(values) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@ -192,26 +174,22 @@ def test_conftestcutdir(pytester: Pytester) -> None:
|
||||||
p = pytester.mkdir("x")
|
p = pytester.mkdir("x")
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
conftest_setinitial(conftest, [pytester.path], confcutdir=p)
|
conftest_setinitial(conftest, [pytester.path], confcutdir=p)
|
||||||
values = conftest._getconftestmodules(
|
conftest._loadconftestmodules(p, importmode="prepend", rootpath=pytester.path)
|
||||||
p, importmode="prepend", rootpath=pytester.path
|
values = conftest._getconftestmodules(p)
|
||||||
)
|
|
||||||
assert len(values) == 0
|
assert len(values) == 0
|
||||||
values = conftest._getconftestmodules(
|
conftest._loadconftestmodules(
|
||||||
conf.parent, importmode="prepend", rootpath=pytester.path
|
conf.parent, importmode="prepend", rootpath=pytester.path
|
||||||
)
|
)
|
||||||
|
values = conftest._getconftestmodules(conf.parent)
|
||||||
assert len(values) == 0
|
assert len(values) == 0
|
||||||
assert not conftest.has_plugin(str(conf))
|
assert not conftest.has_plugin(str(conf))
|
||||||
# but we can still import a conftest directly
|
# but we can still import a conftest directly
|
||||||
conftest._importconftest(conf, importmode="prepend", rootpath=pytester.path)
|
conftest._importconftest(conf, importmode="prepend", rootpath=pytester.path)
|
||||||
values = conftest._getconftestmodules(
|
values = conftest._getconftestmodules(conf.parent)
|
||||||
conf.parent, importmode="prepend", rootpath=pytester.path
|
|
||||||
)
|
|
||||||
assert values[0].__file__ is not None
|
assert values[0].__file__ is not None
|
||||||
assert values[0].__file__.startswith(str(conf))
|
assert values[0].__file__.startswith(str(conf))
|
||||||
# and all sub paths get updated properly
|
# and all sub paths get updated properly
|
||||||
values = conftest._getconftestmodules(
|
values = conftest._getconftestmodules(p)
|
||||||
p, importmode="prepend", rootpath=pytester.path
|
|
||||||
)
|
|
||||||
assert len(values) == 1
|
assert len(values) == 1
|
||||||
assert values[0].__file__ is not None
|
assert values[0].__file__ is not None
|
||||||
assert values[0].__file__.startswith(str(conf))
|
assert values[0].__file__.startswith(str(conf))
|
||||||
|
@ -221,9 +199,7 @@ def test_conftestcutdir_inplace_considered(pytester: Pytester) -> None:
|
||||||
conf = pytester.makeconftest("")
|
conf = pytester.makeconftest("")
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
conftest_setinitial(conftest, [conf.parent], confcutdir=conf.parent)
|
conftest_setinitial(conftest, [conf.parent], confcutdir=conf.parent)
|
||||||
values = conftest._getconftestmodules(
|
values = conftest._getconftestmodules(conf.parent)
|
||||||
conf.parent, importmode="prepend", rootpath=pytester.path
|
|
||||||
)
|
|
||||||
assert len(values) == 1
|
assert len(values) == 1
|
||||||
assert values[0].__file__ is not None
|
assert values[0].__file__ is not None
|
||||||
assert values[0].__file__.startswith(str(conf))
|
assert values[0].__file__.startswith(str(conf))
|
||||||
|
@ -433,10 +409,8 @@ def test_conftest_import_order(pytester: Pytester, monkeypatch: MonkeyPatch) ->
|
||||||
conftest = PytestPluginManager()
|
conftest = PytestPluginManager()
|
||||||
conftest._confcutdir = pytester.path
|
conftest._confcutdir = pytester.path
|
||||||
monkeypatch.setattr(conftest, "_importconftest", impct)
|
monkeypatch.setattr(conftest, "_importconftest", impct)
|
||||||
mods = cast(
|
conftest._loadconftestmodules(sub, importmode="prepend", rootpath=pytester.path)
|
||||||
List[Path],
|
mods = cast(List[Path], conftest._getconftestmodules(sub))
|
||||||
conftest._getconftestmodules(sub, importmode="prepend", rootpath=pytester.path),
|
|
||||||
)
|
|
||||||
expected = [ct1, ct2]
|
expected = [ct1, ct2]
|
||||||
assert mods == expected
|
assert mods == expected
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.compat import LEGACY_PATH
|
from _pytest.compat import LEGACY_PATH
|
||||||
|
from _pytest.fixtures import TopRequest
|
||||||
from _pytest.legacypath import TempdirFactory
|
from _pytest.legacypath import TempdirFactory
|
||||||
from _pytest.legacypath import Testdir
|
from _pytest.legacypath import Testdir
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ def test_fixturerequest_getmodulepath(pytester: pytest.Pytester) -> None:
|
||||||
modcol = pytester.getmodulecol("def test_somefunc(): pass")
|
modcol = pytester.getmodulecol("def test_somefunc(): pass")
|
||||||
(item,) = pytester.genitems([modcol])
|
(item,) = pytester.genitems([modcol])
|
||||||
assert isinstance(item, pytest.Function)
|
assert isinstance(item, pytest.Function)
|
||||||
req = pytest.FixtureRequest(item, _ispytest=True)
|
req = TopRequest(item, _ispytest=True)
|
||||||
assert req.path == modcol.path
|
assert req.path == modcol.path
|
||||||
assert req.fspath == modcol.fspath # type: ignore[attr-defined]
|
assert req.fspath == modcol.fspath # type: ignore[attr-defined]
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue