Coverage for /opt/homebrew/lib/python3.11/site-packages/_pytest/python.py: 55%
904 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 13:14 +0700
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 13:14 +0700
1"""Python test discovery, setup and run of test functions."""
2import enum
3import fnmatch
4import inspect
5import itertools
6import os
7import sys
8import types
9import warnings
10from collections import Counter
11from collections import defaultdict
12from functools import partial
13from pathlib import Path
14from typing import Any
15from typing import Callable
16from typing import Dict
17from typing import Generator
18from typing import Iterable
19from typing import Iterator
20from typing import List
21from typing import Mapping
22from typing import Optional
23from typing import Pattern
24from typing import Sequence
25from typing import Set
26from typing import Tuple
27from typing import TYPE_CHECKING
28from typing import Union
30import attr
32import _pytest
33from _pytest import fixtures
34from _pytest import nodes
35from _pytest._code import filter_traceback
36from _pytest._code import getfslineno
37from _pytest._code.code import ExceptionInfo
38from _pytest._code.code import TerminalRepr
39from _pytest._io import TerminalWriter
40from _pytest._io.saferepr import saferepr
41from _pytest.compat import ascii_escaped
42from _pytest.compat import assert_never
43from _pytest.compat import final
44from _pytest.compat import get_default_arg_names
45from _pytest.compat import get_real_func
46from _pytest.compat import getimfunc
47from _pytest.compat import getlocation
48from _pytest.compat import is_async_function
49from _pytest.compat import is_generator
50from _pytest.compat import LEGACY_PATH
51from _pytest.compat import NOTSET
52from _pytest.compat import safe_getattr
53from _pytest.compat import safe_isclass
54from _pytest.compat import STRING_TYPES
55from _pytest.config import Config
56from _pytest.config import ExitCode
57from _pytest.config import hookimpl
58from _pytest.config.argparsing import Parser
59from _pytest.deprecated import check_ispytest
60from _pytest.deprecated import FSCOLLECTOR_GETHOOKPROXY_ISINITPATH
61from _pytest.deprecated import INSTANCE_COLLECTOR
62from _pytest.deprecated import NOSE_SUPPORT_METHOD
63from _pytest.fixtures import FuncFixtureInfo
64from _pytest.main import Session
65from _pytest.mark import MARK_GEN
66from _pytest.mark import ParameterSet
67from _pytest.mark.structures import get_unpacked_marks
68from _pytest.mark.structures import Mark
69from _pytest.mark.structures import MarkDecorator
70from _pytest.mark.structures import normalize_mark_list
71from _pytest.outcomes import fail
72from _pytest.outcomes import skip
73from _pytest.pathlib import bestrelpath
74from _pytest.pathlib import fnmatch_ex
75from _pytest.pathlib import import_path
76from _pytest.pathlib import ImportPathMismatchError
77from _pytest.pathlib import parts
78from _pytest.pathlib import visit
79from _pytest.scope import Scope
80from _pytest.warning_types import PytestCollectionWarning
81from _pytest.warning_types import PytestReturnNotNoneWarning
82from _pytest.warning_types import PytestUnhandledCoroutineWarning
84if TYPE_CHECKING:
85 from typing_extensions import Literal
87 from _pytest.scope import _ScopeName
90_PYTEST_DIR = Path(_pytest.__file__).parent
93def pytest_addoption(parser: Parser) -> None:
94 group = parser.getgroup("general")
95 group.addoption(
96 "--fixtures",
97 "--funcargs",
98 action="store_true",
99 dest="showfixtures",
100 default=False,
101 help="Show available fixtures, sorted by plugin appearance "
102 "(fixtures with leading '_' are only shown with '-v')",
103 )
104 group.addoption(
105 "--fixtures-per-test",
106 action="store_true",
107 dest="show_fixtures_per_test",
108 default=False,
109 help="Show fixtures per test",
110 )
111 parser.addini(
112 "python_files",
113 type="args",
114 # NOTE: default is also used in AssertionRewritingHook.
115 default=["test_*.py", "*_test.py"],
116 help="Glob-style file patterns for Python test module discovery",
117 )
118 parser.addini(
119 "python_classes",
120 type="args",
121 default=["Test"],
122 help="Prefixes or glob names for Python test class discovery",
123 )
124 parser.addini(
125 "python_functions",
126 type="args",
127 default=["test"],
128 help="Prefixes or glob names for Python test function and method discovery",
129 )
130 parser.addini(
131 "disable_test_id_escaping_and_forfeit_all_rights_to_community_support",
132 type="bool",
133 default=False,
134 help="Disable string escape non-ASCII characters, might cause unwanted "
135 "side effects(use at your own risk)",
136 )
139def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
140 if config.option.showfixtures:
141 showfixtures(config)
142 return 0
143 if config.option.show_fixtures_per_test:
144 show_fixtures_per_test(config)
145 return 0
146 return None
149def pytest_generate_tests(metafunc: "Metafunc") -> None:
150 for marker in metafunc.definition.iter_markers(name="parametrize"):
151 metafunc.parametrize(*marker.args, **marker.kwargs, _param_mark=marker)
154def pytest_configure(config: Config) -> None:
155 config.addinivalue_line(
156 "markers",
157 "parametrize(argnames, argvalues): call a test function multiple "
158 "times passing in different arguments in turn. argvalues generally "
159 "needs to be a list of values if argnames specifies only one name "
160 "or a list of tuples of values if argnames specifies multiple names. "
161 "Example: @parametrize('arg1', [1,2]) would lead to two calls of the "
162 "decorated test function, one with arg1=1 and another with arg1=2."
163 "see https://docs.pytest.org/en/stable/how-to/parametrize.html for more info "
164 "and examples.",
165 )
166 config.addinivalue_line(
167 "markers",
168 "usefixtures(fixturename1, fixturename2, ...): mark tests as needing "
169 "all of the specified fixtures. see "
170 "https://docs.pytest.org/en/stable/explanation/fixtures.html#usefixtures ",
171 )
174def async_warn_and_skip(nodeid: str) -> None:
175 msg = "async def functions are not natively supported and have been skipped.\n"
176 msg += (
177 "You need to install a suitable plugin for your async framework, for example:\n"
178 )
179 msg += " - anyio\n"
180 msg += " - pytest-asyncio\n"
181 msg += " - pytest-tornasync\n"
182 msg += " - pytest-trio\n"
183 msg += " - pytest-twisted"
184 warnings.warn(PytestUnhandledCoroutineWarning(msg.format(nodeid)))
185 skip(reason="async def function and no async plugin installed (see warnings)")
188@hookimpl(trylast=True)
189def pytest_pyfunc_call(pyfuncitem: "Function") -> Optional[object]:
190 testfunction = pyfuncitem.obj
191 if is_async_function(testfunction):
192 async_warn_and_skip(pyfuncitem.nodeid)
193 funcargs = pyfuncitem.funcargs
194 testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames}
195 result = testfunction(**testargs)
196 if hasattr(result, "__await__") or hasattr(result, "__aiter__"):
197 async_warn_and_skip(pyfuncitem.nodeid)
198 elif result is not None:
199 warnings.warn(
200 PytestReturnNotNoneWarning(
201 f"Expected None, but {pyfuncitem.nodeid} returned {result!r}, which will be an error in a "
202 "future version of pytest. Did you mean to use `assert` instead of `return`?"
203 )
204 )
205 return True
208def pytest_collect_file(file_path: Path, parent: nodes.Collector) -> Optional["Module"]:
209 if file_path.suffix == ".py":
210 if not parent.session.isinitpath(file_path):
211 if not path_matches_patterns(
212 file_path, parent.config.getini("python_files") + ["__init__.py"]
213 ):
214 return None
215 ihook = parent.session.gethookproxy(file_path)
216 module: Module = ihook.pytest_pycollect_makemodule(
217 module_path=file_path, parent=parent
218 )
219 return module
220 return None
223def path_matches_patterns(path: Path, patterns: Iterable[str]) -> bool:
224 """Return whether path matches any of the patterns in the list of globs given."""
225 return any(fnmatch_ex(pattern, path) for pattern in patterns)
228def pytest_pycollect_makemodule(module_path: Path, parent) -> "Module":
229 if module_path.name == "__init__.py":
230 pkg: Package = Package.from_parent(parent, path=module_path)
231 return pkg
232 mod: Module = Module.from_parent(parent, path=module_path)
233 return mod
236@hookimpl(trylast=True)
237def pytest_pycollect_makeitem(
238 collector: Union["Module", "Class"], name: str, obj: object
239) -> Union[None, nodes.Item, nodes.Collector, List[Union[nodes.Item, nodes.Collector]]]:
240 assert isinstance(collector, (Class, Module)), type(collector)
241 # Nothing was collected elsewhere, let's do it here.
242 if safe_isclass(obj):
243 if collector.istestclass(obj, name):
244 klass: Class = Class.from_parent(collector, name=name, obj=obj)
245 return klass
246 elif collector.istestfunction(obj, name):
247 # mock seems to store unbound methods (issue473), normalize it.
248 obj = getattr(obj, "__func__", obj)
249 # We need to try and unwrap the function if it's a functools.partial
250 # or a functools.wrapped.
251 # We mustn't if it's been wrapped with mock.patch (python 2 only).
252 if not (inspect.isfunction(obj) or inspect.isfunction(get_real_func(obj))):
253 filename, lineno = getfslineno(obj)
254 warnings.warn_explicit(
255 message=PytestCollectionWarning(
256 "cannot collect %r because it is not a function." % name
257 ),
258 category=None,
259 filename=str(filename),
260 lineno=lineno + 1,
261 )
262 elif getattr(obj, "__test__", True):
263 if is_generator(obj):
264 res: Function = Function.from_parent(collector, name=name)
265 reason = "yield tests were removed in pytest 4.0 - {name} will be ignored".format(
266 name=name
267 )
268 res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
269 res.warn(PytestCollectionWarning(reason))
270 return res
271 else:
272 return list(collector._genfunctions(name, obj))
273 return None
276class PyobjMixin(nodes.Node):
277 """this mix-in inherits from Node to carry over the typing information
279 as its intended to always mix in before a node
280 its position in the mro is unaffected"""
282 _ALLOW_MARKERS = True
284 @property
285 def module(self):
286 """Python module object this node was collected from (can be None)."""
287 node = self.getparent(Module)
288 return node.obj if node is not None else None
290 @property
291 def cls(self):
292 """Python class object this node was collected from (can be None)."""
293 node = self.getparent(Class)
294 return node.obj if node is not None else None
296 @property
297 def instance(self):
298 """Python instance object the function is bound to.
300 Returns None if not a test method, e.g. for a standalone test function,
301 a staticmethod, a class or a module.
302 """
303 node = self.getparent(Function)
304 return getattr(node.obj, "__self__", None) if node is not None else None
306 @property
307 def obj(self):
308 """Underlying Python object."""
309 obj = getattr(self, "_obj", None)
310 if obj is None:
311 self._obj = obj = self._getobj()
312 # XXX evil hack
313 # used to avoid Function marker duplication
314 if self._ALLOW_MARKERS:
315 self.own_markers.extend(get_unpacked_marks(self.obj))
316 # This assumes that `obj` is called before there is a chance
317 # to add custom keys to `self.keywords`, so no fear of overriding.
318 self.keywords.update((mark.name, mark) for mark in self.own_markers)
319 return obj
321 @obj.setter
322 def obj(self, value):
323 self._obj = value
325 def _getobj(self):
326 """Get the underlying Python object. May be overwritten by subclasses."""
327 # TODO: Improve the type of `parent` such that assert/ignore aren't needed.
328 assert self.parent is not None
329 obj = self.parent.obj # type: ignore[attr-defined]
330 return getattr(obj, self.name)
332 def getmodpath(self, stopatmodule: bool = True, includemodule: bool = False) -> str:
333 """Return Python path relative to the containing module."""
334 chain = self.listchain()
335 chain.reverse()
336 parts = []
337 for node in chain:
338 name = node.name
339 if isinstance(node, Module):
340 name = os.path.splitext(name)[0]
341 if stopatmodule:
342 if includemodule:
343 parts.append(name)
344 break
345 parts.append(name)
346 parts.reverse()
347 return ".".join(parts)
349 def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
350 # XXX caching?
351 obj = self.obj
352 compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None)
353 if isinstance(compat_co_firstlineno, int):
354 # nose compatibility
355 file_path = sys.modules[obj.__module__].__file__
356 assert file_path is not None
357 if file_path.endswith(".pyc"):
358 file_path = file_path[:-1]
359 path: Union["os.PathLike[str]", str] = file_path
360 lineno = compat_co_firstlineno
361 else:
362 path, lineno = getfslineno(obj)
363 modpath = self.getmodpath()
364 assert isinstance(lineno, int)
365 return path, lineno, modpath
368# As an optimization, these builtin attribute names are pre-ignored when
369# iterating over an object during collection -- the pytest_pycollect_makeitem
370# hook is not called for them.
371# fmt: off
372class _EmptyClass: pass # noqa: E701
373IGNORED_ATTRIBUTES = frozenset.union( # noqa: E305
374 frozenset(),
375 # Module.
376 dir(types.ModuleType("empty_module")),
377 # Some extra module attributes the above doesn't catch.
378 {"__builtins__", "__file__", "__cached__"},
379 # Class.
380 dir(_EmptyClass),
381 # Instance.
382 dir(_EmptyClass()),
383)
384del _EmptyClass
385# fmt: on
388class PyCollector(PyobjMixin, nodes.Collector):
389 def funcnamefilter(self, name: str) -> bool:
390 return self._matches_prefix_or_glob_option("python_functions", name)
392 def isnosetest(self, obj: object) -> bool:
393 """Look for the __test__ attribute, which is applied by the
394 @nose.tools.istest decorator.
395 """
396 # We explicitly check for "is True" here to not mistakenly treat
397 # classes with a custom __getattr__ returning something truthy (like a
398 # function) as test classes.
399 return safe_getattr(obj, "__test__", False) is True
401 def classnamefilter(self, name: str) -> bool:
402 return self._matches_prefix_or_glob_option("python_classes", name)
404 def istestfunction(self, obj: object, name: str) -> bool:
405 if self.funcnamefilter(name) or self.isnosetest(obj):
406 if isinstance(obj, staticmethod):
407 # staticmethods need to be unwrapped.
408 obj = safe_getattr(obj, "__func__", False)
409 return callable(obj) and fixtures.getfixturemarker(obj) is None
410 else:
411 return False
413 def istestclass(self, obj: object, name: str) -> bool:
414 return self.classnamefilter(name) or self.isnosetest(obj)
416 def _matches_prefix_or_glob_option(self, option_name: str, name: str) -> bool:
417 """Check if the given name matches the prefix or glob-pattern defined
418 in ini configuration."""
419 for option in self.config.getini(option_name):
420 if name.startswith(option):
421 return True
422 # Check that name looks like a glob-string before calling fnmatch
423 # because this is called for every name in each collected module,
424 # and fnmatch is somewhat expensive to call.
425 elif ("*" in option or "?" in option or "[" in option) and fnmatch.fnmatch(
426 name, option
427 ):
428 return True
429 return False
431 def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
432 if not getattr(self.obj, "__test__", True):
433 return []
435 # Avoid random getattrs and peek in the __dict__ instead.
436 dicts = [getattr(self.obj, "__dict__", {})]
437 if isinstance(self.obj, type):
438 for basecls in self.obj.__mro__:
439 dicts.append(basecls.__dict__)
441 # In each class, nodes should be definition ordered.
442 # __dict__ is definition ordered.
443 seen: Set[str] = set()
444 dict_values: List[List[Union[nodes.Item, nodes.Collector]]] = []
445 ihook = self.ihook
446 for dic in dicts:
447 values: List[Union[nodes.Item, nodes.Collector]] = []
448 # Note: seems like the dict can change during iteration -
449 # be careful not to remove the list() without consideration.
450 for name, obj in list(dic.items()):
451 if name in IGNORED_ATTRIBUTES:
452 continue
453 if name in seen:
454 continue
455 seen.add(name)
456 res = ihook.pytest_pycollect_makeitem(
457 collector=self, name=name, obj=obj
458 )
459 if res is None:
460 continue
461 elif isinstance(res, list):
462 values.extend(res)
463 else:
464 values.append(res)
465 dict_values.append(values)
467 # Between classes in the class hierarchy, reverse-MRO order -- nodes
468 # inherited from base classes should come before subclasses.
469 result = []
470 for values in reversed(dict_values):
471 result.extend(values)
472 return result
474 def _genfunctions(self, name: str, funcobj) -> Iterator["Function"]:
475 modulecol = self.getparent(Module)
476 assert modulecol is not None
477 module = modulecol.obj
478 clscol = self.getparent(Class)
479 cls = clscol and clscol.obj or None
481 definition = FunctionDefinition.from_parent(self, name=name, callobj=funcobj)
482 fixtureinfo = definition._fixtureinfo
484 # pytest_generate_tests impls call metafunc.parametrize() which fills
485 # metafunc._calls, the outcome of the hook.
486 metafunc = Metafunc(
487 definition=definition,
488 fixtureinfo=fixtureinfo,
489 config=self.config,
490 cls=cls,
491 module=module,
492 _ispytest=True,
493 )
494 methods = []
495 if hasattr(module, "pytest_generate_tests"):
496 methods.append(module.pytest_generate_tests)
497 if cls is not None and hasattr(cls, "pytest_generate_tests"):
498 methods.append(cls().pytest_generate_tests)
499 self.ihook.pytest_generate_tests.call_extra(methods, dict(metafunc=metafunc))
501 if not metafunc._calls:
502 yield Function.from_parent(self, name=name, fixtureinfo=fixtureinfo)
503 else:
504 # Add funcargs() as fixturedefs to fixtureinfo.arg2fixturedefs.
505 fm = self.session._fixturemanager
506 fixtures.add_funcarg_pseudo_fixture_def(self, metafunc, fm)
508 # Add_funcarg_pseudo_fixture_def may have shadowed some fixtures
509 # with direct parametrization, so make sure we update what the
510 # function really needs.
511 fixtureinfo.prune_dependency_tree()
513 for callspec in metafunc._calls:
514 subname = f"{name}[{callspec.id}]"
515 yield Function.from_parent(
516 self,
517 name=subname,
518 callspec=callspec,
519 fixtureinfo=fixtureinfo,
520 keywords={callspec.id: True},
521 originalname=name,
522 )
525class Module(nodes.File, PyCollector):
526 """Collector for test classes and functions."""
528 def _getobj(self):
529 return self._importtestmodule()
531 def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
532 self._inject_setup_module_fixture()
533 self._inject_setup_function_fixture()
534 self.session._fixturemanager.parsefactories(self)
535 return super().collect()
537 def _inject_setup_module_fixture(self) -> None:
538 """Inject a hidden autouse, module scoped fixture into the collected module object
539 that invokes setUpModule/tearDownModule if either or both are available.
541 Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
542 other fixtures (#517).
543 """
544 has_nose = self.config.pluginmanager.has_plugin("nose")
545 setup_module = _get_first_non_fixture_func(
546 self.obj, ("setUpModule", "setup_module")
547 )
548 if setup_module is None and has_nose:
549 # The name "setup" is too common - only treat as fixture if callable.
550 setup_module = _get_first_non_fixture_func(self.obj, ("setup",))
551 if not callable(setup_module):
552 setup_module = None
553 teardown_module = _get_first_non_fixture_func(
554 self.obj, ("tearDownModule", "teardown_module")
555 )
556 if teardown_module is None and has_nose:
557 teardown_module = _get_first_non_fixture_func(self.obj, ("teardown",))
558 # Same as "setup" above - only treat as fixture if callable.
559 if not callable(teardown_module):
560 teardown_module = None
562 if setup_module is None and teardown_module is None:
563 return
565 @fixtures.fixture(
566 autouse=True,
567 scope="module",
568 # Use a unique name to speed up lookup.
569 name=f"_xunit_setup_module_fixture_{self.obj.__name__}",
570 )
571 def xunit_setup_module_fixture(request) -> Generator[None, None, None]:
572 if setup_module is not None:
573 _call_with_optional_argument(setup_module, request.module)
574 yield
575 if teardown_module is not None:
576 _call_with_optional_argument(teardown_module, request.module)
578 self.obj.__pytest_setup_module = xunit_setup_module_fixture
580 def _inject_setup_function_fixture(self) -> None:
581 """Inject a hidden autouse, function scoped fixture into the collected module object
582 that invokes setup_function/teardown_function if either or both are available.
584 Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
585 other fixtures (#517).
586 """
587 setup_function = _get_first_non_fixture_func(self.obj, ("setup_function",))
588 teardown_function = _get_first_non_fixture_func(
589 self.obj, ("teardown_function",)
590 )
591 if setup_function is None and teardown_function is None:
592 return
594 @fixtures.fixture(
595 autouse=True,
596 scope="function",
597 # Use a unique name to speed up lookup.
598 name=f"_xunit_setup_function_fixture_{self.obj.__name__}",
599 )
600 def xunit_setup_function_fixture(request) -> Generator[None, None, None]:
601 if request.instance is not None:
602 # in this case we are bound to an instance, so we need to let
603 # setup_method handle this
604 yield
605 return
606 if setup_function is not None:
607 _call_with_optional_argument(setup_function, request.function)
608 yield
609 if teardown_function is not None:
610 _call_with_optional_argument(teardown_function, request.function)
612 self.obj.__pytest_setup_function = xunit_setup_function_fixture
614 def _importtestmodule(self):
615 # We assume we are only called once per module.
616 importmode = self.config.getoption("--import-mode")
617 try:
618 mod = import_path(self.path, mode=importmode, root=self.config.rootpath)
619 except SyntaxError as e:
620 raise self.CollectError(
621 ExceptionInfo.from_current().getrepr(style="short")
622 ) from e
623 except ImportPathMismatchError as e:
624 raise self.CollectError(
625 "import file mismatch:\n"
626 "imported module %r has this __file__ attribute:\n"
627 " %s\n"
628 "which is not the same as the test file we want to collect:\n"
629 " %s\n"
630 "HINT: remove __pycache__ / .pyc files and/or use a "
631 "unique basename for your test file modules" % e.args
632 ) from e
633 except ImportError as e:
634 exc_info = ExceptionInfo.from_current()
635 if self.config.getoption("verbose") < 2:
636 exc_info.traceback = exc_info.traceback.filter(filter_traceback)
637 exc_repr = (
638 exc_info.getrepr(style="short")
639 if exc_info.traceback
640 else exc_info.exconly()
641 )
642 formatted_tb = str(exc_repr)
643 raise self.CollectError(
644 "ImportError while importing test module '{path}'.\n"
645 "Hint: make sure your test modules/packages have valid Python names.\n"
646 "Traceback:\n"
647 "{traceback}".format(path=self.path, traceback=formatted_tb)
648 ) from e
649 except skip.Exception as e:
650 if e.allow_module_level:
651 raise
652 raise self.CollectError(
653 "Using pytest.skip outside of a test will skip the entire module. "
654 "If that's your intention, pass `allow_module_level=True`. "
655 "If you want to skip a specific test or an entire class, "
656 "use the @pytest.mark.skip or @pytest.mark.skipif decorators."
657 ) from e
658 self.config.pluginmanager.consider_module(mod)
659 return mod
662class Package(Module):
663 def __init__(
664 self,
665 fspath: Optional[LEGACY_PATH],
666 parent: nodes.Collector,
667 # NOTE: following args are unused:
668 config=None,
669 session=None,
670 nodeid=None,
671 path=Optional[Path],
672 ) -> None:
673 # NOTE: Could be just the following, but kept as-is for compat.
674 # nodes.FSCollector.__init__(self, fspath, parent=parent)
675 session = parent.session
676 nodes.FSCollector.__init__(
677 self,
678 fspath=fspath,
679 path=path,
680 parent=parent,
681 config=config,
682 session=session,
683 nodeid=nodeid,
684 )
685 self.name = self.path.parent.name
687 def setup(self) -> None:
688 # Not using fixtures to call setup_module here because autouse fixtures
689 # from packages are not called automatically (#4085).
690 setup_module = _get_first_non_fixture_func(
691 self.obj, ("setUpModule", "setup_module")
692 )
693 if setup_module is not None:
694 _call_with_optional_argument(setup_module, self.obj)
696 teardown_module = _get_first_non_fixture_func(
697 self.obj, ("tearDownModule", "teardown_module")
698 )
699 if teardown_module is not None:
700 func = partial(_call_with_optional_argument, teardown_module, self.obj)
701 self.addfinalizer(func)
703 def gethookproxy(self, fspath: "os.PathLike[str]"):
704 warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
705 return self.session.gethookproxy(fspath)
707 def isinitpath(self, path: Union[str, "os.PathLike[str]"]) -> bool:
708 warnings.warn(FSCOLLECTOR_GETHOOKPROXY_ISINITPATH, stacklevel=2)
709 return self.session.isinitpath(path)
711 def _recurse(self, direntry: "os.DirEntry[str]") -> bool:
712 if direntry.name == "__pycache__":
713 return False
714 fspath = Path(direntry.path)
715 ihook = self.session.gethookproxy(fspath.parent)
716 if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config):
717 return False
718 norecursepatterns = self.config.getini("norecursedirs")
719 if any(fnmatch_ex(pat, fspath) for pat in norecursepatterns):
720 return False
721 return True
723 def _collectfile(
724 self, fspath: Path, handle_dupes: bool = True
725 ) -> Sequence[nodes.Collector]:
726 assert (
727 fspath.is_file()
728 ), "{!r} is not a file (isdir={!r}, exists={!r}, islink={!r})".format(
729 fspath, fspath.is_dir(), fspath.exists(), fspath.is_symlink()
730 )
731 ihook = self.session.gethookproxy(fspath)
732 if not self.session.isinitpath(fspath):
733 if ihook.pytest_ignore_collect(collection_path=fspath, config=self.config):
734 return ()
736 if handle_dupes:
737 keepduplicates = self.config.getoption("keepduplicates")
738 if not keepduplicates:
739 duplicate_paths = self.config.pluginmanager._duplicatepaths
740 if fspath in duplicate_paths:
741 return ()
742 else:
743 duplicate_paths.add(fspath)
745 return ihook.pytest_collect_file(file_path=fspath, parent=self) # type: ignore[no-any-return]
747 def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
748 this_path = self.path.parent
749 init_module = this_path / "__init__.py"
750 if init_module.is_file() and path_matches_patterns(
751 init_module, self.config.getini("python_files")
752 ):
753 yield Module.from_parent(self, path=init_module)
754 pkg_prefixes: Set[Path] = set()
755 for direntry in visit(str(this_path), recurse=self._recurse):
756 path = Path(direntry.path)
758 # We will visit our own __init__.py file, in which case we skip it.
759 if direntry.is_file():
760 if direntry.name == "__init__.py" and path.parent == this_path:
761 continue
763 parts_ = parts(direntry.path)
764 if any(
765 str(pkg_prefix) in parts_ and pkg_prefix / "__init__.py" != path
766 for pkg_prefix in pkg_prefixes
767 ):
768 continue
770 if direntry.is_file():
771 yield from self._collectfile(path)
772 elif not direntry.is_dir():
773 # Broken symlink or invalid/missing file.
774 continue
775 elif path.joinpath("__init__.py").is_file():
776 pkg_prefixes.add(path)
779def _call_with_optional_argument(func, arg) -> None:
780 """Call the given function with the given argument if func accepts one argument, otherwise
781 calls func without arguments."""
782 arg_count = func.__code__.co_argcount
783 if inspect.ismethod(func):
784 arg_count -= 1
785 if arg_count:
786 func(arg)
787 else:
788 func()
791def _get_first_non_fixture_func(obj: object, names: Iterable[str]) -> Optional[object]:
792 """Return the attribute from the given object to be used as a setup/teardown
793 xunit-style function, but only if not marked as a fixture to avoid calling it twice."""
794 for name in names:
795 meth: Optional[object] = getattr(obj, name, None)
796 if meth is not None and fixtures.getfixturemarker(meth) is None:
797 return meth
798 return None
801class Class(PyCollector):
802 """Collector for test methods."""
804 @classmethod
805 def from_parent(cls, parent, *, name, obj=None, **kw):
806 """The public constructor."""
807 return super().from_parent(name=name, parent=parent, **kw)
809 def newinstance(self):
810 return self.obj()
812 def collect(self) -> Iterable[Union[nodes.Item, nodes.Collector]]:
813 if not safe_getattr(self.obj, "__test__", True):
814 return []
815 if hasinit(self.obj):
816 assert self.parent is not None
817 self.warn(
818 PytestCollectionWarning(
819 "cannot collect test class %r because it has a "
820 "__init__ constructor (from: %s)"
821 % (self.obj.__name__, self.parent.nodeid)
822 )
823 )
824 return []
825 elif hasnew(self.obj):
826 assert self.parent is not None
827 self.warn(
828 PytestCollectionWarning(
829 "cannot collect test class %r because it has a "
830 "__new__ constructor (from: %s)"
831 % (self.obj.__name__, self.parent.nodeid)
832 )
833 )
834 return []
836 self._inject_setup_class_fixture()
837 self._inject_setup_method_fixture()
839 self.session._fixturemanager.parsefactories(self.newinstance(), self.nodeid)
841 return super().collect()
843 def _inject_setup_class_fixture(self) -> None:
844 """Inject a hidden autouse, class scoped fixture into the collected class object
845 that invokes setup_class/teardown_class if either or both are available.
847 Using a fixture to invoke this methods ensures we play nicely and unsurprisingly with
848 other fixtures (#517).
849 """
850 setup_class = _get_first_non_fixture_func(self.obj, ("setup_class",))
851 teardown_class = _get_first_non_fixture_func(self.obj, ("teardown_class",))
852 if setup_class is None and teardown_class is None:
853 return
855 @fixtures.fixture(
856 autouse=True,
857 scope="class",
858 # Use a unique name to speed up lookup.
859 name=f"_xunit_setup_class_fixture_{self.obj.__qualname__}",
860 )
861 def xunit_setup_class_fixture(cls) -> Generator[None, None, None]:
862 if setup_class is not None:
863 func = getimfunc(setup_class)
864 _call_with_optional_argument(func, self.obj)
865 yield
866 if teardown_class is not None:
867 func = getimfunc(teardown_class)
868 _call_with_optional_argument(func, self.obj)
870 self.obj.__pytest_setup_class = xunit_setup_class_fixture
872 def _inject_setup_method_fixture(self) -> None:
873 """Inject a hidden autouse, function scoped fixture into the collected class object
874 that invokes setup_method/teardown_method if either or both are available.
876 Using a fixture to invoke these methods ensures we play nicely and unsurprisingly with
877 other fixtures (#517).
878 """
879 has_nose = self.config.pluginmanager.has_plugin("nose")
880 setup_name = "setup_method"
881 setup_method = _get_first_non_fixture_func(self.obj, (setup_name,))
882 emit_nose_setup_warning = False
883 if setup_method is None and has_nose:
884 setup_name = "setup"
885 emit_nose_setup_warning = True
886 setup_method = _get_first_non_fixture_func(self.obj, (setup_name,))
887 teardown_name = "teardown_method"
888 teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,))
889 emit_nose_teardown_warning = False
890 if teardown_method is None and has_nose:
891 teardown_name = "teardown"
892 emit_nose_teardown_warning = True
893 teardown_method = _get_first_non_fixture_func(self.obj, (teardown_name,))
894 if setup_method is None and teardown_method is None:
895 return
897 @fixtures.fixture(
898 autouse=True,
899 scope="function",
900 # Use a unique name to speed up lookup.
901 name=f"_xunit_setup_method_fixture_{self.obj.__qualname__}",
902 )
903 def xunit_setup_method_fixture(self, request) -> Generator[None, None, None]:
904 method = request.function
905 if setup_method is not None:
906 func = getattr(self, setup_name)
907 _call_with_optional_argument(func, method)
908 if emit_nose_setup_warning:
909 warnings.warn(
910 NOSE_SUPPORT_METHOD.format(
911 nodeid=request.node.nodeid, method="setup"
912 ),
913 stacklevel=2,
914 )
915 yield
916 if teardown_method is not None:
917 func = getattr(self, teardown_name)
918 _call_with_optional_argument(func, method)
919 if emit_nose_teardown_warning:
920 warnings.warn(
921 NOSE_SUPPORT_METHOD.format(
922 nodeid=request.node.nodeid, method="teardown"
923 ),
924 stacklevel=2,
925 )
927 self.obj.__pytest_setup_method = xunit_setup_method_fixture
930class InstanceDummy:
931 """Instance used to be a node type between Class and Function. It has been
932 removed in pytest 7.0. Some plugins exist which reference `pytest.Instance`
933 only to ignore it; this dummy class keeps them working. This will be removed
934 in pytest 8."""
937def __getattr__(name: str) -> object:
938 if name == "Instance":
939 warnings.warn(INSTANCE_COLLECTOR, 2)
940 return InstanceDummy
941 raise AttributeError(f"module {__name__} has no attribute {name}")
944def hasinit(obj: object) -> bool:
945 init: object = getattr(obj, "__init__", None)
946 if init:
947 return init != object.__init__
948 return False
951def hasnew(obj: object) -> bool:
952 new: object = getattr(obj, "__new__", None)
953 if new:
954 return new != object.__new__
955 return False
958@final
959@attr.s(frozen=True, auto_attribs=True, slots=True)
960class IdMaker:
961 """Make IDs for a parametrization."""
963 # The argnames of the parametrization.
964 argnames: Sequence[str]
965 # The ParameterSets of the parametrization.
966 parametersets: Sequence[ParameterSet]
967 # Optionally, a user-provided callable to make IDs for parameters in a
968 # ParameterSet.
969 idfn: Optional[Callable[[Any], Optional[object]]]
970 # Optionally, explicit IDs for ParameterSets by index.
971 ids: Optional[Sequence[Optional[object]]]
972 # Optionally, the pytest config.
973 # Used for controlling ASCII escaping, and for calling the
974 # :hook:`pytest_make_parametrize_id` hook.
975 config: Optional[Config]
976 # Optionally, the ID of the node being parametrized.
977 # Used only for clearer error messages.
978 nodeid: Optional[str]
979 # Optionally, the ID of the function being parametrized.
980 # Used only for clearer error messages.
981 func_name: Optional[str]
983 def make_unique_parameterset_ids(self) -> List[str]:
984 """Make a unique identifier for each ParameterSet, that may be used to
985 identify the parametrization in a node ID.
987 Format is <prm_1_token>-...-<prm_n_token>[counter], where prm_x_token is
988 - user-provided id, if given
989 - else an id derived from the value, applicable for certain types
990 - else <argname><parameterset index>
991 The counter suffix is appended only in case a string wouldn't be unique
992 otherwise.
993 """
994 resolved_ids = list(self._resolve_ids())
995 # All IDs must be unique!
996 if len(resolved_ids) != len(set(resolved_ids)):
997 # Record the number of occurrences of each ID.
998 id_counts = Counter(resolved_ids)
999 # Map the ID to its next suffix.
1000 id_suffixes: Dict[str, int] = defaultdict(int)
1001 # Suffix non-unique IDs to make them unique.
1002 for index, id in enumerate(resolved_ids):
1003 if id_counts[id] > 1:
1004 resolved_ids[index] = f"{id}{id_suffixes[id]}"
1005 id_suffixes[id] += 1
1006 return resolved_ids
1008 def _resolve_ids(self) -> Iterable[str]:
1009 """Resolve IDs for all ParameterSets (may contain duplicates)."""
1010 for idx, parameterset in enumerate(self.parametersets):
1011 if parameterset.id is not None:
1012 # ID provided directly - pytest.param(..., id="...")
1013 yield parameterset.id
1014 elif self.ids and idx < len(self.ids) and self.ids[idx] is not None:
1015 # ID provided in the IDs list - parametrize(..., ids=[...]).
1016 yield self._idval_from_value_required(self.ids[idx], idx)
1017 else:
1018 # ID not provided - generate it.
1019 yield "-".join(
1020 self._idval(val, argname, idx)
1021 for val, argname in zip(parameterset.values, self.argnames)
1022 )
1024 def _idval(self, val: object, argname: str, idx: int) -> str:
1025 """Make an ID for a parameter in a ParameterSet."""
1026 idval = self._idval_from_function(val, argname, idx)
1027 if idval is not None:
1028 return idval
1029 idval = self._idval_from_hook(val, argname)
1030 if idval is not None:
1031 return idval
1032 idval = self._idval_from_value(val)
1033 if idval is not None:
1034 return idval
1035 return self._idval_from_argname(argname, idx)
1037 def _idval_from_function(
1038 self, val: object, argname: str, idx: int
1039 ) -> Optional[str]:
1040 """Try to make an ID for a parameter in a ParameterSet using the
1041 user-provided id callable, if given."""
1042 if self.idfn is None:
1043 return None
1044 try:
1045 id = self.idfn(val)
1046 except Exception as e:
1047 prefix = f"{self.nodeid}: " if self.nodeid is not None else ""
1048 msg = "error raised while trying to determine id of parameter '{}' at position {}"
1049 msg = prefix + msg.format(argname, idx)
1050 raise ValueError(msg) from e
1051 if id is None:
1052 return None
1053 return self._idval_from_value(id)
1055 def _idval_from_hook(self, val: object, argname: str) -> Optional[str]:
1056 """Try to make an ID for a parameter in a ParameterSet by calling the
1057 :hook:`pytest_make_parametrize_id` hook."""
1058 if self.config:
1059 id: Optional[str] = self.config.hook.pytest_make_parametrize_id(
1060 config=self.config, val=val, argname=argname
1061 )
1062 return id
1063 return None
1065 def _idval_from_value(self, val: object) -> Optional[str]:
1066 """Try to make an ID for a parameter in a ParameterSet from its value,
1067 if the value type is supported."""
1068 if isinstance(val, STRING_TYPES):
1069 return _ascii_escaped_by_config(val, self.config)
1070 elif val is None or isinstance(val, (float, int, bool, complex)):
1071 return str(val)
1072 elif isinstance(val, Pattern):
1073 return ascii_escaped(val.pattern)
1074 elif val is NOTSET:
1075 # Fallback to default. Note that NOTSET is an enum.Enum.
1076 pass
1077 elif isinstance(val, enum.Enum):
1078 return str(val)
1079 elif isinstance(getattr(val, "__name__", None), str):
1080 # Name of a class, function, module, etc.
1081 name: str = getattr(val, "__name__")
1082 return name
1083 return None
1085 def _idval_from_value_required(self, val: object, idx: int) -> str:
1086 """Like _idval_from_value(), but fails if the type is not supported."""
1087 id = self._idval_from_value(val)
1088 if id is not None:
1089 return id
1091 # Fail.
1092 if self.func_name is not None:
1093 prefix = f"In {self.func_name}: "
1094 elif self.nodeid is not None:
1095 prefix = f"In {self.nodeid}: "
1096 else:
1097 prefix = ""
1098 msg = (
1099 f"{prefix}ids contains unsupported value {saferepr(val)} (type: {type(val)!r}) at index {idx}. "
1100 "Supported types are: str, bytes, int, float, complex, bool, enum, regex or anything with a __name__."
1101 )
1102 fail(msg, pytrace=False)
1104 @staticmethod
1105 def _idval_from_argname(argname: str, idx: int) -> str:
1106 """Make an ID for a parameter in a ParameterSet from the argument name
1107 and the index of the ParameterSet."""
1108 return str(argname) + str(idx)
1111@final
1112@attr.s(frozen=True, slots=True, auto_attribs=True)
1113class CallSpec2:
1114 """A planned parameterized invocation of a test function.
1116 Calculated during collection for a given test function's Metafunc.
1117 Once collection is over, each callspec is turned into a single Item
1118 and stored in item.callspec.
1119 """
1121 # arg name -> arg value which will be passed to the parametrized test
1122 # function (direct parameterization).
1123 funcargs: Dict[str, object] = attr.Factory(dict)
1124 # arg name -> arg value which will be passed to a fixture of the same name
1125 # (indirect parametrization).
1126 params: Dict[str, object] = attr.Factory(dict)
1127 # arg name -> arg index.
1128 indices: Dict[str, int] = attr.Factory(dict)
1129 # Used for sorting parametrized resources.
1130 _arg2scope: Dict[str, Scope] = attr.Factory(dict)
1131 # Parts which will be added to the item's name in `[..]` separated by "-".
1132 _idlist: List[str] = attr.Factory(list)
1133 # Marks which will be applied to the item.
1134 marks: List[Mark] = attr.Factory(list)
1136 def setmulti(
1137 self,
1138 *,
1139 valtypes: Mapping[str, "Literal['params', 'funcargs']"],
1140 argnames: Iterable[str],
1141 valset: Iterable[object],
1142 id: str,
1143 marks: Iterable[Union[Mark, MarkDecorator]],
1144 scope: Scope,
1145 param_index: int,
1146 ) -> "CallSpec2":
1147 funcargs = self.funcargs.copy()
1148 params = self.params.copy()
1149 indices = self.indices.copy()
1150 arg2scope = self._arg2scope.copy()
1151 for arg, val in zip(argnames, valset):
1152 if arg in params or arg in funcargs:
1153 raise ValueError(f"duplicate {arg!r}")
1154 valtype_for_arg = valtypes[arg]
1155 if valtype_for_arg == "params":
1156 params[arg] = val
1157 elif valtype_for_arg == "funcargs":
1158 funcargs[arg] = val
1159 else:
1160 assert_never(valtype_for_arg)
1161 indices[arg] = param_index
1162 arg2scope[arg] = scope
1163 return CallSpec2(
1164 funcargs=funcargs,
1165 params=params,
1166 arg2scope=arg2scope,
1167 indices=indices,
1168 idlist=[*self._idlist, id],
1169 marks=[*self.marks, *normalize_mark_list(marks)],
1170 )
1172 def getparam(self, name: str) -> object:
1173 try:
1174 return self.params[name]
1175 except KeyError as e:
1176 raise ValueError(name) from e
1178 @property
1179 def id(self) -> str:
1180 return "-".join(self._idlist)
1183@final
1184class Metafunc:
1185 """Objects passed to the :hook:`pytest_generate_tests` hook.
1187 They help to inspect a test function and to generate tests according to
1188 test configuration or values specified in the class or module where a
1189 test function is defined.
1190 """
1192 def __init__(
1193 self,
1194 definition: "FunctionDefinition",
1195 fixtureinfo: fixtures.FuncFixtureInfo,
1196 config: Config,
1197 cls=None,
1198 module=None,
1199 *,
1200 _ispytest: bool = False,
1201 ) -> None:
1202 check_ispytest(_ispytest)
1204 #: Access to the underlying :class:`_pytest.python.FunctionDefinition`.
1205 self.definition = definition
1207 #: Access to the :class:`pytest.Config` object for the test session.
1208 self.config = config
1210 #: The module object where the test function is defined in.
1211 self.module = module
1213 #: Underlying Python test function.
1214 self.function = definition.obj
1216 #: Set of fixture names required by the test function.
1217 self.fixturenames = fixtureinfo.names_closure
1219 #: Class object where the test function is defined in or ``None``.
1220 self.cls = cls
1222 self._arg2fixturedefs = fixtureinfo.name2fixturedefs
1224 # Result of parametrize().
1225 self._calls: List[CallSpec2] = []
1227 def parametrize(
1228 self,
1229 argnames: Union[str, Sequence[str]],
1230 argvalues: Iterable[Union[ParameterSet, Sequence[object], object]],
1231 indirect: Union[bool, Sequence[str]] = False,
1232 ids: Optional[
1233 Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]]
1234 ] = None,
1235 scope: "Optional[_ScopeName]" = None,
1236 *,
1237 _param_mark: Optional[Mark] = None,
1238 ) -> None:
1239 """Add new invocations to the underlying test function using the list
1240 of argvalues for the given argnames. Parametrization is performed
1241 during the collection phase. If you need to setup expensive resources
1242 see about setting indirect to do it rather than at test setup time.
1244 Can be called multiple times, in which case each call parametrizes all
1245 previous parametrizations, e.g.
1247 ::
1249 unparametrized: t
1250 parametrize ["x", "y"]: t[x], t[y]
1251 parametrize [1, 2]: t[x-1], t[x-2], t[y-1], t[y-2]
1253 :param argnames:
1254 A comma-separated string denoting one or more argument names, or
1255 a list/tuple of argument strings.
1257 :param argvalues:
1258 The list of argvalues determines how often a test is invoked with
1259 different argument values.
1261 If only one argname was specified argvalues is a list of values.
1262 If N argnames were specified, argvalues must be a list of
1263 N-tuples, where each tuple-element specifies a value for its
1264 respective argname.
1266 :param indirect:
1267 A list of arguments' names (subset of argnames) or a boolean.
1268 If True the list contains all names from the argnames. Each
1269 argvalue corresponding to an argname in this list will
1270 be passed as request.param to its respective argname fixture
1271 function so that it can perform more expensive setups during the
1272 setup phase of a test rather than at collection time.
1274 :param ids:
1275 Sequence of (or generator for) ids for ``argvalues``,
1276 or a callable to return part of the id for each argvalue.
1278 With sequences (and generators like ``itertools.count()``) the
1279 returned ids should be of type ``string``, ``int``, ``float``,
1280 ``bool``, or ``None``.
1281 They are mapped to the corresponding index in ``argvalues``.
1282 ``None`` means to use the auto-generated id.
1284 If it is a callable it will be called for each entry in
1285 ``argvalues``, and the return value is used as part of the
1286 auto-generated id for the whole set (where parts are joined with
1287 dashes ("-")).
1288 This is useful to provide more specific ids for certain items, e.g.
1289 dates. Returning ``None`` will use an auto-generated id.
1291 If no ids are provided they will be generated automatically from
1292 the argvalues.
1294 :param scope:
1295 If specified it denotes the scope of the parameters.
1296 The scope is used for grouping tests by parameter instances.
1297 It will also override any fixture-function defined scope, allowing
1298 to set a dynamic scope using test context or configuration.
1299 """
1300 argnames, parametersets = ParameterSet._for_parametrize(
1301 argnames,
1302 argvalues,
1303 self.function,
1304 self.config,
1305 nodeid=self.definition.nodeid,
1306 )
1307 del argvalues
1309 if "request" in argnames:
1310 fail(
1311 "'request' is a reserved name and cannot be used in @pytest.mark.parametrize",
1312 pytrace=False,
1313 )
1315 if scope is not None:
1316 scope_ = Scope.from_user(
1317 scope, descr=f"parametrize() call in {self.function.__name__}"
1318 )
1319 else:
1320 scope_ = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
1322 self._validate_if_using_arg_names(argnames, indirect)
1324 arg_values_types = self._resolve_arg_value_types(argnames, indirect)
1326 # Use any already (possibly) generated ids with parametrize Marks.
1327 if _param_mark and _param_mark._param_ids_from:
1328 generated_ids = _param_mark._param_ids_from._param_ids_generated
1329 if generated_ids is not None:
1330 ids = generated_ids
1332 ids = self._resolve_parameter_set_ids(
1333 argnames, ids, parametersets, nodeid=self.definition.nodeid
1334 )
1336 # Store used (possibly generated) ids with parametrize Marks.
1337 if _param_mark and _param_mark._param_ids_from and generated_ids is None:
1338 object.__setattr__(_param_mark._param_ids_from, "_param_ids_generated", ids)
1340 # Create the new calls: if we are parametrize() multiple times (by applying the decorator
1341 # more than once) then we accumulate those calls generating the cartesian product
1342 # of all calls.
1343 newcalls = []
1344 for callspec in self._calls or [CallSpec2()]:
1345 for param_index, (param_id, param_set) in enumerate(
1346 zip(ids, parametersets)
1347 ):
1348 newcallspec = callspec.setmulti(
1349 valtypes=arg_values_types,
1350 argnames=argnames,
1351 valset=param_set.values,
1352 id=param_id,
1353 marks=param_set.marks,
1354 scope=scope_,
1355 param_index=param_index,
1356 )
1357 newcalls.append(newcallspec)
1358 self._calls = newcalls
1360 def _resolve_parameter_set_ids(
1361 self,
1362 argnames: Sequence[str],
1363 ids: Optional[
1364 Union[Iterable[Optional[object]], Callable[[Any], Optional[object]]]
1365 ],
1366 parametersets: Sequence[ParameterSet],
1367 nodeid: str,
1368 ) -> List[str]:
1369 """Resolve the actual ids for the given parameter sets.
1371 :param argnames:
1372 Argument names passed to ``parametrize()``.
1373 :param ids:
1374 The `ids` parameter of the ``parametrize()`` call (see docs).
1375 :param parametersets:
1376 The parameter sets, each containing a set of values corresponding
1377 to ``argnames``.
1378 :param nodeid str:
1379 The nodeid of the definition item that generated this
1380 parametrization.
1381 :returns:
1382 List with ids for each parameter set given.
1383 """
1384 if ids is None:
1385 idfn = None
1386 ids_ = None
1387 elif callable(ids):
1388 idfn = ids
1389 ids_ = None
1390 else:
1391 idfn = None
1392 ids_ = self._validate_ids(ids, parametersets, self.function.__name__)
1393 id_maker = IdMaker(
1394 argnames,
1395 parametersets,
1396 idfn,
1397 ids_,
1398 self.config,
1399 nodeid=nodeid,
1400 func_name=self.function.__name__,
1401 )
1402 return id_maker.make_unique_parameterset_ids()
1404 def _validate_ids(
1405 self,
1406 ids: Iterable[Optional[object]],
1407 parametersets: Sequence[ParameterSet],
1408 func_name: str,
1409 ) -> List[Optional[object]]:
1410 try:
1411 num_ids = len(ids) # type: ignore[arg-type]
1412 except TypeError:
1413 try:
1414 iter(ids)
1415 except TypeError as e:
1416 raise TypeError("ids must be a callable or an iterable") from e
1417 num_ids = len(parametersets)
1419 # num_ids == 0 is a special case: https://github.com/pytest-dev/pytest/issues/1849
1420 if num_ids != len(parametersets) and num_ids != 0:
1421 msg = "In {}: {} parameter sets specified, with different number of ids: {}"
1422 fail(msg.format(func_name, len(parametersets), num_ids), pytrace=False)
1424 return list(itertools.islice(ids, num_ids))
1426 def _resolve_arg_value_types(
1427 self,
1428 argnames: Sequence[str],
1429 indirect: Union[bool, Sequence[str]],
1430 ) -> Dict[str, "Literal['params', 'funcargs']"]:
1431 """Resolve if each parametrized argument must be considered a
1432 parameter to a fixture or a "funcarg" to the function, based on the
1433 ``indirect`` parameter of the parametrized() call.
1435 :param List[str] argnames: List of argument names passed to ``parametrize()``.
1436 :param indirect: Same as the ``indirect`` parameter of ``parametrize()``.
1437 :rtype: Dict[str, str]
1438 A dict mapping each arg name to either:
1439 * "params" if the argname should be the parameter of a fixture of the same name.
1440 * "funcargs" if the argname should be a parameter to the parametrized test function.
1441 """
1442 if isinstance(indirect, bool):
1443 valtypes: Dict[str, Literal["params", "funcargs"]] = dict.fromkeys(
1444 argnames, "params" if indirect else "funcargs"
1445 )
1446 elif isinstance(indirect, Sequence):
1447 valtypes = dict.fromkeys(argnames, "funcargs")
1448 for arg in indirect:
1449 if arg not in argnames:
1450 fail(
1451 "In {}: indirect fixture '{}' doesn't exist".format(
1452 self.function.__name__, arg
1453 ),
1454 pytrace=False,
1455 )
1456 valtypes[arg] = "params"
1457 else:
1458 fail(
1459 "In {func}: expected Sequence or boolean for indirect, got {type}".format(
1460 type=type(indirect).__name__, func=self.function.__name__
1461 ),
1462 pytrace=False,
1463 )
1464 return valtypes
1466 def _validate_if_using_arg_names(
1467 self,
1468 argnames: Sequence[str],
1469 indirect: Union[bool, Sequence[str]],
1470 ) -> None:
1471 """Check if all argnames are being used, by default values, or directly/indirectly.
1473 :param List[str] argnames: List of argument names passed to ``parametrize()``.
1474 :param indirect: Same as the ``indirect`` parameter of ``parametrize()``.
1475 :raises ValueError: If validation fails.
1476 """
1477 default_arg_names = set(get_default_arg_names(self.function))
1478 func_name = self.function.__name__
1479 for arg in argnames:
1480 if arg not in self.fixturenames:
1481 if arg in default_arg_names:
1482 fail(
1483 "In {}: function already takes an argument '{}' with a default value".format(
1484 func_name, arg
1485 ),
1486 pytrace=False,
1487 )
1488 else:
1489 if isinstance(indirect, Sequence):
1490 name = "fixture" if arg in indirect else "argument"
1491 else:
1492 name = "fixture" if indirect else "argument"
1493 fail(
1494 f"In {func_name}: function uses no {name} '{arg}'",
1495 pytrace=False,
1496 )
1499def _find_parametrized_scope(
1500 argnames: Sequence[str],
1501 arg2fixturedefs: Mapping[str, Sequence[fixtures.FixtureDef[object]]],
1502 indirect: Union[bool, Sequence[str]],
1503) -> Scope:
1504 """Find the most appropriate scope for a parametrized call based on its arguments.
1506 When there's at least one direct argument, always use "function" scope.
1508 When a test function is parametrized and all its arguments are indirect
1509 (e.g. fixtures), return the most narrow scope based on the fixtures used.
1511 Related to issue #1832, based on code posted by @Kingdread.
1512 """
1513 if isinstance(indirect, Sequence):
1514 all_arguments_are_fixtures = len(indirect) == len(argnames)
1515 else:
1516 all_arguments_are_fixtures = bool(indirect)
1518 if all_arguments_are_fixtures:
1519 fixturedefs = arg2fixturedefs or {}
1520 used_scopes = [
1521 fixturedef[0]._scope
1522 for name, fixturedef in fixturedefs.items()
1523 if name in argnames
1524 ]
1525 # Takes the most narrow scope from used fixtures.
1526 return min(used_scopes, default=Scope.Function)
1528 return Scope.Function
1531def _ascii_escaped_by_config(val: Union[str, bytes], config: Optional[Config]) -> str:
1532 if config is None:
1533 escape_option = False
1534 else:
1535 escape_option = config.getini(
1536 "disable_test_id_escaping_and_forfeit_all_rights_to_community_support"
1537 )
1538 # TODO: If escaping is turned off and the user passes bytes,
1539 # will return a bytes. For now we ignore this but the
1540 # code *probably* doesn't handle this case.
1541 return val if escape_option else ascii_escaped(val) # type: ignore
1544def _pretty_fixture_path(func) -> str:
1545 cwd = Path.cwd()
1546 loc = Path(getlocation(func, str(cwd)))
1547 prefix = Path("...", "_pytest")
1548 try:
1549 return str(prefix / loc.relative_to(_PYTEST_DIR))
1550 except ValueError:
1551 return bestrelpath(cwd, loc)
1554def show_fixtures_per_test(config):
1555 from _pytest.main import wrap_session
1557 return wrap_session(config, _show_fixtures_per_test)
1560def _show_fixtures_per_test(config: Config, session: Session) -> None:
1561 import _pytest.config
1563 session.perform_collect()
1564 curdir = Path.cwd()
1565 tw = _pytest.config.create_terminal_writer(config)
1566 verbose = config.getvalue("verbose")
1568 def get_best_relpath(func) -> str:
1569 loc = getlocation(func, str(curdir))
1570 return bestrelpath(curdir, Path(loc))
1572 def write_fixture(fixture_def: fixtures.FixtureDef[object]) -> None:
1573 argname = fixture_def.argname
1574 if verbose <= 0 and argname.startswith("_"):
1575 return
1576 prettypath = _pretty_fixture_path(fixture_def.func)
1577 tw.write(f"{argname}", green=True)
1578 tw.write(f" -- {prettypath}", yellow=True)
1579 tw.write("\n")
1580 fixture_doc = inspect.getdoc(fixture_def.func)
1581 if fixture_doc:
1582 write_docstring(
1583 tw, fixture_doc.split("\n\n")[0] if verbose <= 0 else fixture_doc
1584 )
1585 else:
1586 tw.line(" no docstring available", red=True)
1588 def write_item(item: nodes.Item) -> None:
1589 # Not all items have _fixtureinfo attribute.
1590 info: Optional[FuncFixtureInfo] = getattr(item, "_fixtureinfo", None)
1591 if info is None or not info.name2fixturedefs:
1592 # This test item does not use any fixtures.
1593 return
1594 tw.line()
1595 tw.sep("-", f"fixtures used by {item.name}")
1596 # TODO: Fix this type ignore.
1597 tw.sep("-", f"({get_best_relpath(item.function)})") # type: ignore[attr-defined]
1598 # dict key not used in loop but needed for sorting.
1599 for _, fixturedefs in sorted(info.name2fixturedefs.items()):
1600 assert fixturedefs is not None
1601 if not fixturedefs:
1602 continue
1603 # Last item is expected to be the one used by the test item.
1604 write_fixture(fixturedefs[-1])
1606 for session_item in session.items:
1607 write_item(session_item)
1610def showfixtures(config: Config) -> Union[int, ExitCode]:
1611 from _pytest.main import wrap_session
1613 return wrap_session(config, _showfixtures_main)
1616def _showfixtures_main(config: Config, session: Session) -> None:
1617 import _pytest.config
1619 session.perform_collect()
1620 curdir = Path.cwd()
1621 tw = _pytest.config.create_terminal_writer(config)
1622 verbose = config.getvalue("verbose")
1624 fm = session._fixturemanager
1626 available = []
1627 seen: Set[Tuple[str, str]] = set()
1629 for argname, fixturedefs in fm._arg2fixturedefs.items():
1630 assert fixturedefs is not None
1631 if not fixturedefs:
1632 continue
1633 for fixturedef in fixturedefs:
1634 loc = getlocation(fixturedef.func, str(curdir))
1635 if (fixturedef.argname, loc) in seen:
1636 continue
1637 seen.add((fixturedef.argname, loc))
1638 available.append(
1639 (
1640 len(fixturedef.baseid),
1641 fixturedef.func.__module__,
1642 _pretty_fixture_path(fixturedef.func),
1643 fixturedef.argname,
1644 fixturedef,
1645 )
1646 )
1648 available.sort()
1649 currentmodule = None
1650 for baseid, module, prettypath, argname, fixturedef in available:
1651 if currentmodule != module:
1652 if not module.startswith("_pytest."):
1653 tw.line()
1654 tw.sep("-", f"fixtures defined from {module}")
1655 currentmodule = module
1656 if verbose <= 0 and argname.startswith("_"):
1657 continue
1658 tw.write(f"{argname}", green=True)
1659 if fixturedef.scope != "function":
1660 tw.write(" [%s scope]" % fixturedef.scope, cyan=True)
1661 tw.write(f" -- {prettypath}", yellow=True)
1662 tw.write("\n")
1663 doc = inspect.getdoc(fixturedef.func)
1664 if doc:
1665 write_docstring(tw, doc.split("\n\n")[0] if verbose <= 0 else doc)
1666 else:
1667 tw.line(" no docstring available", red=True)
1668 tw.line()
1671def write_docstring(tw: TerminalWriter, doc: str, indent: str = " ") -> None:
1672 for line in doc.split("\n"):
1673 tw.line(indent + line)
1676class Function(PyobjMixin, nodes.Item):
1677 """An Item responsible for setting up and executing a Python test function.
1679 :param name:
1680 The full function name, including any decorations like those
1681 added by parametrization (``my_func[my_param]``).
1682 :param parent:
1683 The parent Node.
1684 :param config:
1685 The pytest Config object.
1686 :param callspec:
1687 If given, this is function has been parametrized and the callspec contains
1688 meta information about the parametrization.
1689 :param callobj:
1690 If given, the object which will be called when the Function is invoked,
1691 otherwise the callobj will be obtained from ``parent`` using ``originalname``.
1692 :param keywords:
1693 Keywords bound to the function object for "-k" matching.
1694 :param session:
1695 The pytest Session object.
1696 :param fixtureinfo:
1697 Fixture information already resolved at this fixture node..
1698 :param originalname:
1699 The attribute name to use for accessing the underlying function object.
1700 Defaults to ``name``. Set this if name is different from the original name,
1701 for example when it contains decorations like those added by parametrization
1702 (``my_func[my_param]``).
1703 """
1705 # Disable since functions handle it themselves.
1706 _ALLOW_MARKERS = False
1708 def __init__(
1709 self,
1710 name: str,
1711 parent,
1712 config: Optional[Config] = None,
1713 callspec: Optional[CallSpec2] = None,
1714 callobj=NOTSET,
1715 keywords: Optional[Mapping[str, Any]] = None,
1716 session: Optional[Session] = None,
1717 fixtureinfo: Optional[FuncFixtureInfo] = None,
1718 originalname: Optional[str] = None,
1719 ) -> None:
1720 super().__init__(name, parent, config=config, session=session)
1722 if callobj is not NOTSET:
1723 self.obj = callobj
1725 #: Original function name, without any decorations (for example
1726 #: parametrization adds a ``"[...]"`` suffix to function names), used to access
1727 #: the underlying function object from ``parent`` (in case ``callobj`` is not given
1728 #: explicitly).
1729 #:
1730 #: .. versionadded:: 3.0
1731 self.originalname = originalname or name
1733 # Note: when FunctionDefinition is introduced, we should change ``originalname``
1734 # to a readonly property that returns FunctionDefinition.name.
1736 self.own_markers.extend(get_unpacked_marks(self.obj))
1737 if callspec:
1738 self.callspec = callspec
1739 self.own_markers.extend(callspec.marks)
1741 # todo: this is a hell of a hack
1742 # https://github.com/pytest-dev/pytest/issues/4569
1743 # Note: the order of the updates is important here; indicates what
1744 # takes priority (ctor argument over function attributes over markers).
1745 # Take own_markers only; NodeKeywords handles parent traversal on its own.
1746 self.keywords.update((mark.name, mark) for mark in self.own_markers)
1747 self.keywords.update(self.obj.__dict__)
1748 if keywords:
1749 self.keywords.update(keywords)
1751 if fixtureinfo is None:
1752 fixtureinfo = self.session._fixturemanager.getfixtureinfo(
1753 self, self.obj, self.cls, funcargs=True
1754 )
1755 self._fixtureinfo: FuncFixtureInfo = fixtureinfo
1756 self.fixturenames = fixtureinfo.names_closure
1757 self._initrequest()
1759 @classmethod
1760 def from_parent(cls, parent, **kw): # todo: determine sound type limitations
1761 """The public constructor."""
1762 return super().from_parent(parent=parent, **kw)
1764 def _initrequest(self) -> None:
1765 self.funcargs: Dict[str, object] = {}
1766 self._request = fixtures.FixtureRequest(self, _ispytest=True)
1768 @property
1769 def function(self):
1770 """Underlying python 'function' object."""
1771 return getimfunc(self.obj)
1773 def _getobj(self):
1774 assert self.parent is not None
1775 if isinstance(self.parent, Class):
1776 # Each Function gets a fresh class instance.
1777 parent_obj = self.parent.newinstance()
1778 else:
1779 parent_obj = self.parent.obj # type: ignore[attr-defined]
1780 return getattr(parent_obj, self.originalname)
1782 @property
1783 def _pyfuncitem(self):
1784 """(compatonly) for code expecting pytest-2.2 style request objects."""
1785 return self
1787 def runtest(self) -> None:
1788 """Execute the underlying test function."""
1789 self.ihook.pytest_pyfunc_call(pyfuncitem=self)
1791 def setup(self) -> None:
1792 self._request._fillfixtures()
1794 def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:
1795 if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False):
1796 code = _pytest._code.Code.from_function(get_real_func(self.obj))
1797 path, firstlineno = code.path, code.firstlineno
1798 traceback = excinfo.traceback
1799 ntraceback = traceback.cut(path=path, firstlineno=firstlineno)
1800 if ntraceback == traceback:
1801 ntraceback = ntraceback.cut(path=path)
1802 if ntraceback == traceback:
1803 ntraceback = ntraceback.filter(filter_traceback)
1804 if not ntraceback:
1805 ntraceback = traceback
1807 excinfo.traceback = ntraceback.filter()
1808 # issue364: mark all but first and last frames to
1809 # only show a single-line message for each frame.
1810 if self.config.getoption("tbstyle", "auto") == "auto":
1811 if len(excinfo.traceback) > 2:
1812 for entry in excinfo.traceback[1:-1]:
1813 entry.set_repr_style("short")
1815 # TODO: Type ignored -- breaks Liskov Substitution.
1816 def repr_failure( # type: ignore[override]
1817 self,
1818 excinfo: ExceptionInfo[BaseException],
1819 ) -> Union[str, TerminalRepr]:
1820 style = self.config.getoption("tbstyle", "auto")
1821 if style == "auto":
1822 style = "long"
1823 return self._repr_failure_py(excinfo, style=style)
1826class FunctionDefinition(Function):
1827 """
1828 This class is a step gap solution until we evolve to have actual function definition nodes
1829 and manage to get rid of ``metafunc``.
1830 """
1832 def runtest(self) -> None:
1833 raise RuntimeError("function definitions are not supposed to be run as tests")
1835 setup = runtest