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