Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" core implementation of testing process: init, session, runtest loop. """ 

2import argparse 

3import fnmatch 

4import functools 

5import importlib 

6import os 

7import sys 

8from typing import Callable 

9from typing import Dict 

10from typing import FrozenSet 

11from typing import Iterator 

12from typing import List 

13from typing import Optional 

14from typing import Sequence 

15from typing import Set 

16from typing import Tuple 

17from typing import Union 

18 

19import attr 

20import py 

21 

22import _pytest._code 

23from _pytest import nodes 

24from _pytest.compat import overload 

25from _pytest.compat import TYPE_CHECKING 

26from _pytest.config import Config 

27from _pytest.config import directory_arg 

28from _pytest.config import ExitCode 

29from _pytest.config import hookimpl 

30from _pytest.config import UsageError 

31from _pytest.config.argparsing import Parser 

32from _pytest.fixtures import FixtureManager 

33from _pytest.outcomes import exit 

34from _pytest.pathlib import Path 

35from _pytest.reports import CollectReport 

36from _pytest.reports import TestReport 

37from _pytest.runner import collect_one_node 

38from _pytest.runner import SetupState 

39 

40 

41if TYPE_CHECKING: 

42 from typing import Type 

43 from typing_extensions import Literal 

44 

45 from _pytest.python import Package 

46 

47 

48def pytest_addoption(parser: Parser) -> None: 

49 parser.addini( 

50 "norecursedirs", 

51 "directory patterns to avoid for recursion", 

52 type="args", 

53 default=[".*", "build", "dist", "CVS", "_darcs", "{arch}", "*.egg", "venv"], 

54 ) 

55 parser.addini( 

56 "testpaths", 

57 "directories to search for tests when no files or directories are given in the " 

58 "command line.", 

59 type="args", 

60 default=[], 

61 ) 

62 group = parser.getgroup("general", "running and selection options") 

63 group._addoption( 

64 "-x", 

65 "--exitfirst", 

66 action="store_const", 

67 dest="maxfail", 

68 const=1, 

69 help="exit instantly on first error or failed test.", 

70 ) 

71 group._addoption( 

72 "--maxfail", 

73 metavar="num", 

74 action="store", 

75 type=int, 

76 dest="maxfail", 

77 default=0, 

78 help="exit after first num failures or errors.", 

79 ) 

80 group._addoption( 

81 "--strict-config", 

82 action="store_true", 

83 help="any warnings encountered while parsing the `pytest` section of the configuration file raise errors.", 

84 ) 

85 group._addoption( 

86 "--strict-markers", 

87 "--strict", 

88 action="store_true", 

89 help="markers not registered in the `markers` section of the configuration file raise errors.", 

90 ) 

91 group._addoption( 

92 "-c", 

93 metavar="file", 

94 type=str, 

95 dest="inifilename", 

96 help="load configuration from `file` instead of trying to locate one of the implicit " 

97 "configuration files.", 

98 ) 

99 group._addoption( 

100 "--continue-on-collection-errors", 

101 action="store_true", 

102 default=False, 

103 dest="continue_on_collection_errors", 

104 help="Force test execution even if collection errors occur.", 

105 ) 

106 group._addoption( 

107 "--rootdir", 

108 action="store", 

109 dest="rootdir", 

110 help="Define root directory for tests. Can be relative path: 'root_dir', './root_dir', " 

111 "'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: " 

112 "'$HOME/root_dir'.", 

113 ) 

114 

115 group = parser.getgroup("collect", "collection") 

116 group.addoption( 

117 "--collectonly", 

118 "--collect-only", 

119 "--co", 

120 action="store_true", 

121 help="only collect tests, don't execute them.", 

122 ) 

123 group.addoption( 

124 "--pyargs", 

125 action="store_true", 

126 help="try to interpret all arguments as python packages.", 

127 ) 

128 group.addoption( 

129 "--ignore", 

130 action="append", 

131 metavar="path", 

132 help="ignore path during collection (multi-allowed).", 

133 ) 

134 group.addoption( 

135 "--ignore-glob", 

136 action="append", 

137 metavar="path", 

138 help="ignore path pattern during collection (multi-allowed).", 

139 ) 

140 group.addoption( 

141 "--deselect", 

142 action="append", 

143 metavar="nodeid_prefix", 

144 help="deselect item (via node id prefix) during collection (multi-allowed).", 

145 ) 

146 group.addoption( 

147 "--confcutdir", 

148 dest="confcutdir", 

149 default=None, 

150 metavar="dir", 

151 type=functools.partial(directory_arg, optname="--confcutdir"), 

152 help="only load conftest.py's relative to specified dir.", 

153 ) 

154 group.addoption( 

155 "--noconftest", 

156 action="store_true", 

157 dest="noconftest", 

158 default=False, 

159 help="Don't load any conftest.py files.", 

160 ) 

161 group.addoption( 

162 "--keepduplicates", 

163 "--keep-duplicates", 

164 action="store_true", 

165 dest="keepduplicates", 

166 default=False, 

167 help="Keep duplicate tests.", 

168 ) 

169 group.addoption( 

170 "--collect-in-virtualenv", 

171 action="store_true", 

172 dest="collect_in_virtualenv", 

173 default=False, 

174 help="Don't ignore tests in a local virtualenv directory", 

175 ) 

176 group.addoption( 

177 "--import-mode", 

178 default="prepend", 

179 choices=["prepend", "append", "importlib"], 

180 dest="importmode", 

181 help="prepend/append to sys.path when importing test modules and conftest files, " 

182 "default is to prepend.", 

183 ) 

184 

185 group = parser.getgroup("debugconfig", "test session debugging and configuration") 

186 group.addoption( 

187 "--basetemp", 

188 dest="basetemp", 

189 default=None, 

190 type=validate_basetemp, 

191 metavar="dir", 

192 help=( 

193 "base temporary directory for this test run." 

194 "(warning: this directory is removed if it exists)" 

195 ), 

196 ) 

197 

198 

199def validate_basetemp(path: str) -> str: 

200 # GH 7119 

201 msg = "basetemp must not be empty, the current working directory or any parent directory of it" 

202 

203 # empty path 

204 if not path: 

205 raise argparse.ArgumentTypeError(msg) 

206 

207 def is_ancestor(base: Path, query: Path) -> bool: 

208 """ return True if query is an ancestor of base, else False.""" 

209 if base == query: 

210 return True 

211 for parent in base.parents: 

212 if parent == query: 

213 return True 

214 return False 

215 

216 # check if path is an ancestor of cwd 

217 if is_ancestor(Path.cwd(), Path(path).absolute()): 

218 raise argparse.ArgumentTypeError(msg) 

219 

220 # check symlinks for ancestors 

221 if is_ancestor(Path.cwd().resolve(), Path(path).resolve()): 

222 raise argparse.ArgumentTypeError(msg) 

223 

224 return path 

225 

226 

227def wrap_session( 

228 config: Config, doit: Callable[[Config, "Session"], Optional[Union[int, ExitCode]]] 

229) -> Union[int, ExitCode]: 

230 """Skeleton command line program""" 

231 session = Session.from_config(config) 

232 session.exitstatus = ExitCode.OK 

233 initstate = 0 

234 try: 

235 try: 

236 config._do_configure() 

237 initstate = 1 

238 config.hook.pytest_sessionstart(session=session) 

239 initstate = 2 

240 session.exitstatus = doit(config, session) or 0 

241 except UsageError: 

242 session.exitstatus = ExitCode.USAGE_ERROR 

243 raise 

244 except Failed: 

245 session.exitstatus = ExitCode.TESTS_FAILED 

246 except (KeyboardInterrupt, exit.Exception): 

247 excinfo = _pytest._code.ExceptionInfo.from_current() 

248 exitstatus = ExitCode.INTERRUPTED # type: Union[int, ExitCode] 

249 if isinstance(excinfo.value, exit.Exception): 

250 if excinfo.value.returncode is not None: 

251 exitstatus = excinfo.value.returncode 

252 if initstate < 2: 

253 sys.stderr.write( 

254 "{}: {}\n".format(excinfo.typename, excinfo.value.msg) 

255 ) 

256 config.hook.pytest_keyboard_interrupt(excinfo=excinfo) 

257 session.exitstatus = exitstatus 

258 except BaseException: 

259 session.exitstatus = ExitCode.INTERNAL_ERROR 

260 excinfo = _pytest._code.ExceptionInfo.from_current() 

261 try: 

262 config.notify_exception(excinfo, config.option) 

263 except exit.Exception as exc: 

264 if exc.returncode is not None: 

265 session.exitstatus = exc.returncode 

266 sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc)) 

267 else: 

268 if isinstance(excinfo.value, SystemExit): 

269 sys.stderr.write("mainloop: caught unexpected SystemExit!\n") 

270 

271 finally: 

272 # Explicitly break reference cycle. 

273 excinfo = None # type: ignore 

274 session.startdir.chdir() 

275 if initstate >= 2: 

276 try: 

277 config.hook.pytest_sessionfinish( 

278 session=session, exitstatus=session.exitstatus 

279 ) 

280 except exit.Exception as exc: 

281 if exc.returncode is not None: 

282 session.exitstatus = exc.returncode 

283 sys.stderr.write("{}: {}\n".format(type(exc).__name__, exc)) 

284 config._ensure_unconfigure() 

285 return session.exitstatus 

286 

287 

288def pytest_cmdline_main(config: Config) -> Union[int, ExitCode]: 

289 return wrap_session(config, _main) 

290 

291 

292def _main(config: Config, session: "Session") -> Optional[Union[int, ExitCode]]: 

293 """ default command line protocol for initialization, session, 

294 running tests and reporting. """ 

295 config.hook.pytest_collection(session=session) 

296 config.hook.pytest_runtestloop(session=session) 

297 

298 if session.testsfailed: 

299 return ExitCode.TESTS_FAILED 

300 elif session.testscollected == 0: 

301 return ExitCode.NO_TESTS_COLLECTED 

302 return None 

303 

304 

305def pytest_collection(session: "Session") -> None: 

306 session.perform_collect() 

307 

308 

309def pytest_runtestloop(session: "Session") -> bool: 

310 if session.testsfailed and not session.config.option.continue_on_collection_errors: 

311 raise session.Interrupted( 

312 "%d error%s during collection" 

313 % (session.testsfailed, "s" if session.testsfailed != 1 else "") 

314 ) 

315 

316 if session.config.option.collectonly: 

317 return True 

318 

319 for i, item in enumerate(session.items): 

320 nextitem = session.items[i + 1] if i + 1 < len(session.items) else None 

321 item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem) 

322 if session.shouldfail: 

323 raise session.Failed(session.shouldfail) 

324 if session.shouldstop: 

325 raise session.Interrupted(session.shouldstop) 

326 return True 

327 

328 

329def _in_venv(path: py.path.local) -> bool: 

330 """Attempts to detect if ``path`` is the root of a Virtual Environment by 

331 checking for the existence of the appropriate activate script""" 

332 bindir = path.join("Scripts" if sys.platform.startswith("win") else "bin") 

333 if not bindir.isdir(): 

334 return False 

335 activates = ( 

336 "activate", 

337 "activate.csh", 

338 "activate.fish", 

339 "Activate", 

340 "Activate.bat", 

341 "Activate.ps1", 

342 ) 

343 return any([fname.basename in activates for fname in bindir.listdir()]) 

344 

345 

346def pytest_ignore_collect(path: py.path.local, config: Config) -> Optional[bool]: 

347 ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath()) 

348 ignore_paths = ignore_paths or [] 

349 excludeopt = config.getoption("ignore") 

350 if excludeopt: 

351 ignore_paths.extend([py.path.local(x) for x in excludeopt]) 

352 

353 if py.path.local(path) in ignore_paths: 

354 return True 

355 

356 ignore_globs = config._getconftest_pathlist( 

357 "collect_ignore_glob", path=path.dirpath() 

358 ) 

359 ignore_globs = ignore_globs or [] 

360 excludeglobopt = config.getoption("ignore_glob") 

361 if excludeglobopt: 

362 ignore_globs.extend([py.path.local(x) for x in excludeglobopt]) 

363 

364 if any(fnmatch.fnmatch(str(path), str(glob)) for glob in ignore_globs): 

365 return True 

366 

367 allow_in_venv = config.getoption("collect_in_virtualenv") 

368 if not allow_in_venv and _in_venv(path): 

369 return True 

370 return None 

371 

372 

373def pytest_collection_modifyitems(items: List[nodes.Item], config: Config) -> None: 

374 deselect_prefixes = tuple(config.getoption("deselect") or []) 

375 if not deselect_prefixes: 

376 return 

377 

378 remaining = [] 

379 deselected = [] 

380 for colitem in items: 

381 if colitem.nodeid.startswith(deselect_prefixes): 

382 deselected.append(colitem) 

383 else: 

384 remaining.append(colitem) 

385 

386 if deselected: 

387 config.hook.pytest_deselected(items=deselected) 

388 items[:] = remaining 

389 

390 

391class NoMatch(Exception): 

392 """ raised if matching cannot locate a matching names. """ 

393 

394 

395class Interrupted(KeyboardInterrupt): 

396 """ signals an interrupted test run. """ 

397 

398 __module__ = "builtins" # for py3 

399 

400 

401class Failed(Exception): 

402 """ signals a stop as failed test run. """ 

403 

404 

405@attr.s 

406class _bestrelpath_cache(dict): 

407 path = attr.ib(type=py.path.local) 

408 

409 def __missing__(self, path: py.path.local) -> str: 

410 r = self.path.bestrelpath(path) # type: str 

411 self[path] = r 

412 return r 

413 

414 

415class Session(nodes.FSCollector): 

416 Interrupted = Interrupted 

417 Failed = Failed 

418 # Set on the session by runner.pytest_sessionstart. 

419 _setupstate = None # type: SetupState 

420 # Set on the session by fixtures.pytest_sessionstart. 

421 _fixturemanager = None # type: FixtureManager 

422 exitstatus = None # type: Union[int, ExitCode] 

423 

424 def __init__(self, config: Config) -> None: 

425 nodes.FSCollector.__init__( 

426 self, config.rootdir, parent=None, config=config, session=self, nodeid="" 

427 ) 

428 self.testsfailed = 0 

429 self.testscollected = 0 

430 self.shouldstop = False # type: Union[bool, str] 

431 self.shouldfail = False # type: Union[bool, str] 

432 self.trace = config.trace.root.get("collection") 

433 self.startdir = config.invocation_dir 

434 self._initialpaths = frozenset() # type: FrozenSet[py.path.local] 

435 

436 # Keep track of any collected nodes in here, so we don't duplicate fixtures 

437 self._collection_node_cache1 = ( 

438 {} 

439 ) # type: Dict[py.path.local, Sequence[nodes.Collector]] 

440 self._collection_node_cache2 = ( 

441 {} 

442 ) # type: Dict[Tuple[Type[nodes.Collector], py.path.local], nodes.Collector] 

443 self._collection_node_cache3 = ( 

444 {} 

445 ) # type: Dict[Tuple[Type[nodes.Collector], str], CollectReport] 

446 

447 # Dirnames of pkgs with dunder-init files. 

448 self._collection_pkg_roots = {} # type: Dict[str, Package] 

449 

450 self._bestrelpathcache = _bestrelpath_cache( 

451 config.rootdir 

452 ) # type: Dict[py.path.local, str] 

453 

454 self.config.pluginmanager.register(self, name="session") 

455 

456 @classmethod 

457 def from_config(cls, config: Config) -> "Session": 

458 session = cls._create(config) # type: Session 

459 return session 

460 

461 def __repr__(self) -> str: 

462 return "<%s %s exitstatus=%r testsfailed=%d testscollected=%d>" % ( 

463 self.__class__.__name__, 

464 self.name, 

465 getattr(self, "exitstatus", "<UNSET>"), 

466 self.testsfailed, 

467 self.testscollected, 

468 ) 

469 

470 def _node_location_to_relpath(self, node_path: py.path.local) -> str: 

471 # bestrelpath is a quite slow function 

472 return self._bestrelpathcache[node_path] 

473 

474 @hookimpl(tryfirst=True) 

475 def pytest_collectstart(self) -> None: 

476 if self.shouldfail: 

477 raise self.Failed(self.shouldfail) 

478 if self.shouldstop: 

479 raise self.Interrupted(self.shouldstop) 

480 

481 @hookimpl(tryfirst=True) 

482 def pytest_runtest_logreport( 

483 self, report: Union[TestReport, CollectReport] 

484 ) -> None: 

485 if report.failed and not hasattr(report, "wasxfail"): 

486 self.testsfailed += 1 

487 maxfail = self.config.getvalue("maxfail") 

488 if maxfail and self.testsfailed >= maxfail: 

489 self.shouldfail = "stopping after %d failures" % (self.testsfailed) 

490 

491 pytest_collectreport = pytest_runtest_logreport 

492 

493 def isinitpath(self, path: py.path.local) -> bool: 

494 return path in self._initialpaths 

495 

496 def gethookproxy(self, fspath: py.path.local): 

497 return super()._gethookproxy(fspath) 

498 

499 @overload 

500 def perform_collect( 

501 self, args: Optional[Sequence[str]] = ..., genitems: "Literal[True]" = ... 

502 ) -> Sequence[nodes.Item]: 

503 raise NotImplementedError() 

504 

505 @overload # noqa: F811 

506 def perform_collect( # noqa: F811 

507 self, args: Optional[Sequence[str]] = ..., genitems: bool = ... 

508 ) -> Sequence[Union[nodes.Item, nodes.Collector]]: 

509 raise NotImplementedError() 

510 

511 def perform_collect( # noqa: F811 

512 self, args: Optional[Sequence[str]] = None, genitems: bool = True 

513 ) -> Sequence[Union[nodes.Item, nodes.Collector]]: 

514 hook = self.config.hook 

515 try: 

516 items = self._perform_collect(args, genitems) 

517 self.config.pluginmanager.check_pending() 

518 hook.pytest_collection_modifyitems( 

519 session=self, config=self.config, items=items 

520 ) 

521 finally: 

522 hook.pytest_collection_finish(session=self) 

523 self.testscollected = len(items) 

524 return items 

525 

526 @overload 

527 def _perform_collect( 

528 self, args: Optional[Sequence[str]], genitems: "Literal[True]" 

529 ) -> List[nodes.Item]: 

530 raise NotImplementedError() 

531 

532 @overload # noqa: F811 

533 def _perform_collect( # noqa: F811 

534 self, args: Optional[Sequence[str]], genitems: bool 

535 ) -> Union[List[Union[nodes.Item]], List[Union[nodes.Item, nodes.Collector]]]: 

536 raise NotImplementedError() 

537 

538 def _perform_collect( # noqa: F811 

539 self, args: Optional[Sequence[str]], genitems: bool 

540 ) -> Union[List[Union[nodes.Item]], List[Union[nodes.Item, nodes.Collector]]]: 

541 if args is None: 

542 args = self.config.args 

543 self.trace("perform_collect", self, args) 

544 self.trace.root.indent += 1 

545 self._notfound = [] # type: List[Tuple[str, NoMatch]] 

546 initialpaths = [] # type: List[py.path.local] 

547 self._initial_parts = [] # type: List[Tuple[py.path.local, List[str]]] 

548 self.items = items = [] # type: List[nodes.Item] 

549 for arg in args: 

550 fspath, parts = self._parsearg(arg) 

551 self._initial_parts.append((fspath, parts)) 

552 initialpaths.append(fspath) 

553 self._initialpaths = frozenset(initialpaths) 

554 rep = collect_one_node(self) 

555 self.ihook.pytest_collectreport(report=rep) 

556 self.trace.root.indent -= 1 

557 if self._notfound: 

558 errors = [] 

559 for arg, exc in self._notfound: 

560 line = "(no name {!r} in any of {!r})".format(arg, exc.args[0]) 

561 errors.append("not found: {}\n{}".format(arg, line)) 

562 raise UsageError(*errors) 

563 if not genitems: 

564 return rep.result 

565 else: 

566 if rep.passed: 

567 for node in rep.result: 

568 self.items.extend(self.genitems(node)) 

569 return items 

570 

571 def collect(self) -> Iterator[Union[nodes.Item, nodes.Collector]]: 

572 for fspath, parts in self._initial_parts: 

573 self.trace("processing argument", (fspath, parts)) 

574 self.trace.root.indent += 1 

575 try: 

576 yield from self._collect(fspath, parts) 

577 except NoMatch as exc: 

578 report_arg = "::".join((str(fspath), *parts)) 

579 # we are inside a make_report hook so 

580 # we cannot directly pass through the exception 

581 self._notfound.append((report_arg, exc)) 

582 

583 self.trace.root.indent -= 1 

584 self._collection_node_cache1.clear() 

585 self._collection_node_cache2.clear() 

586 self._collection_node_cache3.clear() 

587 self._collection_pkg_roots.clear() 

588 

589 def _collect( 

590 self, argpath: py.path.local, names: List[str] 

591 ) -> Iterator[Union[nodes.Item, nodes.Collector]]: 

592 from _pytest.python import Package 

593 

594 # Start with a Session root, and delve to argpath item (dir or file) 

595 # and stack all Packages found on the way. 

596 # No point in finding packages when collecting doctests 

597 if not self.config.getoption("doctestmodules", False): 

598 pm = self.config.pluginmanager 

599 for parent in reversed(argpath.parts()): 

600 if pm._confcutdir and pm._confcutdir.relto(parent): 

601 break 

602 

603 if parent.isdir(): 

604 pkginit = parent.join("__init__.py") 

605 if pkginit.isfile(): 

606 if pkginit not in self._collection_node_cache1: 

607 col = self._collectfile(pkginit, handle_dupes=False) 

608 if col: 

609 if isinstance(col[0], Package): 

610 self._collection_pkg_roots[str(parent)] = col[0] 

611 # always store a list in the cache, matchnodes expects it 

612 self._collection_node_cache1[col[0].fspath] = [col[0]] 

613 

614 # If it's a directory argument, recurse and look for any Subpackages. 

615 # Let the Package collector deal with subnodes, don't collect here. 

616 if argpath.check(dir=1): 

617 assert not names, "invalid arg {!r}".format((argpath, names)) 

618 

619 seen_dirs = set() # type: Set[py.path.local] 

620 for path in argpath.visit( 

621 fil=self._visit_filter, rec=self._recurse, bf=True, sort=True 

622 ): 

623 dirpath = path.dirpath() 

624 if dirpath not in seen_dirs: 

625 # Collect packages first. 

626 seen_dirs.add(dirpath) 

627 pkginit = dirpath.join("__init__.py") 

628 if pkginit.exists(): 

629 for x in self._collectfile(pkginit): 

630 yield x 

631 if isinstance(x, Package): 

632 self._collection_pkg_roots[str(dirpath)] = x 

633 if str(dirpath) in self._collection_pkg_roots: 

634 # Do not collect packages here. 

635 continue 

636 

637 for x in self._collectfile(path): 

638 key = (type(x), x.fspath) 

639 if key in self._collection_node_cache2: 

640 yield self._collection_node_cache2[key] 

641 else: 

642 self._collection_node_cache2[key] = x 

643 yield x 

644 else: 

645 assert argpath.check(file=1) 

646 

647 if argpath in self._collection_node_cache1: 

648 col = self._collection_node_cache1[argpath] 

649 else: 

650 collect_root = self._collection_pkg_roots.get(argpath.dirname, self) 

651 col = collect_root._collectfile(argpath, handle_dupes=False) 

652 if col: 

653 self._collection_node_cache1[argpath] = col 

654 m = self.matchnodes(col, names) 

655 # If __init__.py was the only file requested, then the matched node will be 

656 # the corresponding Package, and the first yielded item will be the __init__ 

657 # Module itself, so just use that. If this special case isn't taken, then all 

658 # the files in the package will be yielded. 

659 if argpath.basename == "__init__.py": 

660 assert isinstance(m[0], nodes.Collector) 

661 try: 

662 yield next(iter(m[0].collect())) 

663 except StopIteration: 

664 # The package collects nothing with only an __init__.py 

665 # file in it, which gets ignored by the default 

666 # "python_files" option. 

667 pass 

668 return 

669 yield from m 

670 

671 @staticmethod 

672 def _visit_filter(f: py.path.local) -> bool: 

673 # TODO: Remove type: ignore once `py` is typed. 

674 return f.check(file=1) # type: ignore 

675 

676 def _tryconvertpyarg(self, x: str) -> str: 

677 """Convert a dotted module name to path.""" 

678 try: 

679 spec = importlib.util.find_spec(x) 

680 # AttributeError: looks like package module, but actually filename 

681 # ImportError: module does not exist 

682 # ValueError: not a module name 

683 except (AttributeError, ImportError, ValueError): 

684 return x 

685 if spec is None or spec.origin is None or spec.origin == "namespace": 

686 return x 

687 elif spec.submodule_search_locations: 

688 return os.path.dirname(spec.origin) 

689 else: 

690 return spec.origin 

691 

692 def _parsearg(self, arg: str) -> Tuple[py.path.local, List[str]]: 

693 """ return (fspath, names) tuple after checking the file exists. """ 

694 strpath, *parts = str(arg).split("::") 

695 if self.config.option.pyargs: 

696 strpath = self._tryconvertpyarg(strpath) 

697 relpath = strpath.replace("/", os.sep) 

698 fspath = self.config.invocation_dir.join(relpath, abs=True) 

699 if not fspath.check(): 

700 if self.config.option.pyargs: 

701 raise UsageError( 

702 "file or package not found: " + arg + " (missing __init__.py?)" 

703 ) 

704 raise UsageError("file not found: " + arg) 

705 return (fspath, parts) 

706 

707 def matchnodes( 

708 self, matching: Sequence[Union[nodes.Item, nodes.Collector]], names: List[str], 

709 ) -> Sequence[Union[nodes.Item, nodes.Collector]]: 

710 self.trace("matchnodes", matching, names) 

711 self.trace.root.indent += 1 

712 nodes = self._matchnodes(matching, names) 

713 num = len(nodes) 

714 self.trace("matchnodes finished -> ", num, "nodes") 

715 self.trace.root.indent -= 1 

716 if num == 0: 

717 raise NoMatch(matching, names[:1]) 

718 return nodes 

719 

720 def _matchnodes( 

721 self, matching: Sequence[Union[nodes.Item, nodes.Collector]], names: List[str], 

722 ) -> Sequence[Union[nodes.Item, nodes.Collector]]: 

723 if not matching or not names: 

724 return matching 

725 name = names[0] 

726 assert name 

727 nextnames = names[1:] 

728 resultnodes = [] # type: List[Union[nodes.Item, nodes.Collector]] 

729 for node in matching: 

730 if isinstance(node, nodes.Item): 

731 if not names: 

732 resultnodes.append(node) 

733 continue 

734 assert isinstance(node, nodes.Collector) 

735 key = (type(node), node.nodeid) 

736 if key in self._collection_node_cache3: 

737 rep = self._collection_node_cache3[key] 

738 else: 

739 rep = collect_one_node(node) 

740 self._collection_node_cache3[key] = rep 

741 if rep.passed: 

742 has_matched = False 

743 for x in rep.result: 

744 # TODO: remove parametrized workaround once collection structure contains parametrization 

745 if x.name == name or x.name.split("[")[0] == name: 

746 resultnodes.extend(self.matchnodes([x], nextnames)) 

747 has_matched = True 

748 # XXX accept IDs that don't have "()" for class instances 

749 if not has_matched and len(rep.result) == 1 and x.name == "()": 

750 nextnames.insert(0, name) 

751 resultnodes.extend(self.matchnodes([x], nextnames)) 

752 else: 

753 # report collection failures here to avoid failing to run some test 

754 # specified in the command line because the module could not be 

755 # imported (#134) 

756 node.ihook.pytest_collectreport(report=rep) 

757 return resultnodes 

758 

759 def genitems( 

760 self, node: Union[nodes.Item, nodes.Collector] 

761 ) -> Iterator[nodes.Item]: 

762 self.trace("genitems", node) 

763 if isinstance(node, nodes.Item): 

764 node.ihook.pytest_itemcollected(item=node) 

765 yield node 

766 else: 

767 assert isinstance(node, nodes.Collector) 

768 rep = collect_one_node(node) 

769 if rep.passed: 

770 for subnode in rep.result: 

771 yield from self.genitems(subnode) 

772 node.ihook.pytest_collectreport(report=rep)