def test_funcarg_non_pycollectobj(self, testdir, recwarn) -> None: # rough jstests usage testdir.makeconftest(""" import pytest def pytest_pycollect_makeitem(collector, name, obj): if name == "MyClass": return MyCollector.from_parent(collector, name=name) class MyCollector(pytest.Collector): def reportinfo(self): return self.fspath, 3, "xyz" """) modcol = testdir.getmodulecol(""" import pytest @pytest.fixture def arg1(request): return 42 class MyClass(object): pass """) # this hook finds funcarg factories rep = runner.collect_one_node(collector=modcol) # TODO: Don't treat as Any. clscol: Any = rep.result[0] clscol.obj = lambda arg1: None clscol.funcargs = {} pytest._fillfuncargs(clscol) assert clscol.funcargs["arg1"] == 42
def test_funcarg_non_pycollectobj(self, testdir): # rough jstests usage testdir.makeconftest(""" import pytest def pytest_pycollect_makeitem(collector, name, obj): if name == "MyClass": return MyCollector(name, parent=collector) class MyCollector(pytest.Collector): def reportinfo(self): return self.fspath, 3, "xyz" """) modcol = testdir.getmodulecol(""" import pytest @pytest.fixture def arg1(request): return 42 class MyClass(object): pass """) # this hook finds funcarg factories rep = runner.collect_one_node(collector=modcol) clscol = rep.result[0] clscol.obj = lambda arg1: None clscol.funcargs = {} pytest._fillfuncargs(clscol) assert clscol.funcargs['arg1'] == 42
def _perform_collect(self, args, genitems): if args is None: args = self.config.args self.trace("perform_collect", self, args) self.trace.root.indent += 1 self._notfound = [] self._initialpaths = set() self._initialparts = [] self.items = items = [] for arg in args: parts = self._parsearg(arg) self._initialparts.append(parts) self._initialpaths.add(parts[0]) rep = collect_one_node(self) self.ihook.pytest_collectreport(report=rep) self.trace.root.indent -= 1 if self._notfound: errors = [] for arg, exc in self._notfound: line = "(no name %r in any of %r)" % (arg, exc.args[0]) errors.append("not found: %s\n%s" % (arg, line)) #XXX: test this raise pytest.UsageError(*errors) if not genitems: return rep.result else: if rep.passed: for node in rep.result: self.items.extend(self.genitems(node)) return items
def _matchnodes(self, matching, names): if not matching or not names: return matching name = names[0] assert name nextnames = names[1:] resultnodes = [] for node in matching: if isinstance(node, Item): if not names: resultnodes.append(node) continue assert isinstance(node, Collector) rep = collect_one_node(node) if rep.passed: has_matched = False for x in rep.result: # TODO: remove parametrized workaround once collection structure contains parametrization if x.name == name or x.name.split("[")[0] == name: resultnodes.extend(self.matchnodes([x], nextnames)) has_matched = True # XXX accept IDs that don't have "()" for class instances if not has_matched and len(rep.result) == 1 and x.name == "()": nextnames.insert(0, name) resultnodes.extend(self.matchnodes([x], nextnames)) else: # report collection failures here to avoid failing to run some test # specified in the command line because the module could not be # imported (#134) node.ihook.pytest_collectreport(report=rep) return resultnodes
def test_autouse_fixture(self, testdir, recwarn): # rough jstests usage testdir.makeconftest( """ import pytest def pytest_pycollect_makeitem(collector, name, obj): if name == "MyClass": return MyCollector.from_parent(collector, name=name) class MyCollector(pytest.Collector): def reportinfo(self): return self.fspath, 3, "xyz" """ ) modcol = testdir.getmodulecol( """ import pytest @pytest.fixture(autouse=True) def hello(): pass @pytest.fixture def arg1(request): return 42 class MyClass(object): pass """ ) # this hook finds funcarg factories rep = runner.collect_one_node(modcol) clscol = rep.result[0] clscol.obj = lambda: None clscol.funcargs = {} pytest._fillfuncargs(clscol) assert not clscol.funcargs
def _matchnodes(self, matching, names): if not matching or not names: return matching name = names[0] assert name nextnames = names[1:] resultnodes = [] for node in matching: if isinstance(node, pytest.Item): if not names: resultnodes.append(node) continue assert isinstance(node, pytest.Collector) rep = collect_one_node(node) if rep.passed: has_matched = False for x in rep.result: if x.name == name: resultnodes.extend(self.matchnodes([x], nextnames)) has_matched = True # XXX accept IDs that don't have "()" for class instances if not has_matched and len(rep.result) == 1 and x.name == "()": nextnames.insert(0, name) resultnodes.extend(self.matchnodes([x], nextnames)) node.ihook.pytest_collectreport(report=rep) return resultnodes
def _perform_collect(self, args, genitems): if args is None: args = self.config.args self.trace("perform_collect", self, args) self.trace.root.indent += 1 self._notfound = [] initialpaths = [] # type: List[py.path.local] self._initial_parts = [] # type: List[Tuple[py.path.local, List[str]]] self.items = items = [] for arg in args: fspath, parts = self._parsearg(arg) self._initial_parts.append((fspath, parts)) initialpaths.append(fspath) self._initialpaths = frozenset(initialpaths) rep = collect_one_node(self) self.ihook.pytest_collectreport(report=rep) self.trace.root.indent -= 1 if self._notfound: errors = [] for arg, exc in self._notfound: line = "(no name {!r} in any of {!r})".format(arg, exc.args[0]) errors.append("not found: {}\n{}".format(arg, line)) raise UsageError(*errors) if not genitems: return rep.result else: if rep.passed: for node in rep.result: self.items.extend(self.genitems(node)) return items
def genitems(self, node): self.trace("genitems", node) if isinstance(node, nodes.Item): node.ihook.pytest_itemcollected(item=node) yield node else: assert isinstance(node, nodes.Collector) rep = collect_one_node(node) if rep.passed: for subnode in rep.result: yield from self.genitems(subnode) node.ihook.pytest_collectreport(report=rep)
def genitems(self, node): self.trace("genitems", node) if isinstance(node, pytest.Item): node.ihook.pytest_itemcollected(item=node) yield node else: assert isinstance(node, pytest.Collector) rep = collect_one_node(node) if rep.passed: for subnode in rep.result: for x in self.genitems(subnode): yield x node.ihook.pytest_collectreport(report=rep)
def test_collect_result(self, testdir): col = testdir.getmodulecol(""" def test_func1(): pass class TestClass: pass """) rep = runner.collect_one_node(col) assert not rep.failed assert not rep.skipped assert rep.passed locinfo = rep.location assert locinfo[0] == col.fspath.basename assert not locinfo[1] assert locinfo[2] == col.fspath.basename res = rep.result assert len(res) == 2 assert res[0].name == "test_func1" assert res[1].name == "TestClass"
def test_collect_result(self, pytester: Pytester) -> None: col = pytester.getmodulecol(""" def test_func1(): pass class TestClass(object): pass """) rep = runner.collect_one_node(col) assert not rep.failed assert not rep.skipped assert rep.passed locinfo = rep.location assert locinfo[0] == col.fspath.basename assert not locinfo[1] assert locinfo[2] == col.fspath.basename res = rep.result assert len(res) == 2 assert res[0].name == "test_func1" assert res[1].name == "TestClass"
def _matchnodes( self, matching: Sequence[Union[nodes.Item, nodes.Collector]], names: List[str], ) -> Sequence[Union[nodes.Item, nodes.Collector]]: if not matching or not names: return matching name = names[0] assert name nextnames = names[1:] resultnodes = [] # type: List[Union[nodes.Item, nodes.Collector]] for node in matching: if isinstance(node, nodes.Item): if not names: resultnodes.append(node) continue assert isinstance(node, nodes.Collector) key = (type(node), node.nodeid) if key in self._collection_node_cache3: rep = self._collection_node_cache3[key] else: rep = collect_one_node(node) self._collection_node_cache3[key] = rep if rep.passed: has_matched = False for x in rep.result: # TODO: Remove parametrized workaround once collection structure contains parametrization. if x.name == name or x.name.split("[")[0] == name: resultnodes.extend(self.matchnodes([x], nextnames)) has_matched = True # XXX Accept IDs that don't have "()" for class instances. if not has_matched and len(rep.result) == 1 and x.name == "()": nextnames.insert(0, name) resultnodes.extend(self.matchnodes([x], nextnames)) else: # Report collection failures here to avoid failing to run some test # specified in the command line because the module could not be # imported (#134). node.ihook.pytest_collectreport(report=rep) return resultnodes
def test_funcarg_non_pycollectobj(self, testdir): # rough jstests usage testdir.makeconftest(""" import pytest def pytest_pycollect_makeitem(collector, name, obj): if name == "MyClass": return MyCollector(name, parent=collector) class MyCollector(pytest.Collector): def reportinfo(self): return self.fspath, 3, "xyz" """) modcol = testdir.getmodulecol(""" def pytest_funcarg__arg1(request): return 42 class MyClass: pass """) # this hook finds funcarg factories rep = runner.collect_one_node(collector=modcol) clscol = rep.result[0] clscol.obj = lambda arg1: None clscol.funcargs = {} pytest._fillfuncargs(clscol) assert clscol.funcargs['arg1'] == 42
def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: from _pytest.python import Package # Keep track of any collected nodes in here, so we don't duplicate fixtures. node_cache1: Dict[py.path.local, Sequence[nodes.Collector]] = {} node_cache2: Dict[ Tuple[Type[nodes.Collector], py.path.local], nodes.Collector ] = ({}) # Keep track of any collected collectors in matchnodes paths, so they # are not collected more than once. matchnodes_cache: Dict[Tuple[Type[nodes.Collector], str], CollectReport] = ({}) # Dirnames of pkgs with dunder-init files. pkg_roots: Dict[str, Package] = {} for argpath, names in self._initial_parts: self.trace("processing argument", (argpath, names)) self.trace.root.indent += 1 # Start with a Session root, and delve to argpath item (dir or file) # and stack all Packages found on the way. # No point in finding packages when collecting doctests. if not self.config.getoption("doctestmodules", False): pm = self.config.pluginmanager confcutdir = pm._confcutdir for parent in (argpath, *argpath.parents): if confcutdir and parent in confcutdir.parents: break if parent.is_dir(): pkginit = py.path.local(parent / "__init__.py") if pkginit.isfile() and pkginit not in node_cache1: col = self._collectfile(pkginit, handle_dupes=False) if col: if isinstance(col[0], Package): pkg_roots[str(parent)] = col[0] node_cache1[col[0].fspath] = [col[0]] # If it's a directory argument, recurse and look for any Subpackages. # Let the Package collector deal with subnodes, don't collect here. if argpath.is_dir(): assert not names, "invalid arg {!r}".format((argpath, names)) seen_dirs: Set[py.path.local] = set() for direntry in visit(str(argpath), self._recurse): if not direntry.is_file(): continue path = py.path.local(direntry.path) dirpath = path.dirpath() if dirpath not in seen_dirs: # Collect packages first. seen_dirs.add(dirpath) pkginit = dirpath.join("__init__.py") if pkginit.exists(): for x in self._collectfile(pkginit): yield x if isinstance(x, Package): pkg_roots[str(dirpath)] = x if str(dirpath) in pkg_roots: # Do not collect packages here. continue for x in self._collectfile(path): key = (type(x), x.fspath) if key in node_cache2: yield node_cache2[key] else: node_cache2[key] = x yield x else: assert argpath.is_file() argpath_ = py.path.local(argpath) if argpath_ in node_cache1: col = node_cache1[argpath_] else: collect_root = pkg_roots.get(argpath_.dirname, self) col = collect_root._collectfile(argpath_, handle_dupes=False) if col: node_cache1[argpath_] = col matching = [] work: List[ Tuple[Sequence[Union[nodes.Item, nodes.Collector]], Sequence[str]] ] = [(col, names)] while work: self.trace("matchnodes", col, names) self.trace.root.indent += 1 matchnodes, matchnames = work.pop() for node in matchnodes: if not matchnames: matching.append(node) continue if not isinstance(node, nodes.Collector): continue key = (type(node), node.nodeid) if key in matchnodes_cache: rep = matchnodes_cache[key] else: rep = collect_one_node(node) matchnodes_cache[key] = rep if rep.passed: submatchnodes = [] for r in rep.result: # TODO: Remove parametrized workaround once collection structure contains # parametrization. if ( r.name == matchnames[0] or r.name.split("[")[0] == matchnames[0] ): submatchnodes.append(r) if submatchnodes: work.append((submatchnodes, matchnames[1:])) # XXX Accept IDs that don't have "()" for class instances. elif len(rep.result) == 1 and rep.result[0].name == "()": work.append((rep.result, matchnames)) else: # Report collection failures here to avoid failing to run some test # specified in the command line because the module could not be # imported (#134). node.ihook.pytest_collectreport(report=rep) self.trace("matchnodes finished -> ", len(matching), "nodes") self.trace.root.indent -= 1 if not matching: report_arg = "::".join((str(argpath), *names)) self._notfound.append((report_arg, col)) continue # If __init__.py was the only file requested, then the matched # node will be the corresponding Package (by default), and the # first yielded item will be the __init__ Module itself, so # just use that. If this special case isn't taken, then all the # files in the package will be yielded. if argpath.name == "__init__.py" and isinstance(matching[0], Package): try: yield next(iter(matching[0].collect())) except StopIteration: # The package collects nothing with only an __init__.py # file in it, which gets ignored by the default # "python_files" option. pass continue yield from matching self.trace.root.indent -= 1
def perform_collect( self, args: Optional[Sequence[str]] = None, genitems: bool = True ) -> Sequence[Union[nodes.Item, nodes.Collector]]: """Perform the collection phase for this session. This is called by the default :func:`pytest_collection <_pytest.hookspec.pytest_collection>` hook implementation; see the documentation of this hook for more details. For testing purposes, it may also be called directly on a fresh ``Session``. This function normally recursively expands any collectors collected from the session to their items, and only items are returned. For testing purposes, this may be suppressed by passing ``genitems=False``, in which case the return value contains these collectors unexpanded, and ``session.items`` is empty. """ if args is None: args = self.config.args self.trace("perform_collect", self, args) self.trace.root.indent += 1 self._notfound: List[Tuple[str, Sequence[nodes.Collector]]] = [] self._initial_parts: List[Tuple[Path, List[str]]] = [] self.items: List[nodes.Item] = [] hook = self.config.hook items: Sequence[Union[nodes.Item, nodes.Collector]] = self.items try: initialpaths: List[Path] = [] for arg in args: fspath, parts = resolve_collection_argument( self.config.invocation_params.dir, arg, as_pypath=self.config.option.pyargs, ) self._initial_parts.append((fspath, parts)) initialpaths.append(fspath) self._initialpaths = frozenset(initialpaths) rep = collect_one_node(self) self.ihook.pytest_collectreport(report=rep) self.trace.root.indent -= 1 if self._notfound: errors = [] for arg, cols in self._notfound: line = f"(no name {arg!r} in any of {cols!r})" errors.append(f"not found: {arg}\n{line}") raise UsageError(*errors) if not genitems: items = rep.result else: if rep.passed: for node in rep.result: self.items.extend(self.genitems(node)) self.config.pluginmanager.check_pending() hook.pytest_collection_modifyitems( session=self, config=self.config, items=items ) finally: hook.pytest_collection_finish(session=self) self.testscollected = len(items) return items