main.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780
  1. # -*- coding: utf-8 -*-
  2. """ core implementation of testing process: init, session, runtest loop. """
  3. from __future__ import absolute_import
  4. from __future__ import division
  5. from __future__ import print_function
  6. import contextlib
  7. import fnmatch
  8. import functools
  9. import os
  10. import pkgutil
  11. import sys
  12. import warnings
  13. import attr
  14. import py
  15. import six
  16. import _pytest._code
  17. from _pytest import nodes
  18. from _pytest.config import directory_arg
  19. from _pytest.config import hookimpl
  20. from _pytest.config import UsageError
  21. from _pytest.deprecated import PYTEST_CONFIG_GLOBAL
  22. from _pytest.outcomes import exit
  23. from _pytest.runner import collect_one_node
  24. # exitcodes for the command line
  25. EXIT_OK = 0
  26. EXIT_TESTSFAILED = 1
  27. EXIT_INTERRUPTED = 2
  28. EXIT_INTERNALERROR = 3
  29. EXIT_USAGEERROR = 4
  30. EXIT_NOTESTSCOLLECTED = 5
  31. def pytest_addoption(parser):
  32. parser.addini(
  33. "norecursedirs",
  34. "directory patterns to avoid for recursion",
  35. type="args",
  36. default=[".*", "build", "dist", "CVS", "_darcs", "{arch}", "*.egg", "venv"],
  37. )
  38. parser.addini(
  39. "testpaths",
  40. "directories to search for tests when no files or directories are given in the "
  41. "command line.",
  42. type="args",
  43. default=[],
  44. )
  45. group = parser.getgroup("general", "running and selection options")
  46. group._addoption(
  47. "-x",
  48. "--exitfirst",
  49. action="store_const",
  50. dest="maxfail",
  51. const=1,
  52. help="exit instantly on first error or failed test.",
  53. ),
  54. group._addoption(
  55. "--maxfail",
  56. metavar="num",
  57. action="store",
  58. type=int,
  59. dest="maxfail",
  60. default=0,
  61. help="exit after first num failures or errors.",
  62. )
  63. group._addoption(
  64. "--strict-markers",
  65. "--strict",
  66. action="store_true",
  67. help="markers not registered in the `markers` section of the configuration file raise errors.",
  68. )
  69. group._addoption(
  70. "-c",
  71. metavar="file",
  72. type=str,
  73. dest="inifilename",
  74. help="load configuration from `file` instead of trying to locate one of the implicit "
  75. "configuration files.",
  76. )
  77. group._addoption(
  78. "--continue-on-collection-errors",
  79. action="store_true",
  80. default=False,
  81. dest="continue_on_collection_errors",
  82. help="Force test execution even if collection errors occur.",
  83. )
  84. group._addoption(
  85. "--rootdir",
  86. action="store",
  87. dest="rootdir",
  88. help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', "
  89. "'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: "
  90. "'$HOME/root_dir'.",
  91. )
  92. group = parser.getgroup("collect", "collection")
  93. group.addoption(
  94. "--collectonly",
  95. "--collect-only",
  96. action="store_true",
  97. help="only collect tests, don't execute them.",
  98. ),
  99. group.addoption(
  100. "--pyargs",
  101. action="store_true",
  102. help="try to interpret all arguments as python packages.",
  103. )
  104. group.addoption(
  105. "--ignore",
  106. action="append",
  107. metavar="path",
  108. help="ignore path during collection (multi-allowed).",
  109. )
  110. group.addoption(
  111. "--ignore-glob",
  112. action="append",
  113. metavar="path",
  114. help="ignore path pattern during collection (multi-allowed).",
  115. )
  116. group.addoption(
  117. "--deselect",
  118. action="append",
  119. metavar="nodeid_prefix",
  120. help="deselect item during collection (multi-allowed).",
  121. )
  122. # when changing this to --conf-cut-dir, config.py Conftest.setinitial
  123. # needs upgrading as well
  124. group.addoption(
  125. "--confcutdir",
  126. dest="confcutdir",
  127. default=None,
  128. metavar="dir",
  129. type=functools.partial(directory_arg, optname="--confcutdir"),
  130. help="only load conftest.py's relative to specified dir.",
  131. )
  132. group.addoption(
  133. "--noconftest",
  134. action="store_true",
  135. dest="noconftest",
  136. default=False,
  137. help="Don't load any conftest.py files.",
  138. )
  139. group.addoption(
  140. "--keepduplicates",
  141. "--keep-duplicates",
  142. action="store_true",
  143. dest="keepduplicates",
  144. default=False,
  145. help="Keep duplicate tests.",
  146. )
  147. group.addoption(
  148. "--collect-in-virtualenv",
  149. action="store_true",
  150. dest="collect_in_virtualenv",
  151. default=False,
  152. help="Don't ignore tests in a local virtualenv directory",
  153. )
  154. group = parser.getgroup("debugconfig", "test session debugging and configuration")
  155. group.addoption(
  156. "--basetemp",
  157. dest="basetemp",
  158. default=None,
  159. metavar="dir",
  160. help=(
  161. "base temporary directory for this test run."
  162. "(warning: this directory is removed if it exists)"
  163. ),
  164. )
  165. class _ConfigDeprecated(object):
  166. def __init__(self, config):
  167. self.__dict__["_config"] = config
  168. def __getattr__(self, attr):
  169. warnings.warn(PYTEST_CONFIG_GLOBAL, stacklevel=2)
  170. return getattr(self._config, attr)
  171. def __setattr__(self, attr, val):
  172. warnings.warn(PYTEST_CONFIG_GLOBAL, stacklevel=2)
  173. return setattr(self._config, attr, val)
  174. def __repr__(self):
  175. return "{}({!r})".format(type(self).__name__, self._config)
  176. def pytest_configure(config):
  177. __import__("pytest").config = _ConfigDeprecated(config) # compatibility
  178. def wrap_session(config, doit):
  179. """Skeleton command line program"""
  180. session = Session(config)
  181. session.exitstatus = EXIT_OK
  182. initstate = 0
  183. try:
  184. try:
  185. config._do_configure()
  186. initstate = 1
  187. config.hook.pytest_sessionstart(session=session)
  188. initstate = 2
  189. session.exitstatus = doit(config, session) or 0
  190. except UsageError:
  191. session.exitstatus = EXIT_USAGEERROR
  192. raise
  193. except Failed:
  194. session.exitstatus = EXIT_TESTSFAILED
  195. except (KeyboardInterrupt, exit.Exception):
  196. excinfo = _pytest._code.ExceptionInfo.from_current()
  197. exitstatus = EXIT_INTERRUPTED
  198. if isinstance(excinfo.value, exit.Exception):
  199. if excinfo.value.returncode is not None:
  200. exitstatus = excinfo.value.returncode
  201. if initstate < 2:
  202. sys.stderr.write(
  203. "{}: {}\n".format(excinfo.typename, excinfo.value.msg)
  204. )
  205. config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
  206. session.exitstatus = exitstatus
  207. except: # noqa
  208. excinfo = _pytest._code.ExceptionInfo.from_current()
  209. config.notify_exception(excinfo, config.option)
  210. session.exitstatus = EXIT_INTERNALERROR
  211. if excinfo.errisinstance(SystemExit):
  212. sys.stderr.write("mainloop: caught unexpected SystemExit!\n")
  213. finally:
  214. excinfo = None # Explicitly break reference cycle.
  215. session.startdir.chdir()
  216. if initstate >= 2:
  217. config.hook.pytest_sessionfinish(
  218. session=session, exitstatus=session.exitstatus
  219. )
  220. config._ensure_unconfigure()
  221. return session.exitstatus
  222. def pytest_cmdline_main(config):
  223. return wrap_session(config, _main)
  224. def _main(config, session):
  225. """ default command line protocol for initialization, session,
  226. running tests and reporting. """
  227. config.hook.pytest_collection(session=session)
  228. config.hook.pytest_runtestloop(session=session)
  229. if session.testsfailed:
  230. return EXIT_TESTSFAILED
  231. elif session.testscollected == 0:
  232. return EXIT_NOTESTSCOLLECTED
  233. def pytest_collection(session):
  234. return session.perform_collect()
  235. def pytest_runtestloop(session):
  236. if session.testsfailed and not session.config.option.continue_on_collection_errors:
  237. raise session.Interrupted("%d errors during collection" % session.testsfailed)
  238. if session.config.option.collectonly:
  239. return True
  240. for i, item in enumerate(session.items):
  241. nextitem = session.items[i + 1] if i + 1 < len(session.items) else None
  242. item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
  243. if session.shouldfail:
  244. raise session.Failed(session.shouldfail)
  245. if session.shouldstop:
  246. raise session.Interrupted(session.shouldstop)
  247. return True
  248. def _in_venv(path):
  249. """Attempts to detect if ``path`` is the root of a Virtual Environment by
  250. checking for the existence of the appropriate activate script"""
  251. bindir = path.join("Scripts" if sys.platform.startswith("win") else "bin")
  252. if not bindir.isdir():
  253. return False
  254. activates = (
  255. "activate",
  256. "activate.csh",
  257. "activate.fish",
  258. "Activate",
  259. "Activate.bat",
  260. "Activate.ps1",
  261. )
  262. return any([fname.basename in activates for fname in bindir.listdir()])
  263. def pytest_ignore_collect(path, config):
  264. ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath())
  265. ignore_paths = ignore_paths or []
  266. excludeopt = config.getoption("ignore")
  267. if excludeopt:
  268. ignore_paths.extend([py.path.local(x) for x in excludeopt])
  269. if py.path.local(path) in ignore_paths:
  270. return True
  271. ignore_globs = config._getconftest_pathlist(
  272. "collect_ignore_glob", path=path.dirpath()
  273. )
  274. ignore_globs = ignore_globs or []
  275. excludeglobopt = config.getoption("ignore_glob")
  276. if excludeglobopt:
  277. ignore_globs.extend([py.path.local(x) for x in excludeglobopt])
  278. if any(
  279. fnmatch.fnmatch(six.text_type(path), six.text_type(glob))
  280. for glob in ignore_globs
  281. ):
  282. return True
  283. allow_in_venv = config.getoption("collect_in_virtualenv")
  284. if not allow_in_venv and _in_venv(path):
  285. return True
  286. return False
  287. def pytest_collection_modifyitems(items, config):
  288. deselect_prefixes = tuple(config.getoption("deselect") or [])
  289. if not deselect_prefixes:
  290. return
  291. remaining = []
  292. deselected = []
  293. for colitem in items:
  294. if colitem.nodeid.startswith(deselect_prefixes):
  295. deselected.append(colitem)
  296. else:
  297. remaining.append(colitem)
  298. if deselected:
  299. config.hook.pytest_deselected(items=deselected)
  300. items[:] = remaining
  301. @contextlib.contextmanager
  302. def _patched_find_module():
  303. """Patch bug in pkgutil.ImpImporter.find_module
  304. When using pkgutil.find_loader on python<3.4 it removes symlinks
  305. from the path due to a call to os.path.realpath. This is not consistent
  306. with actually doing the import (in these versions, pkgutil and __import__
  307. did not share the same underlying code). This can break conftest
  308. discovery for pytest where symlinks are involved.
  309. The only supported python<3.4 by pytest is python 2.7.
  310. """
  311. if six.PY2: # python 3.4+ uses importlib instead
  312. def find_module_patched(self, fullname, path=None):
  313. # Note: we ignore 'path' argument since it is only used via meta_path
  314. subname = fullname.split(".")[-1]
  315. if subname != fullname and self.path is None:
  316. return None
  317. if self.path is None:
  318. path = None
  319. else:
  320. # original: path = [os.path.realpath(self.path)]
  321. path = [self.path]
  322. try:
  323. file, filename, etc = pkgutil.imp.find_module(subname, path)
  324. except ImportError:
  325. return None
  326. return pkgutil.ImpLoader(fullname, file, filename, etc)
  327. old_find_module = pkgutil.ImpImporter.find_module
  328. pkgutil.ImpImporter.find_module = find_module_patched
  329. try:
  330. yield
  331. finally:
  332. pkgutil.ImpImporter.find_module = old_find_module
  333. else:
  334. yield
  335. class FSHookProxy(object):
  336. def __init__(self, fspath, pm, remove_mods):
  337. self.fspath = fspath
  338. self.pm = pm
  339. self.remove_mods = remove_mods
  340. def __getattr__(self, name):
  341. x = self.pm.subset_hook_caller(name, remove_plugins=self.remove_mods)
  342. self.__dict__[name] = x
  343. return x
  344. class NoMatch(Exception):
  345. """ raised if matching cannot locate a matching names. """
  346. class Interrupted(KeyboardInterrupt):
  347. """ signals an interrupted test run. """
  348. __module__ = "builtins" # for py3
  349. class Failed(Exception):
  350. """ signals a stop as failed test run. """
  351. @attr.s
  352. class _bestrelpath_cache(dict):
  353. path = attr.ib()
  354. def __missing__(self, path):
  355. r = self.path.bestrelpath(path)
  356. self[path] = r
  357. return r
  358. class Session(nodes.FSCollector):
  359. Interrupted = Interrupted
  360. Failed = Failed
  361. def __init__(self, config):
  362. nodes.FSCollector.__init__(
  363. self, config.rootdir, parent=None, config=config, session=self, nodeid=""
  364. )
  365. self.testsfailed = 0
  366. self.testscollected = 0
  367. self.shouldstop = False
  368. self.shouldfail = False
  369. self.trace = config.trace.root.get("collection")
  370. self._norecursepatterns = config.getini("norecursedirs")
  371. self.startdir = config.invocation_dir
  372. self._initialpaths = frozenset()
  373. # Keep track of any collected nodes in here, so we don't duplicate fixtures
  374. self._node_cache = {}
  375. self._bestrelpathcache = _bestrelpath_cache(config.rootdir)
  376. # Dirnames of pkgs with dunder-init files.
  377. self._pkg_roots = {}
  378. self.config.pluginmanager.register(self, name="session")
  379. def __repr__(self):
  380. return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % (
  381. self.__class__.__name__,
  382. self.name,
  383. getattr(self, "exitstatus", "<UNSET>"),
  384. self.testsfailed,
  385. self.testscollected,
  386. )
  387. def _node_location_to_relpath(self, node_path):
  388. # bestrelpath is a quite slow function
  389. return self._bestrelpathcache[node_path]
  390. @hookimpl(tryfirst=True)
  391. def pytest_collectstart(self):
  392. if self.shouldfail:
  393. raise self.Failed(self.shouldfail)
  394. if self.shouldstop:
  395. raise self.Interrupted(self.shouldstop)
  396. @hookimpl(tryfirst=True)
  397. def pytest_runtest_logreport(self, report):
  398. if report.failed and not hasattr(report, "wasxfail"):
  399. self.testsfailed += 1
  400. maxfail = self.config.getvalue("maxfail")
  401. if maxfail and self.testsfailed >= maxfail:
  402. self.shouldfail = "stopping after %d failures" % (self.testsfailed)
  403. pytest_collectreport = pytest_runtest_logreport
  404. def isinitpath(self, path):
  405. return path in self._initialpaths
  406. def gethookproxy(self, fspath):
  407. # check if we have the common case of running
  408. # hooks with all conftest.py files
  409. pm = self.config.pluginmanager
  410. my_conftestmodules = pm._getconftestmodules(fspath)
  411. remove_mods = pm._conftest_plugins.difference(my_conftestmodules)
  412. if remove_mods:
  413. # one or more conftests are not in use at this fspath
  414. proxy = FSHookProxy(fspath, pm, remove_mods)
  415. else:
  416. # all plugis are active for this fspath
  417. proxy = self.config.hook
  418. return proxy
  419. def perform_collect(self, args=None, genitems=True):
  420. hook = self.config.hook
  421. try:
  422. items = self._perform_collect(args, genitems)
  423. self.config.pluginmanager.check_pending()
  424. hook.pytest_collection_modifyitems(
  425. session=self, config=self.config, items=items
  426. )
  427. finally:
  428. hook.pytest_collection_finish(session=self)
  429. self.testscollected = len(items)
  430. return items
  431. def _perform_collect(self, args, genitems):
  432. if args is None:
  433. args = self.config.args
  434. self.trace("perform_collect", self, args)
  435. self.trace.root.indent += 1
  436. self._notfound = []
  437. initialpaths = []
  438. self._initialparts = []
  439. self.items = items = []
  440. for arg in args:
  441. parts = self._parsearg(arg)
  442. self._initialparts.append(parts)
  443. initialpaths.append(parts[0])
  444. self._initialpaths = frozenset(initialpaths)
  445. rep = collect_one_node(self)
  446. self.ihook.pytest_collectreport(report=rep)
  447. self.trace.root.indent -= 1
  448. if self._notfound:
  449. errors = []
  450. for arg, exc in self._notfound:
  451. line = "(no name %r in any of %r)" % (arg, exc.args[0])
  452. errors.append("not found: %s\n%s" % (arg, line))
  453. # XXX: test this
  454. raise UsageError(*errors)
  455. if not genitems:
  456. return rep.result
  457. else:
  458. if rep.passed:
  459. for node in rep.result:
  460. self.items.extend(self.genitems(node))
  461. return items
  462. def collect(self):
  463. for initialpart in self._initialparts:
  464. arg = "::".join(map(str, initialpart))
  465. self.trace("processing argument", arg)
  466. self.trace.root.indent += 1
  467. try:
  468. for x in self._collect(arg):
  469. yield x
  470. except NoMatch:
  471. # we are inside a make_report hook so
  472. # we cannot directly pass through the exception
  473. self._notfound.append((arg, sys.exc_info()[1]))
  474. self.trace.root.indent -= 1
  475. def _collect(self, arg):
  476. from _pytest.python import Package
  477. names = self._parsearg(arg)
  478. argpath = names.pop(0)
  479. # Start with a Session root, and delve to argpath item (dir or file)
  480. # and stack all Packages found on the way.
  481. # No point in finding packages when collecting doctests
  482. if not self.config.getoption("doctestmodules", False):
  483. pm = self.config.pluginmanager
  484. for parent in reversed(argpath.parts()):
  485. if pm._confcutdir and pm._confcutdir.relto(parent):
  486. break
  487. if parent.isdir():
  488. pkginit = parent.join("__init__.py")
  489. if pkginit.isfile():
  490. if pkginit not in self._node_cache:
  491. col = self._collectfile(pkginit, handle_dupes=False)
  492. if col:
  493. if isinstance(col[0], Package):
  494. self._pkg_roots[parent] = col[0]
  495. # always store a list in the cache, matchnodes expects it
  496. self._node_cache[col[0].fspath] = [col[0]]
  497. # If it's a directory argument, recurse and look for any Subpackages.
  498. # Let the Package collector deal with subnodes, don't collect here.
  499. if argpath.check(dir=1):
  500. assert not names, "invalid arg %r" % (arg,)
  501. seen_dirs = set()
  502. for path in argpath.visit(
  503. fil=self._visit_filter, rec=self._recurse, bf=True, sort=True
  504. ):
  505. dirpath = path.dirpath()
  506. if dirpath not in seen_dirs:
  507. # Collect packages first.
  508. seen_dirs.add(dirpath)
  509. pkginit = dirpath.join("__init__.py")
  510. if pkginit.exists():
  511. for x in self._collectfile(pkginit):
  512. yield x
  513. if isinstance(x, Package):
  514. self._pkg_roots[dirpath] = x
  515. if dirpath in self._pkg_roots:
  516. # Do not collect packages here.
  517. continue
  518. for x in self._collectfile(path):
  519. key = (type(x), x.fspath)
  520. if key in self._node_cache:
  521. yield self._node_cache[key]
  522. else:
  523. self._node_cache[key] = x
  524. yield x
  525. else:
  526. assert argpath.check(file=1)
  527. if argpath in self._node_cache:
  528. col = self._node_cache[argpath]
  529. else:
  530. collect_root = self._pkg_roots.get(argpath.dirname, self)
  531. col = collect_root._collectfile(argpath, handle_dupes=False)
  532. if col:
  533. self._node_cache[argpath] = col
  534. m = self.matchnodes(col, names)
  535. # If __init__.py was the only file requested, then the matched node will be
  536. # the corresponding Package, and the first yielded item will be the __init__
  537. # Module itself, so just use that. If this special case isn't taken, then all
  538. # the files in the package will be yielded.
  539. if argpath.basename == "__init__.py":
  540. try:
  541. yield next(m[0].collect())
  542. except StopIteration:
  543. # The package collects nothing with only an __init__.py
  544. # file in it, which gets ignored by the default
  545. # "python_files" option.
  546. pass
  547. return
  548. for y in m:
  549. yield y
  550. def _collectfile(self, path, handle_dupes=True):
  551. assert path.isfile(), "%r is not a file (isdir=%r, exists=%r, islink=%r)" % (
  552. path,
  553. path.isdir(),
  554. path.exists(),
  555. path.islink(),
  556. )
  557. ihook = self.gethookproxy(path)
  558. if not self.isinitpath(path):
  559. if ihook.pytest_ignore_collect(path=path, config=self.config):
  560. return ()
  561. if handle_dupes:
  562. keepduplicates = self.config.getoption("keepduplicates")
  563. if not keepduplicates:
  564. duplicate_paths = self.config.pluginmanager._duplicatepaths
  565. if path in duplicate_paths:
  566. return ()
  567. else:
  568. duplicate_paths.add(path)
  569. return ihook.pytest_collect_file(path=path, parent=self)
  570. def _recurse(self, dirpath):
  571. if dirpath.basename == "__pycache__":
  572. return False
  573. ihook = self.gethookproxy(dirpath.dirpath())
  574. if ihook.pytest_ignore_collect(path=dirpath, config=self.config):
  575. return False
  576. for pat in self._norecursepatterns:
  577. if dirpath.check(fnmatch=pat):
  578. return False
  579. ihook = self.gethookproxy(dirpath)
  580. ihook.pytest_collect_directory(path=dirpath, parent=self)
  581. return True
  582. if six.PY2:
  583. @staticmethod
  584. def _visit_filter(f):
  585. return f.check(file=1) and not f.strpath.endswith("*.pyc")
  586. else:
  587. @staticmethod
  588. def _visit_filter(f):
  589. return f.check(file=1)
  590. def _tryconvertpyarg(self, x):
  591. """Convert a dotted module name to path."""
  592. try:
  593. with _patched_find_module():
  594. loader = pkgutil.find_loader(x)
  595. except ImportError:
  596. return x
  597. if loader is None:
  598. return x
  599. # This method is sometimes invoked when AssertionRewritingHook, which
  600. # does not define a get_filename method, is already in place:
  601. try:
  602. with _patched_find_module():
  603. path = loader.get_filename(x)
  604. except AttributeError:
  605. # Retrieve path from AssertionRewritingHook:
  606. path = loader.modules[x][0].co_filename
  607. if loader.is_package(x):
  608. path = os.path.dirname(path)
  609. return path
  610. def _parsearg(self, arg):
  611. """ return (fspath, names) tuple after checking the file exists. """
  612. parts = str(arg).split("::")
  613. if self.config.option.pyargs:
  614. parts[0] = self._tryconvertpyarg(parts[0])
  615. relpath = parts[0].replace("/", os.sep)
  616. path = self.config.invocation_dir.join(relpath, abs=True)
  617. if not path.check():
  618. if self.config.option.pyargs:
  619. raise UsageError(
  620. "file or package not found: " + arg + " (missing __init__.py?)"
  621. )
  622. raise UsageError("file not found: " + arg)
  623. parts[0] = path.realpath()
  624. return parts
  625. def matchnodes(self, matching, names):
  626. self.trace("matchnodes", matching, names)
  627. self.trace.root.indent += 1
  628. nodes = self._matchnodes(matching, names)
  629. num = len(nodes)
  630. self.trace("matchnodes finished -> ", num, "nodes")
  631. self.trace.root.indent -= 1
  632. if num == 0:
  633. raise NoMatch(matching, names[:1])
  634. return nodes
  635. def _matchnodes(self, matching, names):
  636. if not matching or not names:
  637. return matching
  638. name = names[0]
  639. assert name
  640. nextnames = names[1:]
  641. resultnodes = []
  642. for node in matching:
  643. if isinstance(node, nodes.Item):
  644. if not names:
  645. resultnodes.append(node)
  646. continue
  647. assert isinstance(node, nodes.Collector)
  648. key = (type(node), node.nodeid)
  649. if key in self._node_cache:
  650. rep = self._node_cache[key]
  651. else:
  652. rep = collect_one_node(node)
  653. self._node_cache[key] = rep
  654. if rep.passed:
  655. has_matched = False
  656. for x in rep.result:
  657. # TODO: remove parametrized workaround once collection structure contains parametrization
  658. if x.name == name or x.name.split("[")[0] == name:
  659. resultnodes.extend(self.matchnodes([x], nextnames))
  660. has_matched = True
  661. # XXX accept IDs that don't have "()" for class instances
  662. if not has_matched and len(rep.result) == 1 and x.name == "()":
  663. nextnames.insert(0, name)
  664. resultnodes.extend(self.matchnodes([x], nextnames))
  665. else:
  666. # report collection failures here to avoid failing to run some test
  667. # specified in the command line because the module could not be
  668. # imported (#134)
  669. node.ihook.pytest_collectreport(report=rep)
  670. return resultnodes
  671. def genitems(self, node):
  672. self.trace("genitems", node)
  673. if isinstance(node, nodes.Item):
  674. node.ihook.pytest_itemcollected(item=node)
  675. yield node
  676. else:
  677. assert isinstance(node, nodes.Collector)
  678. rep = collect_one_node(node)
  679. if rep.passed:
  680. for subnode in rep.result:
  681. for x in self.genitems(subnode):
  682. yield x
  683. node.ihook.pytest_collectreport(report=rep)