Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/_pytest/main.py : 35%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1""" core implementation of testing process: init, session, runtest loop. """
2import argparse
3import fnmatch
4import functools
5import importlib
6import os
7import sys
8from typing import Callable
9from typing import Dict
10from typing import FrozenSet
11from typing import Iterator
12from typing import List
13from typing import Optional
14from typing import Sequence
15from typing import Set
16from typing import Tuple
17from typing import Union
19import attr
20import py
22import _pytest._code
23from _pytest import nodes
24from _pytest.compat import overload
25from _pytest.compat import TYPE_CHECKING
26from _pytest.config import Config
27from _pytest.config import directory_arg
28from _pytest.config import ExitCode
29from _pytest.config import hookimpl
30from _pytest.config import UsageError
31from _pytest.config.argparsing import Parser
32from _pytest.fixtures import FixtureManager
33from _pytest.outcomes import exit
34from _pytest.pathlib import Path
35from _pytest.reports import CollectReport
36from _pytest.reports import TestReport
37from _pytest.runner import collect_one_node
38from _pytest.runner import SetupState
41if TYPE_CHECKING:
42 from typing import Type
43 from typing_extensions import Literal
45 from _pytest.python import Package
48def pytest_addoption(parser: Parser) -> None:
49 parser.addini(
50 "norecursedirs",
51 "directory patterns to avoid for recursion",
52 type="args",
53 default=[".*", "build", "dist", "CVS", "_darcs", "{arch}", "*.egg", "venv"],
54 )
55 parser.addini(
56 "testpaths",
57 "directories to search for tests when no files or directories are given in the "
58 "command line.",
59 type="args",
60 default=[],
61 )
62 group = parser.getgroup("general", "running and selection options")
63 group._addoption(
64 "-x",
65 "--exitfirst",
66 action="store_const",
67 dest="maxfail",
68 const=1,
69 help="exit instantly on first error or failed test.",
70 )
71 group._addoption(
72 "--maxfail",
73 metavar="num",
74 action="store",
75 type=int,
76 dest="maxfail",
77 default=0,
78 help="exit after first num failures or errors.",
79 )
80 group._addoption(
81 "--strict-config",
82 action="store_true",
83 help="any warnings encountered while parsing the `pytest` section of the configuration file raise errors.",
84 )
85 group._addoption(
86 "--strict-markers",
87 "--strict",
88 action="store_true",
89 help="markers not registered in the `markers` section of the configuration file raise errors.",
90 )
91 group._addoption(
92 "-c",
93 metavar="file",
94 type=str,
95 dest="inifilename",
96 help="load configuration from `file` instead of trying to locate one of the implicit "
97 "configuration files.",
98 )
99 group._addoption(
100 "--continue-on-collection-errors",
101 action="store_true",
102 default=False,
103 dest="continue_on_collection_errors",
104 help="Force test execution even if collection errors occur.",
105 )
106 group._addoption(
107 "--rootdir",
108 action="store",
109 dest="rootdir",
110 help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', "
111 "'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: "
112 "'$HOME/root_dir'.",
113 )
115 group = parser.getgroup("collect", "collection")
116 group.addoption(
117 "--collectonly",
118 "--collect-only",
119 "--co",
120 action="store_true",
121 help="only collect tests, don't execute them.",
122 )
123 group.addoption(
124 "--pyargs",
125 action="store_true",
126 help="try to interpret all arguments as python packages.",
127 )
128 group.addoption(
129 "--ignore",
130 action="append",
131 metavar="path",
132 help="ignore path during collection (multi-allowed).",
133 )
134 group.addoption(
135 "--ignore-glob",
136 action="append",
137 metavar="path",
138 help="ignore path pattern during collection (multi-allowed).",
139 )
140 group.addoption(
141 "--deselect",
142 action="append",
143 metavar="nodeid_prefix",
144 help="deselect item (via node id prefix) during collection (multi-allowed).",
145 )
146 group.addoption(
147 "--confcutdir",
148 dest="confcutdir",
149 default=None,
150 metavar="dir",
151 type=functools.partial(directory_arg, optname="--confcutdir"),
152 help="only load conftest.py's relative to specified dir.",
153 )
154 group.addoption(
155 "--noconftest",
156 action="store_true",
157 dest="noconftest",
158 default=False,
159 help="Don't load any conftest.py files.",
160 )
161 group.addoption(
162 "--keepduplicates",
163 "--keep-duplicates",
164 action="store_true",
165 dest="keepduplicates",
166 default=False,
167 help="Keep duplicate tests.",
168 )
169 group.addoption(
170 "--collect-in-virtualenv",
171 action="store_true",
172 dest="collect_in_virtualenv",
173 default=False,
174 help="Don't ignore tests in a local virtualenv directory",
175 )
176 group.addoption(
177 "--import-mode",
178 default="prepend",
179 choices=["prepend", "append", "importlib"],
180 dest="importmode",
181 help="prepend/append to sys.path when importing test modules and conftest files, "
182 "default is to prepend.",
183 )
185 group = parser.getgroup("debugconfig", "test session debugging and configuration")
186 group.addoption(
187 "--basetemp",
188 dest="basetemp",
189 default=None,
190 type=validate_basetemp,
191 metavar="dir",
192 help=(
193 "base temporary directory for this test run."
194 "(warning: this directory is removed if it exists)"
195 ),
196 )
199def validate_basetemp(path: str) -> str:
200 # GH 7119
201 msg = "basetemp must not be empty, the current working directory or any parent directory of it"
203 # empty path
204 if not path:
205 raise argparse.ArgumentTypeError(msg)
207 def is_ancestor(base: Path, query: Path) -> bool:
208 """ return True if query is an ancestor of base, else False."""
209 if base == query:
210 return True
211 for parent in base.parents:
212 if parent == query:
213 return True
214 return False
216 # check if path is an ancestor of cwd
217 if is_ancestor(Path.cwd(), Path(path).absolute()):
218 raise argparse.ArgumentTypeError(msg)
220 # check symlinks for ancestors
221 if is_ancestor(Path.cwd().resolve(), Path(path).resolve()):
222 raise argparse.ArgumentTypeError(msg)
224 return path
227def wrap_session(
228 config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]]
229) -> Union[int, ExitCode]:
230 """Skeleton command line program"""
231 session = Session.from_config(config)
232 session.exitstatus = ExitCode.OK
233 initstate = 0
234 try:
235 try:
236 config._do_configure()
237 initstate = 1
238 config.hook.pytest_sessionstart(session=session)
239 initstate = 2
240 session.exitstatus = doit(config, session) or 0
241 except UsageError:
242 session.exitstatus = ExitCode.USAGE_ERROR
243 raise
244 except Failed:
245 session.exitstatus = ExitCode.TESTS_FAILED
246 except (KeyboardInterrupt, exit.Exception):
247 excinfo = _pytest._code.ExceptionInfo.from_current()
248 exitstatus = ExitCode.INTERRUPTED # type: Union[int, ExitCode]
249 if isinstance(excinfo.value, exit.Exception):
250 if excinfo.value.returncode is not None:
251 exitstatus = excinfo.value.returncode
252 if initstate < 2:
253 sys.stderr.write(
254 "{}: {}\n".format(excinfo.typename, excinfo.value.msg)
255 )
256 config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
257 session.exitstatus = exitstatus
258 except BaseException:
259 session.exitstatus = ExitCode.INTERNAL_ERROR
260 excinfo = _pytest._code.ExceptionInfo.from_current()
261 try:
262 config.notify_exception(excinfo, config.option)
263 except exit.Exception as exc:
264 if exc.returncode is not None:
265 session.exitstatus = exc.returncode
266 sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc))
267 else:
268 if isinstance(excinfo.value, SystemExit):
269 sys.stderr.write("mainloop: caught unexpected SystemExit!\n")
271 finally:
272 # Explicitly break reference cycle.
273 excinfo = None # type: ignore
274 session.startdir.chdir()
275 if initstate >= 2:
276 try:
277 config.hook.pytest_sessionfinish(
278 session=session, exitstatus=session.exitstatus
279 )
280 except exit.Exception as exc:
281 if exc.returncode is not None:
282 session.exitstatus = exc.returncode
283 sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc))
284 config._ensure_unconfigure()
285 return session.exitstatus
288def pytest_cmdline_main(config: Config) -> Union[int, ExitCode]:
289 return wrap_session(config, _main)
292def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]:
293 """ default command line protocol for initialization, session,
294 running tests and reporting. """
295 config.hook.pytest_collection(session=session)
296 config.hook.pytest_runtestloop(session=session)
298 if session.testsfailed:
299 return ExitCode.TESTS_FAILED
300 elif session.testscollected == 0:
301 return ExitCode.NO_TESTS_COLLECTED
302 return None
305def pytest_collection(session: "Session") -> None:
306 session.perform_collect()
309def pytest_runtestloop(session: "Session") -> bool:
310 if session.testsfailed and not session.config.option.continue_on_collection_errors:
311 raise session.Interrupted(
312 "%d error%s during collection"
313 % (session.testsfailed, "s" if session.testsfailed != 1 else "")
314 )
316 if session.config.option.collectonly:
317 return True
319 for i, item in enumerate(session.items):
320 nextitem = session.items[i + 1] if i + 1 < len(session.items) else None
321 item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
322 if session.shouldfail:
323 raise session.Failed(session.shouldfail)
324 if session.shouldstop:
325 raise session.Interrupted(session.shouldstop)
326 return True
329def _in_venv(path: py.path.local) -> bool:
330 """Attempts to detect if ``path`` is the root of a Virtual Environment by
331 checking for the existence of the appropriate activate script"""
332 bindir = path.join("Scripts" if sys.platform.startswith("win") else "bin")
333 if not bindir.isdir():
334 return False
335 activates = (
336 "activate",
337 "activate.csh",
338 "activate.fish",
339 "Activate",
340 "Activate.bat",
341 "Activate.ps1",
342 )
343 return any([fname.basename in activates for fname in bindir.listdir()])
346def pytest_ignore_collect(path: py.path.local, config: Config) -> Optional[bool]:
347 ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath())
348 ignore_paths = ignore_paths or []
349 excludeopt = config.getoption("ignore")
350 if excludeopt:
351 ignore_paths.extend([py.path.local(x) for x in excludeopt])
353 if py.path.local(path) in ignore_paths:
354 return True
356 ignore_globs = config._getconftest_pathlist(
357 "collect_ignore_glob", path=path.dirpath()
358 )
359 ignore_globs = ignore_globs or []
360 excludeglobopt = config.getoption("ignore_glob")
361 if excludeglobopt:
362 ignore_globs.extend([py.path.local(x) for x in excludeglobopt])
364 if any(fnmatch.fnmatch(str(path), str(glob)) for glob in ignore_globs):
365 return True
367 allow_in_venv = config.getoption("collect_in_virtualenv")
368 if not allow_in_venv and _in_venv(path):
369 return True
370 return None
373def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None:
374 deselect_prefixes = tuple(config.getoption("deselect") or [])
375 if not deselect_prefixes:
376 return
378 remaining = []
379 deselected = []
380 for colitem in items:
381 if colitem.nodeid.startswith(deselect_prefixes):
382 deselected.append(colitem)
383 else:
384 remaining.append(colitem)
386 if deselected:
387 config.hook.pytest_deselected(items=deselected)
388 items[:] = remaining
391class NoMatch(Exception):
392 """ raised if matching cannot locate a matching names. """
395class Interrupted(KeyboardInterrupt):
396 """ signals an interrupted test run. """
398 __module__ = "builtins" # for py3
401class Failed(Exception):
402 """ signals a stop as failed test run. """
405@attr.s
406class _bestrelpath_cache(dict):
407 path = attr.ib(type=py.path.local)
409 def __missing__(self, path: py.path.local) -> str:
410 r = self.path.bestrelpath(path) # type: str
411 self[path] = r
412 return r
415class Session(nodes.FSCollector):
416 Interrupted = Interrupted
417 Failed = Failed
418 # Set on the session by runner.pytest_sessionstart.
419 _setupstate = None # type: SetupState
420 # Set on the session by fixtures.pytest_sessionstart.
421 _fixturemanager = None # type: FixtureManager
422 exitstatus = None # type: Union[int, ExitCode]
424 def __init__(self, config: Config) -> None:
425 nodes.FSCollector.__init__(
426 self, config.rootdir, parent=None, config=config, session=self, nodeid=""
427 )
428 self.testsfailed = 0
429 self.testscollected = 0
430 self.shouldstop = False # type: Union[bool, str]
431 self.shouldfail = False # type: Union[bool, str]
432 self.trace = config.trace.root.get("collection")
433 self.startdir = config.invocation_dir
434 self._initialpaths = frozenset() # type: FrozenSet[py.path.local]
436 # Keep track of any collected nodes in here, so we don't duplicate fixtures
437 self._collection_node_cache1 = (
438 {}
439 ) # type: Dict[py.path.local, Sequence[nodes.Collector]]
440 self._collection_node_cache2 = (
441 {}
442 ) # type: Dict[Tuple[Type[nodes.Collector], py.path.local], nodes.Collector]
443 self._collection_node_cache3 = (
444 {}
445 ) # type: Dict[Tuple[Type[nodes.Collector], str], CollectReport]
447 # Dirnames of pkgs with dunder-init files.
448 self._collection_pkg_roots = {} # type: Dict[str, Package]
450 self._bestrelpathcache = _bestrelpath_cache(
451 config.rootdir
452 ) # type: Dict[py.path.local, str]
454 self.config.pluginmanager.register(self, name="session")
456 @classmethod
457 def from_config(cls, config: Config) -> "Session":
458 session = cls._create(config) # type: Session
459 return session
461 def __repr__(self) -> str:
462 return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % (
463 self.__class__.__name__,
464 self.name,
465 getattr(self, "exitstatus", "<UNSET>"),
466 self.testsfailed,
467 self.testscollected,
468 )
470 def _node_location_to_relpath(self, node_path: py.path.local) -> str:
471 # bestrelpath is a quite slow function
472 return self._bestrelpathcache[node_path]
474 @hookimpl(tryfirst=True)
475 def pytest_collectstart(self) -> None:
476 if self.shouldfail:
477 raise self.Failed(self.shouldfail)
478 if self.shouldstop:
479 raise self.Interrupted(self.shouldstop)
481 @hookimpl(tryfirst=True)
482 def pytest_runtest_logreport(
483 self, report: Union[TestReport, CollectReport]
484 ) -> None:
485 if report.failed and not hasattr(report, "wasxfail"):
486 self.testsfailed += 1
487 maxfail = self.config.getvalue("maxfail")
488 if maxfail and self.testsfailed >= maxfail:
489 self.shouldfail = "stopping after %d failures" % (self.testsfailed)
491 pytest_collectreport = pytest_runtest_logreport
493 def isinitpath(self, path: py.path.local) -> bool:
494 return path in self._initialpaths
496 def gethookproxy(self, fspath: py.path.local):
497 return super()._gethookproxy(fspath)
499 @overload
500 def perform_collect(
501 self, args: Optional[Sequence[str]] = ..., genitems: "Literal[True]" = ...
502 ) -> Sequence[nodes.Item]:
503 raise NotImplementedError()
505 @overload # noqa: F811
506 def perform_collect( # noqa: F811
507 self, args: Optional[Sequence[str]] = ..., genitems: bool = ...
508 ) -> Sequence[Union[nodes.Item, nodes.Collector]]:
509 raise NotImplementedError()
511 def perform_collect( # noqa: F811
512 self, args: Optional[Sequence[str]] = None, genitems: bool = True
513 ) -> Sequence[Union[nodes.Item, nodes.Collector]]:
514 hook = self.config.hook
515 try:
516 items = self._perform_collect(args, genitems)
517 self.config.pluginmanager.check_pending()
518 hook.pytest_collection_modifyitems(
519 session=self, config=self.config, items=items
520 )
521 finally:
522 hook.pytest_collection_finish(session=self)
523 self.testscollected = len(items)
524 return items
526 @overload
527 def _perform_collect(
528 self, args: Optional[Sequence[str]], genitems: "Literal[True]"
529 ) -> List[nodes.Item]:
530 raise NotImplementedError()
532 @overload # noqa: F811
533 def _perform_collect( # noqa: F811
534 self, args: Optional[Sequence[str]], genitems: bool
535 ) -> Union[List[Union[nodes.Item]], List[Union[nodes.Item, nodes.Collector]]]:
536 raise NotImplementedError()
538 def _perform_collect( # noqa: F811
539 self, args: Optional[Sequence[str]], genitems: bool
540 ) -> Union[List[Union[nodes.Item]], List[Union[nodes.Item, nodes.Collector]]]:
541 if args is None:
542 args = self.config.args
543 self.trace("perform_collect", self, args)
544 self.trace.root.indent += 1
545 self._notfound = [] # type: List[Tuple[str, NoMatch]]
546 initialpaths = [] # type: List[py.path.local]
547 self._initial_parts = [] # type: List[Tuple[py.path.local, List[str]]]
548 self.items = items = [] # type: List[nodes.Item]
549 for arg in args:
550 fspath, parts = self._parsearg(arg)
551 self._initial_parts.append((fspath, parts))
552 initialpaths.append(fspath)
553 self._initialpaths = frozenset(initialpaths)
554 rep = collect_one_node(self)
555 self.ihook.pytest_collectreport(report=rep)
556 self.trace.root.indent -= 1
557 if self._notfound:
558 errors = []
559 for arg, exc in self._notfound:
560 line = "(no name {!r} in any of {!r})".format(arg, exc.args[0])
561 errors.append("not found: {}\n{}".format(arg, line))
562 raise UsageError(*errors)
563 if not genitems:
564 return rep.result
565 else:
566 if rep.passed:
567 for node in rep.result:
568 self.items.extend(self.genitems(node))
569 return items
571 def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]:
572 for fspath, parts in self._initial_parts:
573 self.trace("processing argument", (fspath, parts))
574 self.trace.root.indent += 1
575 try:
576 yield from self._collect(fspath, parts)
577 except NoMatch as exc:
578 report_arg = "::".join((str(fspath), *parts))
579 # we are inside a make_report hook so
580 # we cannot directly pass through the exception
581 self._notfound.append((report_arg, exc))
583 self.trace.root.indent -= 1
584 self._collection_node_cache1.clear()
585 self._collection_node_cache2.clear()
586 self._collection_node_cache3.clear()
587 self._collection_pkg_roots.clear()
589 def _collect(
590 self, argpath: py.path.local, names: List[str]
591 ) -> Iterator[Union[nodes.Item, nodes.Collector]]:
592 from _pytest.python import Package
594 # Start with a Session root, and delve to argpath item (dir or file)
595 # and stack all Packages found on the way.
596 # No point in finding packages when collecting doctests
597 if not self.config.getoption("doctestmodules", False):
598 pm = self.config.pluginmanager
599 for parent in reversed(argpath.parts()):
600 if pm._confcutdir and pm._confcutdir.relto(parent):
601 break
603 if parent.isdir():
604 pkginit = parent.join("__init__.py")
605 if pkginit.isfile():
606 if pkginit not in self._collection_node_cache1:
607 col = self._collectfile(pkginit, handle_dupes=False)
608 if col:
609 if isinstance(col[0], Package):
610 self._collection_pkg_roots[str(parent)] = col[0]
611 # always store a list in the cache, matchnodes expects it
612 self._collection_node_cache1[col[0].fspath] = [col[0]]
614 # If it's a directory argument, recurse and look for any Subpackages.
615 # Let the Package collector deal with subnodes, don't collect here.
616 if argpath.check(dir=1):
617 assert not names, "invalid arg {!r}".format((argpath, names))
619 seen_dirs = set() # type: Set[py.path.local]
620 for path in argpath.visit(
621 fil=self._visit_filter, rec=self._recurse, bf=True, sort=True
622 ):
623 dirpath = path.dirpath()
624 if dirpath not in seen_dirs:
625 # Collect packages first.
626 seen_dirs.add(dirpath)
627 pkginit = dirpath.join("__init__.py")
628 if pkginit.exists():
629 for x in self._collectfile(pkginit):
630 yield x
631 if isinstance(x, Package):
632 self._collection_pkg_roots[str(dirpath)] = x
633 if str(dirpath) in self._collection_pkg_roots:
634 # Do not collect packages here.
635 continue
637 for x in self._collectfile(path):
638 key = (type(x), x.fspath)
639 if key in self._collection_node_cache2:
640 yield self._collection_node_cache2[key]
641 else:
642 self._collection_node_cache2[key] = x
643 yield x
644 else:
645 assert argpath.check(file=1)
647 if argpath in self._collection_node_cache1:
648 col = self._collection_node_cache1[argpath]
649 else:
650 collect_root = self._collection_pkg_roots.get(argpath.dirname, self)
651 col = collect_root._collectfile(argpath, handle_dupes=False)
652 if col:
653 self._collection_node_cache1[argpath] = col
654 m = self.matchnodes(col, names)
655 # If __init__.py was the only file requested, then the matched node will be
656 # the corresponding Package, and the first yielded item will be the __init__
657 # Module itself, so just use that. If this special case isn't taken, then all
658 # the files in the package will be yielded.
659 if argpath.basename == "__init__.py":
660 assert isinstance(m[0], nodes.Collector)
661 try:
662 yield next(iter(m[0].collect()))
663 except StopIteration:
664 # The package collects nothing with only an __init__.py
665 # file in it, which gets ignored by the default
666 # "python_files" option.
667 pass
668 return
669 yield from m
671 @staticmethod
672 def _visit_filter(f: py.path.local) -> bool:
673 # TODO: Remove type: ignore once `py` is typed.
674 return f.check(file=1) # type: ignore
676 def _tryconvertpyarg(self, x: str) -> str:
677 """Convert a dotted module name to path."""
678 try:
679 spec = importlib.util.find_spec(x)
680 # AttributeError: looks like package module, but actually filename
681 # ImportError: module does not exist
682 # ValueError: not a module name
683 except (AttributeError, ImportError, ValueError):
684 return x
685 if spec is None or spec.origin is None or spec.origin == "namespace":
686 return x
687 elif spec.submodule_search_locations:
688 return os.path.dirname(spec.origin)
689 else:
690 return spec.origin
692 def _parsearg(self, arg: str) -> Tuple[py.path.local, List[str]]:
693 """ return (fspath, names) tuple after checking the file exists. """
694 strpath, *parts = str(arg).split("::")
695 if self.config.option.pyargs:
696 strpath = self._tryconvertpyarg(strpath)
697 relpath = strpath.replace("/", os.sep)
698 fspath = self.config.invocation_dir.join(relpath, abs=True)
699 if not fspath.check():
700 if self.config.option.pyargs:
701 raise UsageError(
702 "file or package not found: " + arg + " (missing __init__.py?)"
703 )
704 raise UsageError("file not found: " + arg)
705 return (fspath, parts)
707 def matchnodes(
708 self, matching: Sequence[Union[nodes.Item, nodes.Collector]], names: List[str],
709 ) -> Sequence[Union[nodes.Item, nodes.Collector]]:
710 self.trace("matchnodes", matching, names)
711 self.trace.root.indent += 1
712 nodes = self._matchnodes(matching, names)
713 num = len(nodes)
714 self.trace("matchnodes finished -> ", num, "nodes")
715 self.trace.root.indent -= 1
716 if num == 0:
717 raise NoMatch(matching, names[:1])
718 return nodes
720 def _matchnodes(
721 self, matching: Sequence[Union[nodes.Item, nodes.Collector]], names: List[str],
722 ) -> Sequence[Union[nodes.Item, nodes.Collector]]:
723 if not matching or not names:
724 return matching
725 name = names[0]
726 assert name
727 nextnames = names[1:]
728 resultnodes = [] # type: List[Union[nodes.Item, nodes.Collector]]
729 for node in matching:
730 if isinstance(node, nodes.Item):
731 if not names:
732 resultnodes.append(node)
733 continue
734 assert isinstance(node, nodes.Collector)
735 key = (type(node), node.nodeid)
736 if key in self._collection_node_cache3:
737 rep = self._collection_node_cache3[key]
738 else:
739 rep = collect_one_node(node)
740 self._collection_node_cache3[key] = rep
741 if rep.passed:
742 has_matched = False
743 for x in rep.result:
744 # TODO: remove parametrized workaround once collection structure contains parametrization
745 if x.name == name or x.name.split("[")[0] == name:
746 resultnodes.extend(self.matchnodes([x], nextnames))
747 has_matched = True
748 # XXX accept IDs that don't have "()" for class instances
749 if not has_matched and len(rep.result) == 1 and x.name == "()":
750 nextnames.insert(0, name)
751 resultnodes.extend(self.matchnodes([x], nextnames))
752 else:
753 # report collection failures here to avoid failing to run some test
754 # specified in the command line because the module could not be
755 # imported (#134)
756 node.ihook.pytest_collectreport(report=rep)
757 return resultnodes
759 def genitems(
760 self, node: Union[nodes.Item, nodes.Collector]
761 ) -> Iterator[nodes.Item]:
762 self.trace("genitems", node)
763 if isinstance(node, nodes.Item):
764 node.ihook.pytest_itemcollected(item=node)
765 yield node
766 else:
767 assert isinstance(node, nodes.Collector)
768 rep = collect_one_node(node)
769 if rep.passed:
770 for subnode in rep.result:
771 yield from self.genitems(subnode)
772 node.ihook.pytest_collectreport(report=rep)