aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/python/pytest/py3/_pytest/python.py
diff options
context:
space:
mode:
authordeshevoy <deshevoy@yandex-team.ru>2022-02-10 16:46:56 +0300
committerDaniil Cherednik <dcherednik@yandex-team.ru>2022-02-10 16:46:56 +0300
commite988f30484abe5fdeedcc7a5d3c226c01a21800c (patch)
tree0a217b173aabb57b7e51f8a169989b1a3e0309fe /contrib/python/pytest/py3/_pytest/python.py
parent33ee501c05d3f24036ae89766a858930ae66c548 (diff)
downloadydb-e988f30484abe5fdeedcc7a5d3c226c01a21800c.tar.gz
Restoring authorship annotation for <deshevoy@yandex-team.ru>. Commit 1 of 2.
Diffstat (limited to 'contrib/python/pytest/py3/_pytest/python.py')
-rw-r--r--contrib/python/pytest/py3/_pytest/python.py1406
1 files changed, 703 insertions, 703 deletions
diff --git a/contrib/python/pytest/py3/_pytest/python.py b/contrib/python/pytest/py3/_pytest/python.py
index f1a47d7d33..c5d6702e0a 100644
--- a/contrib/python/pytest/py3/_pytest/python.py
+++ b/contrib/python/pytest/py3/_pytest/python.py
@@ -1,12 +1,12 @@
"""Python test discovery, setup and run of test functions."""
import enum
-import fnmatch
-import inspect
+import fnmatch
+import inspect
import itertools
-import os
-import sys
+import os
+import sys
import types
-import warnings
+import warnings
from collections import Counter
from collections import defaultdict
from functools import partial
@@ -25,95 +25,95 @@ from typing import Tuple
from typing import Type
from typing import TYPE_CHECKING
from typing import Union
-
-import py
-
-import _pytest
-from _pytest import fixtures
-from _pytest import nodes
-from _pytest._code import filter_traceback
+
+import py
+
+import _pytest
+from _pytest import fixtures
+from _pytest import nodes
+from _pytest._code import filter_traceback
from _pytest._code import getfslineno
from _pytest._code.code import ExceptionInfo
from _pytest._code.code import TerminalRepr
from _pytest._io import TerminalWriter
from _pytest._io.saferepr import saferepr
-from _pytest.compat import ascii_escaped
+from _pytest.compat import ascii_escaped
from _pytest.compat import final
-from _pytest.compat import get_default_arg_names
-from _pytest.compat import get_real_func
-from _pytest.compat import getimfunc
-from _pytest.compat import getlocation
+from _pytest.compat import get_default_arg_names
+from _pytest.compat import get_real_func
+from _pytest.compat import getimfunc
+from _pytest.compat import getlocation
from _pytest.compat import is_async_function
-from _pytest.compat import is_generator
-from _pytest.compat import NOTSET
-from _pytest.compat import REGEX_TYPE
-from _pytest.compat import safe_getattr
-from _pytest.compat import safe_isclass
-from _pytest.compat import STRING_TYPES
+from _pytest.compat import is_generator
+from _pytest.compat import NOTSET
+from _pytest.compat import REGEX_TYPE
+from _pytest.compat import safe_getattr
+from _pytest.compat import safe_isclass
+from _pytest.compat import STRING_TYPES
from _pytest.config import Config
from _pytest.config import ExitCode
-from _pytest.config import hookimpl
+from _pytest.config import hookimpl
from _pytest.config.argparsing import Parser
from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
from _pytest.fixtures import FuncFixtureInfo
from _pytest.main import Session
from _pytest.mark import MARK_GEN
from _pytest.mark import ParameterSet
-from _pytest.mark.structures import get_unpacked_marks
+from _pytest.mark.structures import get_unpacked_marks
from _pytest.mark.structures import Mark
from _pytest.mark.structures import MarkDecorator
-from _pytest.mark.structures import normalize_mark_list
-from _pytest.outcomes import fail
+from _pytest.mark.structures import normalize_mark_list
+from _pytest.outcomes import fail
from _pytest.outcomes import skip
from _pytest.pathlib import import_path
from _pytest.pathlib import ImportPathMismatchError
-from _pytest.pathlib import parts
+from _pytest.pathlib import parts
from _pytest.pathlib import visit
from _pytest.warning_types import PytestCollectionWarning
from _pytest.warning_types import PytestUnhandledCoroutineWarning
-
+
if TYPE_CHECKING:
from typing_extensions import Literal
from _pytest.fixtures import _Scope
-
-
+
+
def pytest_addoption(parser: Parser) -> None:
- group = parser.getgroup("general")
- group.addoption(
- "--fixtures",
- "--funcargs",
- action="store_true",
- dest="showfixtures",
- default=False,
- help="show available fixtures, sorted by plugin appearance "
- "(fixtures with leading '_' are only shown with '-v')",
- )
- group.addoption(
- "--fixtures-per-test",
- action="store_true",
- dest="show_fixtures_per_test",
- default=False,
- help="show fixtures per test",
- )
- parser.addini(
- "python_files",
- type="args",
+ group = parser.getgroup("general")
+ group.addoption(
+ "--fixtures",
+ "--funcargs",
+ action="store_true",
+ dest="showfixtures",
+ default=False,
+ help="show available fixtures, sorted by plugin appearance "
+ "(fixtures with leading '_' are only shown with '-v')",
+ )
+ group.addoption(
+ "--fixtures-per-test",
+ action="store_true",
+ dest="show_fixtures_per_test",
+ default=False,
+ help="show fixtures per test",
+ )
+ parser.addini(
+ "python_files",
+ type="args",
# NOTE: default is also used in AssertionRewritingHook.
- default=["test_*.py", "*_test.py"],
- help="glob-style file patterns for Python test module discovery",
- )
- parser.addini(
- "python_classes",
- type="args",
- default=["Test"],
- help="prefixes or glob names for Python test class discovery",
- )
- parser.addini(
- "python_functions",
- type="args",
- default=["test"],
- help="prefixes or glob names for Python test function and method discovery",
- )
+ default=["test_*.py", "*_test.py"],
+ help="glob-style file patterns for Python test module discovery",
+ )
+ parser.addini(
+ "python_classes",
+ type="args",
+ default=["Test"],
+ help="prefixes or glob names for Python test class discovery",
+ )
+ parser.addini(
+ "python_functions",
+ type="args",
+ default=["test"],
+ help="prefixes or glob names for Python test function and method discovery",
+ )
parser.addini(
"disable_test_id_escaping_and_forfeit_all_rights_to_community_support",
type="bool",
@@ -121,44 +121,44 @@ def pytest_addoption(parser: Parser) -> None:
help="disable string escape non-ascii characters, might cause unwanted "
"side effects(use at your own risk)",
)
-
-
+
+
def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
- if config.option.showfixtures:
- showfixtures(config)
- return 0
- if config.option.show_fixtures_per_test:
- show_fixtures_per_test(config)
- return 0
+ if config.option.showfixtures:
+ showfixtures(config)
+ return 0
+ if config.option.show_fixtures_per_test:
+ show_fixtures_per_test(config)
+ return 0
return None
-
-
+
+
def pytest_generate_tests(metafunc: "Metafunc") -> None:
- for marker in metafunc.definition.iter_markers(name="parametrize"):
+ for marker in metafunc.definition.iter_markers(name="parametrize"):
# TODO: Fix this type-ignore (overlapping kwargs).
metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker) # type: ignore[misc]
-
-
+
+
def pytest_configure(config: Config) -> None:
- config.addinivalue_line(
- "markers",
- "parametrize(argnames, argvalues): call a test function multiple "
- "times passing in different arguments in turn. argvalues generally "
- "needs to be a list of values if argnames specifies only one name "
- "or a list of tuples of values if argnames specifies multiple names. "
- "Example: @parametrize('arg1', [1,2]) would lead to two calls of the "
- "decorated test function, one with arg1=1 and another with arg1=2."
+ config.addinivalue_line(
+ "markers",
+ "parametrize(argnames, argvalues): call a test function multiple "
+ "times passing in different arguments in turn. argvalues generally "
+ "needs to be a list of values if argnames specifies only one name "
+ "or a list of tuples of values if argnames specifies multiple names. "
+ "Example: @parametrize('arg1', [1,2]) would lead to two calls of the "
+ "decorated test function, one with arg1=1 and another with arg1=2."
"see https://docs.pytest.org/en/stable/parametrize.html for more info "
- "and examples.",
- )
- config.addinivalue_line(
- "markers",
- "usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
- "all of the specified fixtures. see "
+ "and examples.",
+ )
+ config.addinivalue_line(
+ "markers",
+ "usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
+ "all of the specified fixtures. see "
"https://docs.pytest.org/en/stable/fixture.html#usefixtures ",
- )
-
-
+ )
+
+
def async_warn_and_skip(nodeid: str) -> None:
msg = "async def functions are not natively supported and have been skipped.\n"
msg += (
@@ -173,9 +173,9 @@ def async_warn_and_skip(nodeid: str) -> None:
skip(msg="async def function and no async plugin installed (see warnings)")
-@hookimpl(trylast=True)
+@hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
- testfunction = pyfuncitem.obj
+ testfunction = pyfuncitem.obj
if is_async_function(testfunction):
async_warn_and_skip(pyfuncitem.nodeid)
funcargs = pyfuncitem.funcargs
@@ -183,76 +183,76 @@ def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
result = testfunction(**testargs)
if hasattr(result, "__await__") or hasattr(result, "__aiter__"):
async_warn_and_skip(pyfuncitem.nodeid)
- return True
-
-
+ return True
+
+
def pytest_collect_file(
path: py.path.local, parent: nodes.Collector
) -> Optional["Module"]:
- ext = path.ext
- if ext == ".py":
- if not parent.session.isinitpath(path):
- if not path_matches_patterns(
- path, parent.config.getini("python_files") + ["__init__.py"]
- ):
+ ext = path.ext
+ if ext == ".py":
+ if not parent.session.isinitpath(path):
+ if not path_matches_patterns(
+ path, parent.config.getini("python_files") + ["__init__.py"]
+ ):
return None
- ihook = parent.session.gethookproxy(path)
+ ihook = parent.session.gethookproxy(path)
module: Module = ihook.pytest_pycollect_makemodule(path=path, parent=parent)
return module
return None
-
-
+
+
def path_matches_patterns(path: py.path.local, patterns: Iterable[str]) -> bool:
"""Return whether path matches any of the patterns in the list of globs given."""
- return any(path.fnmatch(pattern) for pattern in patterns)
-
-
+ return any(path.fnmatch(pattern) for pattern in patterns)
+
+
def pytest_pycollect_makemodule(path: py.path.local, parent) -> "Module":
- if path.basename == "__init__.py":
+ if path.basename == "__init__.py":
pkg: Package = Package.from_parent(parent, fspath=path)
return pkg
mod: Module = Module.from_parent(parent, fspath=path)
return mod
-
-
+
+
@hookimpl(trylast=True)
def pytest_pycollect_makeitem(collector: "PyCollector", name: str, obj: object):
# Nothing was collected elsewhere, let's do it here.
- if safe_isclass(obj):
- if collector.istestclass(obj, name):
+ if safe_isclass(obj):
+ if collector.istestclass(obj, name):
return Class.from_parent(collector, name=name, obj=obj)
- elif collector.istestfunction(obj, name):
+ elif collector.istestfunction(obj, name):
# mock seems to store unbound methods (issue473), normalize it.
- obj = getattr(obj, "__func__", obj)
- # We need to try and unwrap the function if it's a functools.partial
+ obj = getattr(obj, "__func__", obj)
+ # We need to try and unwrap the function if it's a functools.partial
# or a functools.wrapped.
# We mustn't if it's been wrapped with mock.patch (python 2 only).
if not (inspect.isfunction(obj) or inspect.isfunction(get_real_func(obj))):
- filename, lineno = getfslineno(obj)
- warnings.warn_explicit(
+ filename, lineno = getfslineno(obj)
+ warnings.warn_explicit(
message=PytestCollectionWarning(
- "cannot collect %r because it is not a function." % name
- ),
- category=None,
- filename=str(filename),
- lineno=lineno + 1,
- )
- elif getattr(obj, "__test__", True):
- if is_generator(obj):
+ "cannot collect %r because it is not a function." % name
+ ),
+ category=None,
+ filename=str(filename),
+ lineno=lineno + 1,
+ )
+ elif getattr(obj, "__test__", True):
+ if is_generator(obj):
res = Function.from_parent(collector, name=name)
reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format(
name=name
)
res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
res.warn(PytestCollectionWarning(reason))
- else:
- res = list(collector._genfunctions(name, obj))
+ else:
+ res = list(collector._genfunctions(name, obj))
return res
-
-
+
+
class PyobjMixin:
- _ALLOW_MARKERS = True
-
+ _ALLOW_MARKERS = True
+
# Function and attributes that the mixin needs (for type-checking only).
if TYPE_CHECKING:
name: str = ""
@@ -294,55 +294,55 @@ class PyobjMixin:
if self._ALLOW_MARKERS:
self.own_markers.extend(get_unpacked_marks(self.obj))
return obj
-
+
@obj.setter
def obj(self, value):
self._obj = value
-
- def _getobj(self):
+
+ def _getobj(self):
"""Get the underlying Python object. May be overwritten by subclasses."""
# TODO: Improve the type of `parent` such that assert/ignore aren't needed.
assert self.parent is not None
obj = self.parent.obj # type: ignore[attr-defined]
return getattr(obj, self.name)
-
+
def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> str:
"""Return Python path relative to the containing module."""
- chain = self.listchain()
- chain.reverse()
- parts = []
- for node in chain:
- if isinstance(node, Instance):
- continue
- name = node.name
- if isinstance(node, Module):
- name = os.path.splitext(name)[0]
- if stopatmodule:
- if includemodule:
- parts.append(name)
- break
- parts.append(name)
- parts.reverse()
+ chain = self.listchain()
+ chain.reverse()
+ parts = []
+ for node in chain:
+ if isinstance(node, Instance):
+ continue
+ name = node.name
+ if isinstance(node, Module):
+ name = os.path.splitext(name)[0]
+ if stopatmodule:
+ if includemodule:
+ parts.append(name)
+ break
+ parts.append(name)
+ parts.reverse()
return ".".join(parts)
-
+
def reportinfo(self) -> Tuple[Union[py.path.local, str], int, str]:
- # XXX caching?
- obj = self.obj
- compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None)
- if isinstance(compat_co_firstlineno, int):
- # nose compatibility
+ # XXX caching?
+ obj = self.obj
+ compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None)
+ if isinstance(compat_co_firstlineno, int):
+ # nose compatibility
file_path = sys.modules[obj.__module__].__file__
if file_path.endswith(".pyc"):
file_path = file_path[:-1]
fspath: Union[py.path.local, str] = file_path
- lineno = compat_co_firstlineno
- else:
- fspath, lineno = getfslineno(obj)
- modpath = self.getmodpath()
- assert isinstance(lineno, int)
- return fspath, lineno, modpath
-
-
+ lineno = compat_co_firstlineno
+ else:
+ fspath, lineno = getfslineno(obj)
+ modpath = self.getmodpath()
+ assert isinstance(lineno, int)
+ return fspath, lineno, modpath
+
+
# As an optimization, these builtin attribute names are pre-ignored when
# iterating over an object during collection -- the pytest_pycollect_makeitem
# hook is not called for them.
@@ -363,78 +363,78 @@ del _EmptyClass
# fmt: on
-class PyCollector(PyobjMixin, nodes.Collector):
+class PyCollector(PyobjMixin, nodes.Collector):
def funcnamefilter(self, name: str) -> bool:
- return self._matches_prefix_or_glob_option("python_functions", name)
-
+ return self._matches_prefix_or_glob_option("python_functions", name)
+
def isnosetest(self, obj: object) -> bool:
"""Look for the __test__ attribute, which is applied by the
@nose.tools.istest decorator.
- """
- # We explicitly check for "is True" here to not mistakenly treat
- # classes with a custom __getattr__ returning something truthy (like a
- # function) as test classes.
- return safe_getattr(obj, "__test__", False) is True
-
+ """
+ # We explicitly check for "is True" here to not mistakenly treat
+ # classes with a custom __getattr__ returning something truthy (like a
+ # function) as test classes.
+ return safe_getattr(obj, "__test__", False) is True
+
def classnamefilter(self, name: str) -> bool:
- return self._matches_prefix_or_glob_option("python_classes", name)
-
+ return self._matches_prefix_or_glob_option("python_classes", name)
+
def istestfunction(self, obj: object, name: str) -> bool:
- if self.funcnamefilter(name) or self.isnosetest(obj):
- if isinstance(obj, staticmethod):
+ if self.funcnamefilter(name) or self.isnosetest(obj):
+ if isinstance(obj, staticmethod):
# staticmethods need to be unwrapped.
- obj = safe_getattr(obj, "__func__", False)
- return (
- safe_getattr(obj, "__call__", False)
- and fixtures.getfixturemarker(obj) is None
- )
- else:
- return False
-
+ obj = safe_getattr(obj, "__func__", False)
+ return (
+ safe_getattr(obj, "__call__", False)
+ and fixtures.getfixturemarker(obj) is None
+ )
+ else:
+ return False
+
def istestclass(self, obj: object, name: str) -> bool:
- return self.classnamefilter(name) or self.isnosetest(obj)
-
+ return self.classnamefilter(name) or self.isnosetest(obj)
+
def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool:
"""Check if the given name matches the prefix or glob-pattern defined
in ini configuration."""
- for option in self.config.getini(option_name):
- if name.startswith(option):
- return True
+ for option in self.config.getini(option_name):
+ if name.startswith(option):
+ return True
# Check that name looks like a glob-string before calling fnmatch
- # because this is called for every name in each collected module,
+ # because this is called for every name in each collected module,
# and fnmatch is somewhat expensive to call.
- elif ("*" in option or "?" in option or "[" in option) and fnmatch.fnmatch(
- name, option
- ):
- return True
- return False
-
+ elif ("*" in option or "?" in option or "[" in option) and fnmatch.fnmatch(
+ name, option
+ ):
+ return True
+ return False
+
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
- if not getattr(self.obj, "__test__", True):
- return []
-
- # NB. we avoid random getattrs and peek in the __dict__ instead
- # (XXX originally introduced from a PyPy need, still true?)
- dicts = [getattr(self.obj, "__dict__", {})]
+ if not getattr(self.obj, "__test__", True):
+ return []
+
+ # NB. we avoid random getattrs and peek in the __dict__ instead
+ # (XXX originally introduced from a PyPy need, still true?)
+ dicts = [getattr(self.obj, "__dict__", {})]
for basecls in self.obj.__class__.__mro__:
- dicts.append(basecls.__dict__)
+ dicts.append(basecls.__dict__)
seen: Set[str] = set()
values: List[Union[nodes.Item, nodes.Collector]] = []
ihook = self.ihook
- for dic in dicts:
+ for dic in dicts:
# Note: seems like the dict can change during iteration -
# be careful not to remove the list() without consideration.
- for name, obj in list(dic.items()):
+ for name, obj in list(dic.items()):
if name in IGNORED_ATTRIBUTES:
continue
- if name in seen:
- continue
+ if name in seen:
+ continue
seen.add(name)
res = ihook.pytest_pycollect_makeitem(
collector=self, name=name, obj=obj
)
- if res is None:
- continue
+ if res is None:
+ continue
elif isinstance(res, list):
values.extend(res)
else:
@@ -445,66 +445,66 @@ class PyCollector(PyobjMixin, nodes.Collector):
return (str(fspath), lineno)
values.sort(key=sort_key)
- return values
-
+ return values
+
def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
modulecol = self.getparent(Module)
assert modulecol is not None
module = modulecol.obj
- clscol = self.getparent(Class)
- cls = clscol and clscol.obj or None
- fm = self.session._fixturemanager
-
+ clscol = self.getparent(Class)
+ cls = clscol and clscol.obj or None
+ fm = self.session._fixturemanager
+
definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj)
fixtureinfo = definition._fixtureinfo
-
- metafunc = Metafunc(
- definition, fixtureinfo, self.config, cls=cls, module=module
- )
- methods = []
- if hasattr(module, "pytest_generate_tests"):
- methods.append(module.pytest_generate_tests)
+
+ metafunc = Metafunc(
+ definition, fixtureinfo, self.config, cls=cls, module=module
+ )
+ methods = []
+ if hasattr(module, "pytest_generate_tests"):
+ methods.append(module.pytest_generate_tests)
if cls is not None and hasattr(cls, "pytest_generate_tests"):
- methods.append(cls().pytest_generate_tests)
-
+ methods.append(cls().pytest_generate_tests)
+
self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
- if not metafunc._calls:
+ if not metafunc._calls:
yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
- else:
+ else:
# Add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs.
- fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
-
+ fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
+
# Add_funcarg_pseudo_fixture_def may have shadowed some fixtures
- # with direct parametrization, so make sure we update what the
- # function really needs.
- fixtureinfo.prune_dependency_tree()
-
- for callspec in metafunc._calls:
+ # with direct parametrization, so make sure we update what the
+ # function really needs.
+ fixtureinfo.prune_dependency_tree()
+
+ for callspec in metafunc._calls:
subname = f"{name}[{callspec.id}]"
yield Function.from_parent(
self,
- name=subname,
- callspec=callspec,
- callobj=funcobj,
- fixtureinfo=fixtureinfo,
- keywords={callspec.id: True},
- originalname=name,
- )
-
-
-class Module(nodes.File, PyCollector):
+ name=subname,
+ callspec=callspec,
+ callobj=funcobj,
+ fixtureinfo=fixtureinfo,
+ keywords={callspec.id: True},
+ originalname=name,
+ )
+
+
+class Module(nodes.File, PyCollector):
"""Collector for test classes and functions."""
-
- def _getobj(self):
- return self._importtestmodule()
-
+
+ def _getobj(self):
+ return self._importtestmodule()
+
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
self._inject_setup_module_fixture()
self._inject_setup_function_fixture()
- self.session._fixturemanager.parsefactories(self)
+ self.session._fixturemanager.parsefactories(self)
return super().collect()
-
+
def _inject_setup_module_fixture(self) -> None:
"""Inject a hidden autouse, module scoped fixture into the collected module object
that invokes setUpModule/tearDownModule if either or both are available.
@@ -571,55 +571,55 @@ class Module(nodes.File, PyCollector):
self.obj.__pytest_setup_function = xunit_setup_function_fixture
- def _importtestmodule(self):
+ def _importtestmodule(self):
# We assume we are only called once per module.
- importmode = self.config.getoption("--import-mode")
- try:
+ importmode = self.config.getoption("--import-mode")
+ try:
mod = import_path(self.fspath, mode=importmode)
except SyntaxError as e:
- raise self.CollectError(
+ raise self.CollectError(
ExceptionInfo.from_current().getrepr(style="short")
) from e
except ImportPathMismatchError as e:
raise self.CollectError(
- "import file mismatch:\n"
- "imported module %r has this __file__ attribute:\n"
- " %s\n"
- "which is not the same as the test file we want to collect:\n"
- " %s\n"
- "HINT: remove __pycache__ / .pyc files and/or use a "
- "unique basename for your test file modules" % e.args
+ "import file mismatch:\n"
+ "imported module %r has this __file__ attribute:\n"
+ " %s\n"
+ "which is not the same as the test file we want to collect:\n"
+ " %s\n"
+ "HINT: remove __pycache__ / .pyc files and/or use a "
+ "unique basename for your test file modules" % e.args
) from e
except ImportError as e:
exc_info = ExceptionInfo.from_current()
- if self.config.getoption("verbose") < 2:
- exc_info.traceback = exc_info.traceback.filter(filter_traceback)
- exc_repr = (
- exc_info.getrepr(style="short")
- if exc_info.traceback
- else exc_info.exconly()
- )
+ if self.config.getoption("verbose") < 2:
+ exc_info.traceback = exc_info.traceback.filter(filter_traceback)
+ exc_repr = (
+ exc_info.getrepr(style="short")
+ if exc_info.traceback
+ else exc_info.exconly()
+ )
formatted_tb = str(exc_repr)
- raise self.CollectError(
- "ImportError while importing test module '{fspath}'.\n"
- "Hint: make sure your test modules/packages have valid Python names.\n"
- "Traceback:\n"
- "{traceback}".format(fspath=self.fspath, traceback=formatted_tb)
+ raise self.CollectError(
+ "ImportError while importing test module '{fspath}'.\n"
+ "Hint: make sure your test modules/packages have valid Python names.\n"
+ "Traceback:\n"
+ "{traceback}".format(fspath=self.fspath, traceback=formatted_tb)
) from e
except skip.Exception as e:
- if e.allow_module_level:
- raise
- raise self.CollectError(
- "Using pytest.skip outside of a test is not allowed. "
- "To decorate a test function, use the @pytest.mark.skip "
- "or @pytest.mark.skipif decorators instead, and to skip a "
- "module use `pytestmark = pytest.mark.{skip,skipif}."
+ if e.allow_module_level:
+ raise
+ raise self.CollectError(
+ "Using pytest.skip outside of a test is not allowed. "
+ "To decorate a test function, use the @pytest.mark.skip "
+ "or @pytest.mark.skipif decorators instead, and to skip a "
+ "module use `pytestmark = pytest.mark.{skip,skipif}."
) from e
- self.config.pluginmanager.consider_module(mod)
- return mod
-
-
-class Package(Module):
+ self.config.pluginmanager.consider_module(mod)
+ return mod
+
+
+class Package(Module):
def __init__(
self,
fspath: py.path.local,
@@ -631,10 +631,10 @@ class Package(Module):
) -> None:
# NOTE: Could be just the following, but kept as-is for compat.
# nodes.FSCollector.__init__(self, fspath, parent=parent)
- session = parent.session
- nodes.FSCollector.__init__(
- self, fspath, parent=parent, config=config, session=session, nodeid=nodeid
- )
+ session = parent.session
+ nodes.FSCollector.__init__(
+ self, fspath, parent=parent, config=config, session=session, nodeid=nodeid
+ )
self.name = os.path.basename(str(fspath.dirname))
def setup(self) -> None:
@@ -656,11 +656,11 @@ class Package(Module):
def gethookproxy(self, fspath: py.path.local):
warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
return self.session.gethookproxy(fspath)
-
+
def isinitpath(self, path: py.path.local) -> bool:
warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
return self.session.isinitpath(path)
-
+
def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
if direntry.name == "__pycache__":
return False
@@ -698,37 +698,37 @@ class Package(Module):
return ihook.pytest_collect_file(path=path, parent=self) # type: ignore[no-any-return]
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
- this_path = self.fspath.dirpath()
- init_module = this_path.join("__init__.py")
- if init_module.check(file=1) and path_matches_patterns(
- init_module, self.config.getini("python_files")
- ):
+ this_path = self.fspath.dirpath()
+ init_module = this_path.join("__init__.py")
+ if init_module.check(file=1) and path_matches_patterns(
+ init_module, self.config.getini("python_files")
+ ):
yield Module.from_parent(self, fspath=init_module)
pkg_prefixes: Set[py.path.local] = set()
for direntry in visit(str(this_path), recurse=self._recurse):
path = py.path.local(direntry.path)
- # We will visit our own __init__.py file, in which case we skip it.
+ # We will visit our own __init__.py file, in which case we skip it.
if direntry.is_file():
if direntry.name == "__init__.py" and path.dirpath() == this_path:
- continue
-
+ continue
+
parts_ = parts(direntry.path)
- if any(
+ if any(
str(pkg_prefix) in parts_ and pkg_prefix.join("__init__.py") != path
- for pkg_prefix in pkg_prefixes
- ):
- continue
-
+ for pkg_prefix in pkg_prefixes
+ ):
+ continue
+
if direntry.is_file():
yield from self._collectfile(path)
elif not direntry.is_dir():
# Broken symlink or invalid/missing file.
continue
elif path.join("__init__.py").check(file=1):
- pkg_prefixes.add(path)
-
-
+ pkg_prefixes.add(path)
+
+
def _call_with_optional_argument(func, arg) -> None:
"""Call the given function with the given argument if func accepts one argument, otherwise
calls func without arguments."""
@@ -742,55 +742,55 @@ def _call_with_optional_argument(func, arg) -> None:
def _get_first_non_fixture_func(obj: object, names: Iterable[str]):
- """Return the attribute from the given object to be used as a setup/teardown
+ """Return the attribute from the given object to be used as a setup/teardown
xunit-style function, but only if not marked as a fixture to avoid calling it twice."""
for name in names:
meth = getattr(obj, name, None)
if meth is not None and fixtures.getfixturemarker(meth) is None:
return meth
-
-
-class Class(PyCollector):
+
+
+class Class(PyCollector):
"""Collector for test methods."""
-
+
@classmethod
def from_parent(cls, parent, *, name, obj=None):
"""The public constructor."""
return super().from_parent(name=name, parent=parent)
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
- if not safe_getattr(self.obj, "__test__", True):
- return []
- if hasinit(self.obj):
+ if not safe_getattr(self.obj, "__test__", True):
+ return []
+ if hasinit(self.obj):
assert self.parent is not None
- self.warn(
+ self.warn(
PytestCollectionWarning(
- "cannot collect test class %r because it has a "
+ "cannot collect test class %r because it has a "
"__init__ constructor (from: %s)"
% (self.obj.__name__, self.parent.nodeid)
- )
- )
- return []
- elif hasnew(self.obj):
+ )
+ )
+ return []
+ elif hasnew(self.obj):
assert self.parent is not None
- self.warn(
+ self.warn(
PytestCollectionWarning(
- "cannot collect test class %r because it has a "
+ "cannot collect test class %r because it has a "
"__new__ constructor (from: %s)"
% (self.obj.__name__, self.parent.nodeid)
- )
- )
- return []
-
+ )
+ )
+ return []
+
self._inject_setup_class_fixture()
self._inject_setup_method_fixture()
-
+
return [Instance.from_parent(self, name="()")]
-
+
def _inject_setup_class_fixture(self) -> None:
"""Inject a hidden autouse, class scoped fixture into the collected class object
that invokes setup_class/teardown_class if either or both are available.
-
+
Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
other fixtures (#517).
"""
@@ -847,45 +847,45 @@ class Class(PyCollector):
self.obj.__pytest_setup_method = xunit_setup_method_fixture
-class Instance(PyCollector):
- _ALLOW_MARKERS = False # hack, destroy later
+class Instance(PyCollector):
+ _ALLOW_MARKERS = False # hack, destroy later
# Instances share the object with their parents in a way
- # that duplicates markers instances if not taken out
+ # that duplicates markers instances if not taken out
# can be removed at node structure reorganization time.
-
- def _getobj(self):
+
+ def _getobj(self):
# TODO: Improve the type of `parent` such that assert/ignore aren't needed.
assert self.parent is not None
obj = self.parent.obj # type: ignore[attr-defined]
return obj()
-
+
def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
- self.session._fixturemanager.parsefactories(self)
+ self.session._fixturemanager.parsefactories(self)
return super().collect()
-
- def newinstance(self):
- self.obj = self._getobj()
- return self.obj
-
-
+
+ def newinstance(self):
+ self.obj = self._getobj()
+ return self.obj
+
+
def hasinit(obj: object) -> bool:
init: object = getattr(obj, "__init__", None)
- if init:
- return init != object.__init__
+ if init:
+ return init != object.__init__
return False
-
-
+
+
def hasnew(obj: object) -> bool:
new: object = getattr(obj, "__new__", None)
- if new:
- return new != object.__new__
+ if new:
+ return new != object.__new__
return False
-
-
+
+
@final
class CallSpec2:
def __init__(self, metafunc: "Metafunc") -> None:
- self.metafunc = metafunc
+ self.metafunc = metafunc
self.funcargs: Dict[str, object] = {}
self._idlist: List[str] = []
self.params: Dict[str, object] = {}
@@ -893,31 +893,31 @@ class CallSpec2:
self._arg2scopenum: Dict[str, int] = {}
self.marks: List[Mark] = []
self.indices: Dict[str, int] = {}
-
+
def copy(self) -> "CallSpec2":
- cs = CallSpec2(self.metafunc)
- cs.funcargs.update(self.funcargs)
- cs.params.update(self.params)
- cs.marks.extend(self.marks)
- cs.indices.update(self.indices)
- cs._arg2scopenum.update(self._arg2scopenum)
- cs._idlist = list(self._idlist)
- return cs
-
+ cs = CallSpec2(self.metafunc)
+ cs.funcargs.update(self.funcargs)
+ cs.params.update(self.params)
+ cs.marks.extend(self.marks)
+ cs.indices.update(self.indices)
+ cs._arg2scopenum.update(self._arg2scopenum)
+ cs._idlist = list(self._idlist)
+ return cs
+
def _checkargnotcontained(self, arg: str) -> None:
- if arg in self.params or arg in self.funcargs:
+ if arg in self.params or arg in self.funcargs:
raise ValueError(f"duplicate {arg!r}")
-
+
def getparam(self, name: str) -> object:
- try:
- return self.params[name]
+ try:
+ return self.params[name]
except KeyError as e:
raise ValueError(name) from e
-
- @property
+
+ @property
def id(self) -> str:
return "-".join(map(str, self._idlist))
-
+
def setmulti2(
self,
valtypes: Mapping[str, "Literal['params', 'funcargs']"],
@@ -928,30 +928,30 @@ class CallSpec2:
scopenum: int,
param_index: int,
) -> None:
- for arg, val in zip(argnames, valset):
- self._checkargnotcontained(arg)
- valtype_for_arg = valtypes[arg]
+ for arg, val in zip(argnames, valset):
+ self._checkargnotcontained(arg)
+ valtype_for_arg = valtypes[arg]
if valtype_for_arg == "params":
self.params[arg] = val
elif valtype_for_arg == "funcargs":
self.funcargs[arg] = val
else: # pragma: no cover
assert False, f"Unhandled valtype for arg: {valtype_for_arg}"
- self.indices[arg] = param_index
- self._arg2scopenum[arg] = scopenum
- self._idlist.append(id)
- self.marks.extend(normalize_mark_list(marks))
-
-
+ self.indices[arg] = param_index
+ self._arg2scopenum[arg] = scopenum
+ self._idlist.append(id)
+ self.marks.extend(normalize_mark_list(marks))
+
+
@final
class Metafunc:
"""Objects passed to the :func:`pytest_generate_tests <_pytest.hookspec.pytest_generate_tests>` hook.
- They help to inspect a test function and to generate tests according to
- test configuration or values specified in the class or module where a
- test function is defined.
- """
-
+ They help to inspect a test function and to generate tests according to
+ test configuration or values specified in the class or module where a
+ test function is defined.
+ """
+
def __init__(
self,
definition: "FunctionDefinition",
@@ -961,26 +961,26 @@ class Metafunc:
module=None,
) -> None:
#: Access to the underlying :class:`_pytest.python.FunctionDefinition`.
- self.definition = definition
-
+ self.definition = definition
+
#: Access to the :class:`_pytest.config.Config` object for the test session.
- self.config = config
-
+ self.config = config
+
#: The module object where the test function is defined in.
- self.module = module
-
+ self.module = module
+
#: Underlying Python test function.
- self.function = definition.obj
-
+ self.function = definition.obj
+
#: Set of fixture names required by the test function.
- self.fixturenames = fixtureinfo.names_closure
-
+ self.fixturenames = fixtureinfo.names_closure
+
#: Class object where the test function is defined in or ``None``.
- self.cls = cls
-
+ self.cls = cls
+
self._calls: List[CallSpec2] = []
- self._arg2fixturedefs = fixtureinfo.name2fixturedefs
-
+ self._arg2fixturedefs = fixtureinfo.name2fixturedefs
+
def parametrize(
self,
argnames: Union[str, List[str], Tuple[str, ...]],
@@ -997,18 +997,18 @@ class Metafunc:
_param_mark: Optional[Mark] = None,
) -> None:
"""Add new invocations to the underlying test function using the list
- of argvalues for the given argnames. Parametrization is performed
- during the collection phase. If you need to setup expensive resources
- see about setting indirect to do it rather at test setup time.
-
+ of argvalues for the given argnames. Parametrization is performed
+ during the collection phase. If you need to setup expensive resources
+ see about setting indirect to do it rather at test setup time.
+
:param argnames:
A comma-separated string denoting one or more argument names, or
a list/tuple of argument strings.
-
+
:param argvalues:
The list of argvalues determines how often a test is invoked with
different argument values.
-
+
If only one argname was specified argvalues is a list of values.
If N argnames were specified, argvalues must be a list of
N-tuples, where each tuple-element specifies a value for its
@@ -1018,10 +1018,10 @@ class Metafunc:
A list of arguments' names (subset of argnames) or a boolean.
If True the list contains all names from the argnames. Each
argvalue corresponding to an argname in this list will
- be passed as request.param to its respective argname fixture
- function so that it can perform more expensive setups during the
- setup phase of a test rather than at collection time.
-
+ be passed as request.param to its respective argname fixture
+ function so that it can perform more expensive setups during the
+ setup phase of a test rather than at collection time.
+
:param ids:
Sequence of (or generator for) ids for ``argvalues``,
or a callable to return part of the id for each argvalue.
@@ -1039,39 +1039,39 @@ class Metafunc:
This is useful to provide more specific ids for certain items, e.g.
dates. Returning ``None`` will use an auto-generated id.
- If no ids are provided they will be generated automatically from
- the argvalues.
-
+ If no ids are provided they will be generated automatically from
+ the argvalues.
+
:param scope:
If specified it denotes the scope of the parameters.
- The scope is used for grouping tests by parameter instances.
- It will also override any fixture-function defined scope, allowing
- to set a dynamic scope using test context or configuration.
- """
- from _pytest.fixtures import scope2index
-
- argnames, parameters = ParameterSet._for_parametrize(
- argnames,
- argvalues,
- self.function,
- self.config,
+ The scope is used for grouping tests by parameter instances.
+ It will also override any fixture-function defined scope, allowing
+ to set a dynamic scope using test context or configuration.
+ """
+ from _pytest.fixtures import scope2index
+
+ argnames, parameters = ParameterSet._for_parametrize(
+ argnames,
+ argvalues,
+ self.function,
+ self.config,
nodeid=self.definition.nodeid,
- )
- del argvalues
-
+ )
+ del argvalues
+
if "request" in argnames:
fail(
"'request' is a reserved name and cannot be used in @pytest.mark.parametrize",
pytrace=False,
)
- if scope is None:
- scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
-
- self._validate_if_using_arg_names(argnames, indirect)
-
- arg_values_types = self._resolve_arg_value_types(argnames, indirect)
-
+ if scope is None:
+ scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, 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.
if _param_mark and _param_mark._param_ids_from:
generated_ids = _param_mark._param_ids_from._param_ids_generated
@@ -1081,34 +1081,34 @@ class Metafunc:
ids = self._resolve_arg_ids(
argnames, ids, parameters, nodeid=self.definition.nodeid
)
-
+
# Store used (possibly generated) ids with parametrize Marks.
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)
- scopenum = scope2index(
+ scopenum = scope2index(
scope, descr=f"parametrize() call in {self.function.__name__}"
- )
-
+ )
+
# 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.
- newcalls = []
- for callspec in self._calls or [CallSpec2(self)]:
- for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)):
- newcallspec = callspec.copy()
- newcallspec.setmulti2(
- arg_values_types,
- argnames,
- param_set.values,
- param_id,
- param_set.marks,
- scopenum,
- param_index,
- )
- newcalls.append(newcallspec)
- self._calls = newcalls
-
+ newcalls = []
+ for callspec in self._calls or [CallSpec2(self)]:
+ for param_index, (param_id, param_set) in enumerate(zip(ids, parameters)):
+ newcallspec = callspec.copy()
+ newcallspec.setmulti2(
+ arg_values_types,
+ argnames,
+ param_set.values,
+ param_id,
+ param_set.marks,
+ scopenum,
+ param_index,
+ )
+ newcalls.append(newcallspec)
+ self._calls = newcalls
+
def _resolve_arg_ids(
self,
argnames: Sequence[str],
@@ -1122,26 +1122,26 @@ class Metafunc:
nodeid: str,
) -> List[str]:
"""Resolve the actual ids for the given argnames, based on the ``ids`` parameter given
- to ``parametrize``.
-
+ to ``parametrize``.
+
:param List[str] argnames: List of argument names passed to ``parametrize()``.
:param ids: The ids parameter of the parametrized call (see docs).
:param List[ParameterSet] parameters: The list of parameter values, same size as ``argnames``.
:param str str: The nodeid of the item that generated this parametrized call.
- :rtype: List[str]
+ :rtype: List[str]
:returns: The list of ids for each argname given.
- """
+ """
if ids is None:
idfn = None
ids_ = None
elif callable(ids):
- idfn = ids
+ idfn = ids
ids_ = None
else:
idfn = None
ids_ = self._validate_ids(ids, parameters, self.function.__name__)
return idmaker(argnames, parameters, idfn, ids_, self.config, nodeid=nodeid)
-
+
def _validate_ids(
self,
ids: Iterable[Union[None, str, float, int, bool]],
@@ -1185,29 +1185,29 @@ class Metafunc:
"""Resolve if each parametrized argument must be considered a
parameter to a fixture or a "funcarg" to the function, based on the
``indirect`` parameter of the parametrized() call.
-
+
:param List[str] argnames: List of argument names passed to ``parametrize()``.
:param indirect: Same as the ``indirect`` parameter of ``parametrize()``.
- :rtype: Dict[str, str]
- A dict mapping each arg name to either:
- * "params" if the argname should be the parameter of a fixture of the same name.
- * "funcargs" if the argname should be a parameter to the parametrized test function.
- """
+ :rtype: Dict[str, str]
+ A dict mapping each arg name to either:
+ * "params" if the argname should be the parameter of a fixture of the same name.
+ * "funcargs" if the argname should be a parameter to the parametrized test function.
+ """
if isinstance(indirect, bool):
valtypes: Dict[str, Literal["params", "funcargs"]] = dict.fromkeys(
argnames, "params" if indirect else "funcargs"
)
elif isinstance(indirect, Sequence):
- valtypes = dict.fromkeys(argnames, "funcargs")
- for arg in indirect:
- if arg not in argnames:
- fail(
- "In {}: indirect fixture '{}' doesn't exist".format(
- self.function.__name__, arg
- ),
- pytrace=False,
- )
- valtypes[arg] = "params"
+ valtypes = dict.fromkeys(argnames, "funcargs")
+ for arg in indirect:
+ if arg not in argnames:
+ fail(
+ "In {}: indirect fixture '{}' doesn't exist".format(
+ self.function.__name__, arg
+ ),
+ pytrace=False,
+ )
+ valtypes[arg] = "params"
else:
fail(
"In {func}: expected Sequence or boolean for indirect, got {type}".format(
@@ -1215,74 +1215,74 @@ class Metafunc:
),
pytrace=False,
)
- return valtypes
-
+ return valtypes
+
def _validate_if_using_arg_names(
self, argnames: Sequence[str], indirect: Union[bool, Sequence[str]],
) -> None:
"""Check if all argnames are being used, by default values, or directly/indirectly.
-
+
:param List[str] argnames: List of argument names passed to ``parametrize()``.
:param indirect: Same as the ``indirect`` parameter of ``parametrize()``.
:raises ValueError: If validation fails.
- """
- default_arg_names = set(get_default_arg_names(self.function))
- func_name = self.function.__name__
- for arg in argnames:
- if arg not in self.fixturenames:
- if arg in default_arg_names:
- fail(
- "In {}: function already takes an argument '{}' with a default value".format(
- func_name, arg
- ),
- pytrace=False,
- )
- else:
+ """
+ default_arg_names = set(get_default_arg_names(self.function))
+ func_name = self.function.__name__
+ for arg in argnames:
+ if arg not in self.fixturenames:
+ if arg in default_arg_names:
+ fail(
+ "In {}: function already takes an argument '{}' with a default value".format(
+ func_name, arg
+ ),
+ pytrace=False,
+ )
+ else:
if isinstance(indirect, Sequence):
- name = "fixture" if arg in indirect else "argument"
- else:
- name = "fixture" if indirect else "argument"
- fail(
+ name = "fixture" if arg in indirect else "argument"
+ else:
+ name = "fixture" if indirect else "argument"
+ fail(
f"In {func_name}: function uses no {name} '{arg}'",
- pytrace=False,
- )
-
-
+ pytrace=False,
+ )
+
+
def _find_parametrized_scope(
argnames: Sequence[str],
arg2fixturedefs: Mapping[str, Sequence[fixtures.FixtureDef[object]]],
indirect: Union[bool, Sequence[str]],
) -> "fixtures._Scope":
- """Find the most appropriate scope for a parametrized call based on its arguments.
-
- When there's at least one direct argument, always use "function" scope.
-
- When a test function is parametrized and all its arguments are indirect
- (e.g. fixtures), return the most narrow scope based on the fixtures used.
-
- Related to issue #1832, based on code posted by @Kingdread.
- """
+ """Find the most appropriate scope for a parametrized call based on its arguments.
+
+ When there's at least one direct argument, always use "function" scope.
+
+ When a test function is parametrized and all its arguments are indirect
+ (e.g. fixtures), return the most narrow scope based on the fixtures used.
+
+ Related to issue #1832, based on code posted by @Kingdread.
+ """
if isinstance(indirect, Sequence):
- all_arguments_are_fixtures = len(indirect) == len(argnames)
- else:
- all_arguments_are_fixtures = bool(indirect)
-
- if all_arguments_are_fixtures:
- fixturedefs = arg2fixturedefs or {}
- used_scopes = [
- fixturedef[0].scope
- for name, fixturedef in fixturedefs.items()
- if name in argnames
- ]
- if used_scopes:
+ all_arguments_are_fixtures = len(indirect) == len(argnames)
+ else:
+ all_arguments_are_fixtures = bool(indirect)
+
+ if all_arguments_are_fixtures:
+ fixturedefs = arg2fixturedefs or {}
+ used_scopes = [
+ fixturedef[0].scope
+ for name, fixturedef in fixturedefs.items()
+ if name in argnames
+ ]
+ if used_scopes:
# Takes the most narrow scope from used fixtures.
for scope in reversed(fixtures.scopes):
- if scope in used_scopes:
- return scope
-
- return "function"
-
-
+ if scope in used_scopes:
+ return scope
+
+ return "function"
+
+
def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -> str:
if config is None:
escape_option = False
@@ -1304,41 +1304,41 @@ def _idval(
nodeid: Optional[str],
config: Optional[Config],
) -> str:
- if idfn:
- try:
+ if idfn:
+ try:
generated_id = idfn(val)
if generated_id is not None:
val = generated_id
- except Exception as e:
+ except Exception as e:
prefix = f"{nodeid}: " if nodeid is not None else ""
msg = "error raised while trying to determine id of parameter '{}' at position {}"
msg = prefix + msg.format(argname, idx)
raise ValueError(msg) from e
elif config:
hook_id: Optional[str] = config.hook.pytest_make_parametrize_id(
- config=config, val=val, argname=argname
+ config=config, val=val, argname=argname
)
- if hook_id:
- return hook_id
-
- if isinstance(val, STRING_TYPES):
+ if hook_id:
+ return hook_id
+
+ if isinstance(val, STRING_TYPES):
return _ascii_escaped_by_config(val, config)
elif val is None or isinstance(val, (float, int, bool)):
- return str(val)
- elif isinstance(val, REGEX_TYPE):
- return ascii_escaped(val.pattern)
+ return str(val)
+ elif isinstance(val, REGEX_TYPE):
+ return ascii_escaped(val.pattern)
elif val is NOTSET:
# Fallback to default. Note that NOTSET is an enum.Enum.
pass
elif isinstance(val, enum.Enum):
- return str(val)
+ return str(val)
elif isinstance(getattr(val, "__name__", None), str):
# Name of a class, function, module, etc.
name: str = getattr(val, "__name__")
return name
- return str(argname) + str(idx)
-
-
+ return str(argname) + str(idx)
+
+
def limit_idval(limit):
import functools
@@ -1375,19 +1375,19 @@ def _idvalset(
nodeid: Optional[str],
config: Optional[Config],
) -> str:
- if parameterset.id is not None:
- return parameterset.id
+ if parameterset.id is not None:
+ return parameterset.id
id = None if ids is None or idx >= len(ids) else ids[idx]
if id is None:
- this_id = [
+ this_id = [
_idval(val, argname, idx, idfn, nodeid=nodeid, config=config)
- for val, argname in zip(parameterset.values, argnames)
- ]
- return "-".join(this_id)
- else:
+ for val, argname in zip(parameterset.values, argnames)
+ ]
+ return "-".join(this_id)
+ else:
return _ascii_escaped_by_config(id, config)
-
-
+
+
def idmaker(
argnames: Iterable[str],
parametersets: Iterable[ParameterSet],
@@ -1400,13 +1400,13 @@ def idmaker(
_idvalset(
valindex, parameterset, argnames, idfn, ids, config=config, nodeid=nodeid
)
- for valindex, parameterset in enumerate(parametersets)
- ]
-
+ for valindex, parameterset in enumerate(parametersets)
+ ]
+
# All IDs must be unique!
unique_ids = set(resolved_ids)
if len(unique_ids) != len(resolved_ids):
-
+
# Record the number of occurrences of each test ID.
test_id_counts = Counter(resolved_ids)
@@ -1422,130 +1422,130 @@ def idmaker(
return resolved_ids
-def show_fixtures_per_test(config):
- from _pytest.main import wrap_session
-
- return wrap_session(config, _show_fixtures_per_test)
-
-
+def show_fixtures_per_test(config):
+ from _pytest.main import wrap_session
+
+ return wrap_session(config, _show_fixtures_per_test)
+
+
def _show_fixtures_per_test(config: Config, session: Session) -> None:
- import _pytest.config
-
- session.perform_collect()
- curdir = py.path.local()
- tw = _pytest.config.create_terminal_writer(config)
- verbose = config.getvalue("verbose")
-
- def get_best_relpath(func):
+ import _pytest.config
+
+ session.perform_collect()
+ curdir = py.path.local()
+ tw = _pytest.config.create_terminal_writer(config)
+ verbose = config.getvalue("verbose")
+
+ def get_best_relpath(func):
loc = getlocation(func, str(curdir))
return curdir.bestrelpath(py.path.local(loc))
-
+
def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None:
- argname = fixture_def.argname
- if verbose <= 0 and argname.startswith("_"):
- return
- if verbose > 0:
- bestrel = get_best_relpath(fixture_def.func)
+ argname = fixture_def.argname
+ if verbose <= 0 and argname.startswith("_"):
+ return
+ if verbose > 0:
+ bestrel = get_best_relpath(fixture_def.func)
funcargspec = f"{argname} -- {bestrel}"
- else:
- funcargspec = argname
- tw.line(funcargspec, green=True)
+ else:
+ funcargspec = argname
+ tw.line(funcargspec, green=True)
fixture_doc = inspect.getdoc(fixture_def.func)
- if fixture_doc:
- write_docstring(tw, fixture_doc)
- else:
- tw.line(" no docstring available", red=True)
-
+ if fixture_doc:
+ write_docstring(tw, fixture_doc)
+ else:
+ tw.line(" no docstring available", red=True)
+
def write_item(item: nodes.Item) -> None:
# Not all items have _fixtureinfo attribute.
info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None)
if info is None or not info.name2fixturedefs:
# This test item does not use any fixtures.
- return
- tw.line()
+ return
+ tw.line()
tw.sep("-", f"fixtures used by {item.name}")
# TODO: Fix this type ignore.
tw.sep("-", "({})".format(get_best_relpath(item.function))) # type: ignore[attr-defined]
# dict key not used in loop but needed for sorting.
- for _, fixturedefs in sorted(info.name2fixturedefs.items()):
- assert fixturedefs is not None
- if not fixturedefs:
- continue
+ for _, fixturedefs in sorted(info.name2fixturedefs.items()):
+ assert fixturedefs is not None
+ if not fixturedefs:
+ continue
# Last item is expected to be the one used by the test item.
- write_fixture(fixturedefs[-1])
-
- for session_item in session.items:
- write_item(session_item)
-
-
+ write_fixture(fixturedefs[-1])
+
+ for session_item in session.items:
+ write_item(session_item)
+
+
def showfixtures(config: Config) -> Union[int, ExitCode]:
- from _pytest.main import wrap_session
-
- return wrap_session(config, _showfixtures_main)
-
-
+ from _pytest.main import wrap_session
+
+ return wrap_session(config, _showfixtures_main)
+
+
def _showfixtures_main(config: Config, session: Session) -> None:
- import _pytest.config
-
- session.perform_collect()
- curdir = py.path.local()
- tw = _pytest.config.create_terminal_writer(config)
- verbose = config.getvalue("verbose")
-
- fm = session._fixturemanager
-
- available = []
+ import _pytest.config
+
+ session.perform_collect()
+ curdir = py.path.local()
+ tw = _pytest.config.create_terminal_writer(config)
+ verbose = config.getvalue("verbose")
+
+ fm = session._fixturemanager
+
+ available = []
seen: Set[Tuple[str, str]] = set()
-
- for argname, fixturedefs in fm._arg2fixturedefs.items():
- assert fixturedefs is not None
- if not fixturedefs:
- continue
- for fixturedef in fixturedefs:
+
+ for argname, fixturedefs in fm._arg2fixturedefs.items():
+ assert fixturedefs is not None
+ if not fixturedefs:
+ continue
+ for fixturedef in fixturedefs:
loc = getlocation(fixturedef.func, str(curdir))
- if (fixturedef.argname, loc) in seen:
- continue
- seen.add((fixturedef.argname, loc))
- available.append(
- (
- len(fixturedef.baseid),
- fixturedef.func.__module__,
+ if (fixturedef.argname, loc) in seen:
+ continue
+ seen.add((fixturedef.argname, loc))
+ available.append(
+ (
+ len(fixturedef.baseid),
+ fixturedef.func.__module__,
curdir.bestrelpath(py.path.local(loc)),
- fixturedef.argname,
- fixturedef,
- )
- )
-
- available.sort()
- currentmodule = None
- for baseid, module, bestrel, argname, fixturedef in available:
- if currentmodule != module:
- if not module.startswith("_pytest."):
- tw.line()
+ fixturedef.argname,
+ fixturedef,
+ )
+ )
+
+ available.sort()
+ currentmodule = None
+ for baseid, module, bestrel, argname, fixturedef in available:
+ if currentmodule != module:
+ if not module.startswith("_pytest."):
+ tw.line()
tw.sep("-", f"fixtures defined from {module}")
- currentmodule = module
- if verbose <= 0 and argname[0] == "_":
- continue
+ currentmodule = module
+ if verbose <= 0 and argname[0] == "_":
+ continue
tw.write(argname, green=True)
if fixturedef.scope != "function":
tw.write(" [%s scope]" % fixturedef.scope, cyan=True)
- if verbose > 0:
+ if verbose > 0:
tw.write(" -- %s" % bestrel, yellow=True)
tw.write("\n")
loc = getlocation(fixturedef.func, str(curdir))
doc = inspect.getdoc(fixturedef.func)
- if doc:
- write_docstring(tw, doc)
- else:
+ if doc:
+ write_docstring(tw, doc)
+ else:
tw.line(f" {loc}: no docstring available", red=True)
tw.line()
-
-
+
+
def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None:
for line in doc.split("\n"):
tw.line(indent + line)
-
-
+
+
class Function(PyobjMixin, nodes.Item):
"""An Item responsible for setting up and executing a Python test function.
@@ -1573,28 +1573,28 @@ class Function(PyobjMixin, nodes.Item):
Defaults to ``name``. Set this if name is different from the original name,
for example when it contains decorations like those added by parametrization
(``my_func[my_param]``).
- """
-
+ """
+
# Disable since functions handle it themselves.
- _ALLOW_MARKERS = False
-
- def __init__(
- self,
+ _ALLOW_MARKERS = False
+
+ def __init__(
+ self,
name: str,
- parent,
+ parent,
config: Optional[Config] = None,
callspec: Optional[CallSpec2] = None,
- callobj=NOTSET,
- keywords=None,
+ callobj=NOTSET,
+ keywords=None,
session: Optional[Session] = None,
fixtureinfo: Optional[FuncFixtureInfo] = None,
originalname: Optional[str] = None,
) -> None:
super().__init__(name, parent, config=config, session=session)
- if callobj is not NOTSET:
- self.obj = callobj
-
+ if callobj is not NOTSET:
+ self.obj = callobj
+
#: Original function name, without any decorations (for example
#: parametrization adds a ``"[...]"`` suffix to function names), used to access
#: the underlying function object from ``parent`` (in case ``callobj`` is not given
@@ -1606,21 +1606,21 @@ class Function(PyobjMixin, nodes.Item):
# Note: when FunctionDefinition is introduced, we should change ``originalname``
# to a readonly property that returns FunctionDefinition.name.
- self.keywords.update(self.obj.__dict__)
- self.own_markers.extend(get_unpacked_marks(self.obj))
- if callspec:
- self.callspec = callspec
- # this is total hostile and a mess
- # keywords are broken by design by now
- # this will be redeemed later
- for mark in callspec.marks:
- # feel free to cry, this was broken for years before
- # and keywords cant fix it per design
- self.keywords[mark.name] = mark
- self.own_markers.extend(normalize_mark_list(callspec.marks))
- if keywords:
- self.keywords.update(keywords)
-
+ self.keywords.update(self.obj.__dict__)
+ self.own_markers.extend(get_unpacked_marks(self.obj))
+ if callspec:
+ self.callspec = callspec
+ # this is total hostile and a mess
+ # keywords are broken by design by now
+ # this will be redeemed later
+ for mark in callspec.marks:
+ # feel free to cry, this was broken for years before
+ # and keywords cant fix it per design
+ self.keywords[mark.name] = mark
+ self.own_markers.extend(normalize_mark_list(callspec.marks))
+ if keywords:
+ self.keywords.update(keywords)
+
# todo: this is a hell of a hack
# https://github.com/pytest-dev/pytest/issues/4569
@@ -1632,14 +1632,14 @@ class Function(PyobjMixin, nodes.Item):
}
)
- if fixtureinfo is None:
- fixtureinfo = self.session._fixturemanager.getfixtureinfo(
+ if fixtureinfo is None:
+ fixtureinfo = self.session._fixturemanager.getfixtureinfo(
self, self.obj, self.cls, funcargs=True
- )
+ )
self._fixtureinfo: FuncFixtureInfo = fixtureinfo
- self.fixturenames = fixtureinfo.names_closure
- self._initrequest()
-
+ self.fixturenames = fixtureinfo.names_closure
+ self._initrequest()
+
@classmethod
def from_parent(cls, parent, **kw): # todo: determine sound type limitations
"""The public constructor."""
@@ -1648,31 +1648,31 @@ class Function(PyobjMixin, nodes.Item):
def _initrequest(self) -> None:
self.funcargs: Dict[str, object] = {}
self._request = fixtures.FixtureRequest(self, _ispytest=True)
-
- @property
- def function(self):
+
+ @property
+ def function(self):
"""Underlying python 'function' object."""
- return getimfunc(self.obj)
-
- def _getobj(self):
+ return getimfunc(self.obj)
+
+ def _getobj(self):
assert self.parent is not None
return getattr(self.parent.obj, self.originalname) # type: ignore[attr-defined]
-
- @property
- def _pyfuncitem(self):
+
+ @property
+ def _pyfuncitem(self):
"""(compatonly) for code expecting pytest-2.2 style request objects."""
- return self
-
+ return self
+
def runtest(self) -> None:
"""Execute the underlying test function."""
- self.ihook.pytest_pyfunc_call(pyfuncitem=self)
-
+ self.ihook.pytest_pyfunc_call(pyfuncitem=self)
+
def setup(self) -> None:
if isinstance(self.parent, Instance):
self.parent.newinstance()
self.obj = self._getobj()
self._request._fillfixtures()
-
+
def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:
if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False):
code = _pytest._code.Code.from_function(get_real_func(self.obj))
@@ -1685,7 +1685,7 @@ class Function(PyobjMixin, nodes.Item):
ntraceback = ntraceback.filter(filter_traceback)
if not ntraceback:
ntraceback = traceback
-
+
excinfo.traceback = ntraceback.filter()
# issue364: mark all but first and last frames to
# only show a single-line message for each frame.
@@ -1704,13 +1704,13 @@ class Function(PyobjMixin, nodes.Item):
return self._repr_failure_py(excinfo, style=style)
-class FunctionDefinition(Function):
- """
+class FunctionDefinition(Function):
+ """
This class is a step gap solution until we evolve to have actual function definition nodes
and manage to get rid of ``metafunc``.
- """
-
+ """
+
def runtest(self) -> None:
raise RuntimeError("function definitions are not supposed to be run as tests")
-
- setup = runtest
+
+ setup = runtest