1""" core implementation of testing process: init, session, runtest loop. """ 2from __future__ import absolute_import, division, print_function 3 4import functools 5import os 6import sys 7 8import _pytest 9import _pytest._code 10import py 11try: 12 from collections import MutableMapping as MappingMixin 13except ImportError: 14 from UserDict import DictMixin as MappingMixin 15 16from _pytest.config import directory_arg, UsageError, hookimpl 17from _pytest.runner import collect_one_node, exit 18 19tracebackcutdir = py.path.local(_pytest.__file__).dirpath() 20 21# exitcodes for the command line 22EXIT_OK = 0 23EXIT_TESTSFAILED = 1 24EXIT_INTERRUPTED = 2 25EXIT_INTERNALERROR = 3 26EXIT_USAGEERROR = 4 27EXIT_NOTESTSCOLLECTED = 5 28 29 30def pytest_addoption(parser): 31 parser.addini("norecursedirs", "directory patterns to avoid for recursion", 32 type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv']) 33 parser.addini("testpaths", "directories to search for tests when no files or directories are given in the command line.", 34 type="args", default=[]) 35 #parser.addini("dirpatterns", 36 # "patterns specifying possible locations of test files", 37 # type="linelist", default=["**/test_*.txt", 38 # "**/test_*.py", "**/*_test.py"] 39 #) 40 group = parser.getgroup("general", "running and selection options") 41 group._addoption('-x', '--exitfirst', action="store_const", 42 dest="maxfail", const=1, 43 help="exit instantly on first error or failed test."), 44 group._addoption('--maxfail', metavar="num", 45 action="store", type=int, dest="maxfail", default=0, 46 help="exit after first num failures or errors.") 47 group._addoption('--strict', action="store_true", 48 help="run pytest in strict mode, warnings become errors.") 49 group._addoption("-c", metavar="file", type=str, dest="inifilename", 50 help="load configuration from `file` instead of trying to locate one of the implicit configuration files.") 51 group._addoption("--continue-on-collection-errors", action="store_true", 52 default=False, dest="continue_on_collection_errors", 53 help="Force test execution even if collection errors occur.") 54 55 group = parser.getgroup("collect", "collection") 56 group.addoption('--collectonly', '--collect-only', action="store_true", 57 help="only collect tests, don't execute them."), 58 group.addoption('--pyargs', action="store_true", 59 help="try to interpret all arguments as python packages.") 60 group.addoption("--ignore", action="append", metavar="path", 61 help="ignore path during collection (multi-allowed).") 62 # when changing this to --conf-cut-dir, config.py Conftest.setinitial 63 # needs upgrading as well 64 group.addoption('--confcutdir', dest="confcutdir", default=None, 65 metavar="dir", type=functools.partial(directory_arg, optname="--confcutdir"), 66 help="only load conftest.py's relative to specified dir.") 67 group.addoption('--noconftest', action="store_true", 68 dest="noconftest", default=False, 69 help="Don't load any conftest.py files.") 70 group.addoption('--keepduplicates', '--keep-duplicates', action="store_true", 71 dest="keepduplicates", default=False, 72 help="Keep duplicate tests.") 73 74 group = parser.getgroup("debugconfig", 75 "test session debugging and configuration") 76 group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir", 77 help="base temporary directory for this test run.") 78 79 80 81def pytest_namespace(): 82 """keeping this one works around a deeper startup issue in pytest 83 84 i tried to find it for a while but the amount of time turned unsustainable, 85 so i put a hack in to revisit later 86 """ 87 return {} 88 89 90def pytest_configure(config): 91 __import__('pytest').config = config # compatibiltiy 92 93 94def wrap_session(config, doit): 95 """Skeleton command line program""" 96 session = Session(config) 97 session.exitstatus = EXIT_OK 98 initstate = 0 99 try: 100 try: 101 config._do_configure() 102 initstate = 1 103 config.hook.pytest_sessionstart(session=session) 104 initstate = 2 105 session.exitstatus = doit(config, session) or 0 106 except UsageError: 107 raise 108 except KeyboardInterrupt: 109 excinfo = _pytest._code.ExceptionInfo() 110 if initstate < 2 and isinstance(excinfo.value, exit.Exception): 111 sys.stderr.write('{0}: {1}\n'.format( 112 excinfo.typename, excinfo.value.msg)) 113 config.hook.pytest_keyboard_interrupt(excinfo=excinfo) 114 session.exitstatus = EXIT_INTERRUPTED 115 except: 116 excinfo = _pytest._code.ExceptionInfo() 117 config.notify_exception(excinfo, config.option) 118 session.exitstatus = EXIT_INTERNALERROR 119 if excinfo.errisinstance(SystemExit): 120 sys.stderr.write("mainloop: caught Spurious SystemExit!\n") 121 122 finally: 123 excinfo = None # Explicitly break reference cycle. 124 session.startdir.chdir() 125 if initstate >= 2: 126 config.hook.pytest_sessionfinish( 127 session=session, 128 exitstatus=session.exitstatus) 129 config._ensure_unconfigure() 130 return session.exitstatus 131 132 133def pytest_cmdline_main(config): 134 return wrap_session(config, _main) 135 136 137def _main(config, session): 138 """ default command line protocol for initialization, session, 139 running tests and reporting. """ 140 config.hook.pytest_collection(session=session) 141 config.hook.pytest_runtestloop(session=session) 142 143 if session.testsfailed: 144 return EXIT_TESTSFAILED 145 elif session.testscollected == 0: 146 return EXIT_NOTESTSCOLLECTED 147 148 149def pytest_collection(session): 150 return session.perform_collect() 151 152 153def pytest_runtestloop(session): 154 if (session.testsfailed and 155 not session.config.option.continue_on_collection_errors): 156 raise session.Interrupted( 157 "%d errors during collection" % session.testsfailed) 158 159 if session.config.option.collectonly: 160 return True 161 162 for i, item in enumerate(session.items): 163 nextitem = session.items[i+1] if i+1 < len(session.items) else None 164 item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) 165 if session.shouldstop: 166 raise session.Interrupted(session.shouldstop) 167 return True 168 169 170def pytest_ignore_collect(path, config): 171 ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath()) 172 ignore_paths = ignore_paths or [] 173 excludeopt = config.getoption("ignore") 174 if excludeopt: 175 ignore_paths.extend([py.path.local(x) for x in excludeopt]) 176 177 if py.path.local(path) in ignore_paths: 178 return True 179 180 # Skip duplicate paths. 181 keepduplicates = config.getoption("keepduplicates") 182 duplicate_paths = config.pluginmanager._duplicatepaths 183 if not keepduplicates: 184 if path in duplicate_paths: 185 return True 186 else: 187 duplicate_paths.add(path) 188 189 return False 190 191 192class FSHookProxy: 193 def __init__(self, fspath, pm, remove_mods): 194 self.fspath = fspath 195 self.pm = pm 196 self.remove_mods = remove_mods 197 198 def __getattr__(self, name): 199 x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods) 200 self.__dict__[name] = x 201 return x 202 203class _CompatProperty(object): 204 def __init__(self, name): 205 self.name = name 206 207 def __get__(self, obj, owner): 208 if obj is None: 209 return self 210 211 # TODO: reenable in the features branch 212 # warnings.warn( 213 # "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format( 214 # name=self.name, owner=type(owner).__name__), 215 # PendingDeprecationWarning, stacklevel=2) 216 return getattr(__import__('pytest'), self.name) 217 218 219 220class NodeKeywords(MappingMixin): 221 def __init__(self, node): 222 self.node = node 223 self.parent = node.parent 224 self._markers = {node.name: True} 225 226 def __getitem__(self, key): 227 try: 228 return self._markers[key] 229 except KeyError: 230 if self.parent is None: 231 raise 232 return self.parent.keywords[key] 233 234 def __setitem__(self, key, value): 235 self._markers[key] = value 236 237 def __delitem__(self, key): 238 raise ValueError("cannot delete key in keywords dict") 239 240 def __iter__(self): 241 seen = set(self._markers) 242 if self.parent is not None: 243 seen.update(self.parent.keywords) 244 return iter(seen) 245 246 def __len__(self): 247 return len(self.__iter__()) 248 249 def keys(self): 250 return list(self) 251 252 def __repr__(self): 253 return "<NodeKeywords for node %s>" % (self.node, ) 254 255 256class Node(object): 257 """ base class for Collector and Item the test collection tree. 258 Collector subclasses have children, Items are terminal nodes.""" 259 260 def __init__(self, name, parent=None, config=None, session=None): 261 #: a unique name within the scope of the parent node 262 self.name = name 263 264 #: the parent collector node. 265 self.parent = parent 266 267 #: the pytest config object 268 self.config = config or parent.config 269 270 #: the session this node is part of 271 self.session = session or parent.session 272 273 #: filesystem path where this node was collected from (can be None) 274 self.fspath = getattr(parent, 'fspath', None) 275 276 #: keywords/markers collected from all scopes 277 self.keywords = NodeKeywords(self) 278 279 #: allow adding of extra keywords to use for matching 280 self.extra_keyword_matches = set() 281 282 # used for storing artificial fixturedefs for direct parametrization 283 self._name2pseudofixturedef = {} 284 285 @property 286 def ihook(self): 287 """ fspath sensitive hook proxy used to call pytest hooks""" 288 return self.session.gethookproxy(self.fspath) 289 290 Module = _CompatProperty("Module") 291 Class = _CompatProperty("Class") 292 Instance = _CompatProperty("Instance") 293 Function = _CompatProperty("Function") 294 File = _CompatProperty("File") 295 Item = _CompatProperty("Item") 296 297 def _getcustomclass(self, name): 298 maybe_compatprop = getattr(type(self), name) 299 if isinstance(maybe_compatprop, _CompatProperty): 300 return getattr(__import__('pytest'), name) 301 else: 302 cls = getattr(self, name) 303 # TODO: reenable in the features branch 304 # warnings.warn("use of node.%s is deprecated, " 305 # "use pytest_pycollect_makeitem(...) to create custom " 306 # "collection nodes" % name, category=DeprecationWarning) 307 return cls 308 309 def __repr__(self): 310 return "<%s %r>" %(self.__class__.__name__, 311 getattr(self, 'name', None)) 312 313 def warn(self, code, message): 314 """ generate a warning with the given code and message for this 315 item. """ 316 assert isinstance(code, str) 317 fslocation = getattr(self, "location", None) 318 if fslocation is None: 319 fslocation = getattr(self, "fspath", None) 320 self.ihook.pytest_logwarning.call_historic(kwargs=dict( 321 code=code, message=message, 322 nodeid=self.nodeid, fslocation=fslocation)) 323 324 # methods for ordering nodes 325 @property 326 def nodeid(self): 327 """ a ::-separated string denoting its collection tree address. """ 328 try: 329 return self._nodeid 330 except AttributeError: 331 self._nodeid = x = self._makeid() 332 return x 333 334 def _makeid(self): 335 return self.parent.nodeid + "::" + self.name 336 337 def __hash__(self): 338 return hash(self.nodeid) 339 340 def setup(self): 341 pass 342 343 def teardown(self): 344 pass 345 346 def _memoizedcall(self, attrname, function): 347 exattrname = "_ex_" + attrname 348 failure = getattr(self, exattrname, None) 349 if failure is not None: 350 py.builtin._reraise(failure[0], failure[1], failure[2]) 351 if hasattr(self, attrname): 352 return getattr(self, attrname) 353 try: 354 res = function() 355 except py.builtin._sysex: 356 raise 357 except: 358 failure = sys.exc_info() 359 setattr(self, exattrname, failure) 360 raise 361 setattr(self, attrname, res) 362 return res 363 364 def listchain(self): 365 """ return list of all parent collectors up to self, 366 starting from root of collection tree. """ 367 chain = [] 368 item = self 369 while item is not None: 370 chain.append(item) 371 item = item.parent 372 chain.reverse() 373 return chain 374 375 def add_marker(self, marker): 376 """ dynamically add a marker object to the node. 377 378 ``marker`` can be a string or pytest.mark.* instance. 379 """ 380 from _pytest.mark import MarkDecorator, MARK_GEN 381 if isinstance(marker, py.builtin._basestring): 382 marker = getattr(MARK_GEN, marker) 383 elif not isinstance(marker, MarkDecorator): 384 raise ValueError("is not a string or pytest.mark.* Marker") 385 self.keywords[marker.name] = marker 386 387 def get_marker(self, name): 388 """ get a marker object from this node or None if 389 the node doesn't have a marker with that name. """ 390 val = self.keywords.get(name, None) 391 if val is not None: 392 from _pytest.mark import MarkInfo, MarkDecorator 393 if isinstance(val, (MarkDecorator, MarkInfo)): 394 return val 395 396 def listextrakeywords(self): 397 """ Return a set of all extra keywords in self and any parents.""" 398 extra_keywords = set() 399 item = self 400 for item in self.listchain(): 401 extra_keywords.update(item.extra_keyword_matches) 402 return extra_keywords 403 404 def listnames(self): 405 return [x.name for x in self.listchain()] 406 407 def addfinalizer(self, fin): 408 """ register a function to be called when this node is finalized. 409 410 This method can only be called when this node is active 411 in a setup chain, for example during self.setup(). 412 """ 413 self.session._setupstate.addfinalizer(fin, self) 414 415 def getparent(self, cls): 416 """ get the next parent node (including ourself) 417 which is an instance of the given class""" 418 current = self 419 while current and not isinstance(current, cls): 420 current = current.parent 421 return current 422 423 def _prunetraceback(self, excinfo): 424 pass 425 426 def _repr_failure_py(self, excinfo, style=None): 427 fm = self.session._fixturemanager 428 if excinfo.errisinstance(fm.FixtureLookupError): 429 return excinfo.value.formatrepr() 430 tbfilter = True 431 if self.config.option.fulltrace: 432 style="long" 433 else: 434 tb = _pytest._code.Traceback([excinfo.traceback[-1]]) 435 self._prunetraceback(excinfo) 436 if len(excinfo.traceback) == 0: 437 excinfo.traceback = tb 438 tbfilter = False # prunetraceback already does it 439 if style == "auto": 440 style = "long" 441 # XXX should excinfo.getrepr record all data and toterminal() process it? 442 if style is None: 443 if self.config.option.tbstyle == "short": 444 style = "short" 445 else: 446 style = "long" 447 448 try: 449 os.getcwd() 450 abspath = False 451 except OSError: 452 abspath = True 453 454 return excinfo.getrepr(funcargs=True, abspath=abspath, 455 showlocals=self.config.option.showlocals, 456 style=style, tbfilter=tbfilter) 457 458 repr_failure = _repr_failure_py 459 460class Collector(Node): 461 """ Collector instances create children through collect() 462 and thus iteratively build a tree. 463 """ 464 465 class CollectError(Exception): 466 """ an error during collection, contains a custom message. """ 467 468 def collect(self): 469 """ returns a list of children (items and collectors) 470 for this collection node. 471 """ 472 raise NotImplementedError("abstract") 473 474 def repr_failure(self, excinfo): 475 """ represent a collection failure. """ 476 if excinfo.errisinstance(self.CollectError): 477 exc = excinfo.value 478 return str(exc.args[0]) 479 return self._repr_failure_py(excinfo, style="short") 480 481 def _prunetraceback(self, excinfo): 482 if hasattr(self, 'fspath'): 483 traceback = excinfo.traceback 484 ntraceback = traceback.cut(path=self.fspath) 485 if ntraceback == traceback: 486 ntraceback = ntraceback.cut(excludepath=tracebackcutdir) 487 excinfo.traceback = ntraceback.filter() 488 489class FSCollector(Collector): 490 def __init__(self, fspath, parent=None, config=None, session=None): 491 fspath = py.path.local(fspath) # xxx only for test_resultlog.py? 492 name = fspath.basename 493 if parent is not None: 494 rel = fspath.relto(parent.fspath) 495 if rel: 496 name = rel 497 name = name.replace(os.sep, "/") 498 super(FSCollector, self).__init__(name, parent, config, session) 499 self.fspath = fspath 500 501 def _makeid(self): 502 relpath = self.fspath.relto(self.config.rootdir) 503 if os.sep != "/": 504 relpath = relpath.replace(os.sep, "/") 505 return relpath 506 507class File(FSCollector): 508 """ base class for collecting tests from a file. """ 509 510class Item(Node): 511 """ a basic test invocation item. Note that for a single function 512 there might be multiple test invocation items. 513 """ 514 nextitem = None 515 516 def __init__(self, name, parent=None, config=None, session=None): 517 super(Item, self).__init__(name, parent, config, session) 518 self._report_sections = [] 519 520 def add_report_section(self, when, key, content): 521 if content: 522 self._report_sections.append((when, key, content)) 523 524 def reportinfo(self): 525 return self.fspath, None, "" 526 527 @property 528 def location(self): 529 try: 530 return self._location 531 except AttributeError: 532 location = self.reportinfo() 533 # bestrelpath is a quite slow function 534 cache = self.config.__dict__.setdefault("_bestrelpathcache", {}) 535 try: 536 fspath = cache[location[0]] 537 except KeyError: 538 fspath = self.session.fspath.bestrelpath(location[0]) 539 cache[location[0]] = fspath 540 location = (fspath, location[1], str(location[2])) 541 self._location = location 542 return location 543 544class NoMatch(Exception): 545 """ raised if matching cannot locate a matching names. """ 546 547class Interrupted(KeyboardInterrupt): 548 """ signals an interrupted test run. """ 549 __module__ = 'builtins' # for py3 550 551class Session(FSCollector): 552 Interrupted = Interrupted 553 554 def __init__(self, config): 555 FSCollector.__init__(self, config.rootdir, parent=None, 556 config=config, session=self) 557 self.testsfailed = 0 558 self.testscollected = 0 559 self.shouldstop = False 560 self.trace = config.trace.root.get("collection") 561 self._norecursepatterns = config.getini("norecursedirs") 562 self.startdir = py.path.local() 563 self.config.pluginmanager.register(self, name="session") 564 565 def _makeid(self): 566 return "" 567 568 @hookimpl(tryfirst=True) 569 def pytest_collectstart(self): 570 if self.shouldstop: 571 raise self.Interrupted(self.shouldstop) 572 573 @hookimpl(tryfirst=True) 574 def pytest_runtest_logreport(self, report): 575 if report.failed and not hasattr(report, 'wasxfail'): 576 self.testsfailed += 1 577 maxfail = self.config.getvalue("maxfail") 578 if maxfail and self.testsfailed >= maxfail: 579 self.shouldstop = "stopping after %d failures" % ( 580 self.testsfailed) 581 pytest_collectreport = pytest_runtest_logreport 582 583 def isinitpath(self, path): 584 return path in self._initialpaths 585 586 def gethookproxy(self, fspath): 587 # check if we have the common case of running 588 # hooks with all conftest.py filesall conftest.py 589 pm = self.config.pluginmanager 590 my_conftestmodules = pm._getconftestmodules(fspath) 591 remove_mods = pm._conftest_plugins.difference(my_conftestmodules) 592 if remove_mods: 593 # one or more conftests are not in use at this fspath 594 proxy = FSHookProxy(fspath, pm, remove_mods) 595 else: 596 # all plugis are active for this fspath 597 proxy = self.config.hook 598 return proxy 599 600 def perform_collect(self, args=None, genitems=True): 601 hook = self.config.hook 602 try: 603 items = self._perform_collect(args, genitems) 604 self.config.pluginmanager.check_pending() 605 hook.pytest_collection_modifyitems(session=self, 606 config=self.config, items=items) 607 finally: 608 hook.pytest_collection_finish(session=self) 609 self.testscollected = len(items) 610 return items 611 612 def _perform_collect(self, args, genitems): 613 if args is None: 614 args = self.config.args 615 self.trace("perform_collect", self, args) 616 self.trace.root.indent += 1 617 self._notfound = [] 618 self._initialpaths = set() 619 self._initialparts = [] 620 self.items = items = [] 621 for arg in args: 622 parts = self._parsearg(arg) 623 self._initialparts.append(parts) 624 self._initialpaths.add(parts[0]) 625 rep = collect_one_node(self) 626 self.ihook.pytest_collectreport(report=rep) 627 self.trace.root.indent -= 1 628 if self._notfound: 629 errors = [] 630 for arg, exc in self._notfound: 631 line = "(no name %r in any of %r)" % (arg, exc.args[0]) 632 errors.append("not found: %s\n%s" % (arg, line)) 633 # XXX: test this 634 raise UsageError(*errors) 635 if not genitems: 636 return rep.result 637 else: 638 if rep.passed: 639 for node in rep.result: 640 self.items.extend(self.genitems(node)) 641 return items 642 643 def collect(self): 644 for parts in self._initialparts: 645 arg = "::".join(map(str, parts)) 646 self.trace("processing argument", arg) 647 self.trace.root.indent += 1 648 try: 649 for x in self._collect(arg): 650 yield x 651 except NoMatch: 652 # we are inside a make_report hook so 653 # we cannot directly pass through the exception 654 self._notfound.append((arg, sys.exc_info()[1])) 655 656 self.trace.root.indent -= 1 657 658 def _collect(self, arg): 659 names = self._parsearg(arg) 660 path = names.pop(0) 661 if path.check(dir=1): 662 assert not names, "invalid arg %r" % (arg,) 663 for path in path.visit(fil=lambda x: x.check(file=1), 664 rec=self._recurse, bf=True, sort=True): 665 for x in self._collectfile(path): 666 yield x 667 else: 668 assert path.check(file=1) 669 for x in self.matchnodes(self._collectfile(path), names): 670 yield x 671 672 def _collectfile(self, path): 673 ihook = self.gethookproxy(path) 674 if not self.isinitpath(path): 675 if ihook.pytest_ignore_collect(path=path, config=self.config): 676 return () 677 return ihook.pytest_collect_file(path=path, parent=self) 678 679 def _recurse(self, path): 680 ihook = self.gethookproxy(path.dirpath()) 681 if ihook.pytest_ignore_collect(path=path, config=self.config): 682 return 683 for pat in self._norecursepatterns: 684 if path.check(fnmatch=pat): 685 return False 686 ihook = self.gethookproxy(path) 687 ihook.pytest_collect_directory(path=path, parent=self) 688 return True 689 690 def _tryconvertpyarg(self, x): 691 """Convert a dotted module name to path. 692 693 """ 694 import pkgutil 695 try: 696 loader = pkgutil.find_loader(x) 697 except ImportError: 698 return x 699 if loader is None: 700 return x 701 # This method is sometimes invoked when AssertionRewritingHook, which 702 # does not define a get_filename method, is already in place: 703 try: 704 path = loader.get_filename(x) 705 except AttributeError: 706 # Retrieve path from AssertionRewritingHook: 707 path = loader.modules[x][0].co_filename 708 if loader.is_package(x): 709 path = os.path.dirname(path) 710 return path 711 712 def _parsearg(self, arg): 713 """ return (fspath, names) tuple after checking the file exists. """ 714 parts = str(arg).split("::") 715 if self.config.option.pyargs: 716 parts[0] = self._tryconvertpyarg(parts[0]) 717 relpath = parts[0].replace("/", os.sep) 718 path = self.config.invocation_dir.join(relpath, abs=True) 719 if not path.check(): 720 if self.config.option.pyargs: 721 raise UsageError( 722 "file or package not found: " + arg + 723 " (missing __init__.py?)") 724 else: 725 raise UsageError("file not found: " + arg) 726 parts[0] = path 727 return parts 728 729 def matchnodes(self, matching, names): 730 self.trace("matchnodes", matching, names) 731 self.trace.root.indent += 1 732 nodes = self._matchnodes(matching, names) 733 num = len(nodes) 734 self.trace("matchnodes finished -> ", num, "nodes") 735 self.trace.root.indent -= 1 736 if num == 0: 737 raise NoMatch(matching, names[:1]) 738 return nodes 739 740 def _matchnodes(self, matching, names): 741 if not matching or not names: 742 return matching 743 name = names[0] 744 assert name 745 nextnames = names[1:] 746 resultnodes = [] 747 for node in matching: 748 if isinstance(node, Item): 749 if not names: 750 resultnodes.append(node) 751 continue 752 assert isinstance(node, Collector) 753 rep = collect_one_node(node) 754 if rep.passed: 755 has_matched = False 756 for x in rep.result: 757 # TODO: remove parametrized workaround once collection structure contains parametrization 758 if x.name == name or x.name.split("[")[0] == name: 759 resultnodes.extend(self.matchnodes([x], nextnames)) 760 has_matched = True 761 # XXX accept IDs that don't have "()" for class instances 762 if not has_matched and len(rep.result) == 1 and x.name == "()": 763 nextnames.insert(0, name) 764 resultnodes.extend(self.matchnodes([x], nextnames)) 765 else: 766 # report collection failures here to avoid failing to run some test 767 # specified in the command line because the module could not be 768 # imported (#134) 769 node.ihook.pytest_collectreport(report=rep) 770 return resultnodes 771 772 def genitems(self, node): 773 self.trace("genitems", node) 774 if isinstance(node, Item): 775 node.ihook.pytest_itemcollected(item=node) 776 yield node 777 else: 778 assert isinstance(node, Collector) 779 rep = collect_one_node(node) 780 if rep.passed: 781 for subnode in rep.result: 782 for x in self.genitems(subnode): 783 yield x 784 node.ihook.pytest_collectreport(report=rep) 785