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""" command line options, ini-file and conftest.py processing. """ 

2import argparse 

3import copy 

4import inspect 

5import os 

6import shlex 

7import sys 

8import types 

9import warnings 

10from functools import lru_cache 

11from types import TracebackType 

12from typing import Any 

13from typing import Callable 

14from typing import Dict 

15from typing import List 

16from typing import Optional 

17from typing import Sequence 

18from typing import Set 

19from typing import Tuple 

20from typing import Union 

21 

22import attr 

23import py 

24from packaging.version import Version 

25from pluggy import HookimplMarker 

26from pluggy import HookspecMarker 

27from pluggy import PluginManager 

28 

29import _pytest._code 

30import _pytest.assertion 

31import _pytest.deprecated 

32import _pytest.hookspec # the extension point definitions 

33from .exceptions import PrintHelp 

34from .exceptions import UsageError 

35from .findpaths import determine_setup 

36from .findpaths import exists 

37from _pytest._code import ExceptionInfo 

38from _pytest._code import filter_traceback 

39from _pytest.compat import importlib_metadata 

40from _pytest.compat import TYPE_CHECKING 

41from _pytest.outcomes import fail 

42from _pytest.outcomes import Skipped 

43from _pytest.pathlib import Path 

44from _pytest.warning_types import PytestConfigWarning 

45 

46if TYPE_CHECKING: 

47 from typing import Type 

48 

49 

50hookimpl = HookimplMarker("pytest") 

51hookspec = HookspecMarker("pytest") 

52 

53 

54class ConftestImportFailure(Exception): 

55 def __init__(self, path, excinfo): 

56 Exception.__init__(self, path, excinfo) 

57 self.path = path 

58 self.excinfo = excinfo # type: Tuple[Type[Exception], Exception, TracebackType] 

59 

60 

61def main(args=None, plugins=None) -> "Union[int, _pytest.main.ExitCode]": 

62 """ return exit code, after performing an in-process test run. 

63 

64 :arg args: list of command line arguments. 

65 

66 :arg plugins: list of plugin objects to be auto-registered during 

67 initialization. 

68 """ 

69 from _pytest.main import ExitCode 

70 

71 try: 

72 try: 

73 config = _prepareconfig(args, plugins) 

74 except ConftestImportFailure as e: 

75 exc_info = ExceptionInfo(e.excinfo) 

76 tw = py.io.TerminalWriter(sys.stderr) 

77 tw.line( 

78 "ImportError while loading conftest '{e.path}'.".format(e=e), red=True 

79 ) 

80 exc_info.traceback = exc_info.traceback.filter(filter_traceback) 

81 exc_repr = ( 

82 exc_info.getrepr(style="short", chain=False) 

83 if exc_info.traceback 

84 else exc_info.exconly() 

85 ) 

86 formatted_tb = str(exc_repr) 

87 for line in formatted_tb.splitlines(): 

88 tw.line(line.rstrip(), red=True) 

89 return ExitCode.USAGE_ERROR 

90 else: 

91 try: 

92 ret = config.hook.pytest_cmdline_main( 

93 config=config 

94 ) # type: Union[ExitCode, int] 

95 try: 

96 return ExitCode(ret) 

97 except ValueError: 

98 return ret 

99 finally: 

100 config._ensure_unconfigure() 

101 except UsageError as e: 

102 tw = py.io.TerminalWriter(sys.stderr) 

103 for msg in e.args: 

104 tw.line("ERROR: {}\n".format(msg), red=True) 

105 return ExitCode.USAGE_ERROR 

106 

107 

108class cmdline: # compatibility namespace 

109 main = staticmethod(main) 

110 

111 

112def filename_arg(path, optname): 

113 """ Argparse type validator for filename arguments. 

114 

115 :path: path of filename 

116 :optname: name of the option 

117 """ 

118 if os.path.isdir(path): 

119 raise UsageError("{} must be a filename, given: {}".format(optname, path)) 

120 return path 

121 

122 

123def directory_arg(path, optname): 

124 """Argparse type validator for directory arguments. 

125 

126 :path: path of directory 

127 :optname: name of the option 

128 """ 

129 if not os.path.isdir(path): 

130 raise UsageError("{} must be a directory, given: {}".format(optname, path)) 

131 return path 

132 

133 

134# Plugins that cannot be disabled via "-p no:X" currently. 

135essential_plugins = ( 

136 "mark", 

137 "main", 

138 "runner", 

139 "fixtures", 

140 "helpconfig", # Provides -p. 

141) 

142 

143default_plugins = essential_plugins + ( 

144 "python", 

145 "terminal", 

146 "debugging", 

147 "unittest", 

148 "capture", 

149 "skipping", 

150 "tmpdir", 

151 "monkeypatch", 

152 "recwarn", 

153 "pastebin", 

154 "nose", 

155 "assertion", 

156 "junitxml", 

157 "resultlog", 

158 "doctest", 

159 "cacheprovider", 

160 "freeze_support", 

161 "setuponly", 

162 "setupplan", 

163 "stepwise", 

164 "warnings", 

165 "logging", 

166 "reports", 

167 "faulthandler", 

168) 

169 

170builtin_plugins = set(default_plugins) 

171builtin_plugins.add("pytester") 

172 

173 

174def get_config(args=None, plugins=None): 

175 # subsequent calls to main will create a fresh instance 

176 pluginmanager = PytestPluginManager() 

177 config = Config( 

178 pluginmanager, 

179 invocation_params=Config.InvocationParams( 

180 args=args or (), plugins=plugins, dir=Path().resolve() 

181 ), 

182 ) 

183 

184 if args is not None: 

185 # Handle any "-p no:plugin" args. 

186 pluginmanager.consider_preparse(args) 

187 

188 for spec in default_plugins: 

189 pluginmanager.import_plugin(spec) 

190 return config 

191 

192 

193def get_plugin_manager(): 

194 """ 

195 Obtain a new instance of the 

196 :py:class:`_pytest.config.PytestPluginManager`, with default plugins 

197 already loaded. 

198 

199 This function can be used by integration with other tools, like hooking 

200 into pytest to run tests into an IDE. 

201 """ 

202 return get_config().pluginmanager 

203 

204 

205def _prepareconfig(args=None, plugins=None): 

206 if args is None: 

207 args = sys.argv[1:] 

208 elif isinstance(args, py.path.local): 

209 args = [str(args)] 

210 elif not isinstance(args, (tuple, list)): 

211 msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})" 

212 raise TypeError(msg.format(args, type(args))) 

213 

214 config = get_config(args, plugins) 

215 pluginmanager = config.pluginmanager 

216 try: 

217 if plugins: 

218 for plugin in plugins: 

219 if isinstance(plugin, str): 

220 pluginmanager.consider_pluginarg(plugin) 

221 else: 

222 pluginmanager.register(plugin) 

223 return pluginmanager.hook.pytest_cmdline_parse( 

224 pluginmanager=pluginmanager, args=args 

225 ) 

226 except BaseException: 

227 config._ensure_unconfigure() 

228 raise 

229 

230 

231def _fail_on_non_top_pytest_plugins(conftestpath, confcutdir): 

232 msg = ( 

233 "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n" 

234 "It affects the entire test suite instead of just below the conftest as expected.\n" 

235 " {}\n" 

236 "Please move it to a top level conftest file at the rootdir:\n" 

237 " {}\n" 

238 "For more information, visit:\n" 

239 " https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files" 

240 ) 

241 fail(msg.format(conftestpath, confcutdir), pytrace=False) 

242 

243 

244class PytestPluginManager(PluginManager): 

245 """ 

246 Overwrites :py:class:`pluggy.PluginManager <pluggy.PluginManager>` to add pytest-specific 

247 functionality: 

248 

249 * loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and 

250 ``pytest_plugins`` global variables found in plugins being loaded; 

251 * ``conftest.py`` loading during start-up; 

252 """ 

253 

254 def __init__(self): 

255 super().__init__("pytest") 

256 # The objects are module objects, only used generically. 

257 self._conftest_plugins = set() # type: Set[object] 

258 

259 # state related to local conftest plugins 

260 # Maps a py.path.local to a list of module objects. 

261 self._dirpath2confmods = {} # type: Dict[Any, List[object]] 

262 # Maps a py.path.local to a module object. 

263 self._conftestpath2mod = {} # type: Dict[Any, object] 

264 self._confcutdir = None 

265 self._noconftest = False 

266 # Set of py.path.local's. 

267 self._duplicatepaths = set() # type: Set[Any] 

268 

269 self.add_hookspecs(_pytest.hookspec) 

270 self.register(self) 

271 if os.environ.get("PYTEST_DEBUG"): 

272 err = sys.stderr 

273 encoding = getattr(err, "encoding", "utf8") 

274 try: 

275 err = py.io.dupfile(err, encoding=encoding) 

276 except Exception: 

277 pass 

278 self.trace.root.setwriter(err.write) 

279 self.enable_tracing() 

280 

281 # Config._consider_importhook will set a real object if required. 

282 self.rewrite_hook = _pytest.assertion.DummyRewriteHook() 

283 # Used to know when we are importing conftests after the pytest_configure stage 

284 self._configured = False 

285 

286 def parse_hookimpl_opts(self, plugin, name): 

287 # pytest hooks are always prefixed with pytest_ 

288 # so we avoid accessing possibly non-readable attributes 

289 # (see issue #1073) 

290 if not name.startswith("pytest_"): 

291 return 

292 # ignore names which can not be hooks 

293 if name == "pytest_plugins": 

294 return 

295 

296 method = getattr(plugin, name) 

297 opts = super().parse_hookimpl_opts(plugin, name) 

298 

299 # consider only actual functions for hooks (#3775) 

300 if not inspect.isroutine(method): 

301 return 

302 

303 # collect unmarked hooks as long as they have the `pytest_' prefix 

304 if opts is None and name.startswith("pytest_"): 

305 opts = {} 

306 if opts is not None: 

307 # TODO: DeprecationWarning, people should use hookimpl 

308 # https://github.com/pytest-dev/pytest/issues/4562 

309 known_marks = {m.name for m in getattr(method, "pytestmark", [])} 

310 

311 for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"): 

312 opts.setdefault(name, hasattr(method, name) or name in known_marks) 

313 return opts 

314 

315 def parse_hookspec_opts(self, module_or_class, name): 

316 opts = super().parse_hookspec_opts(module_or_class, name) 

317 if opts is None: 

318 method = getattr(module_or_class, name) 

319 

320 if name.startswith("pytest_"): 

321 # todo: deprecate hookspec hacks 

322 # https://github.com/pytest-dev/pytest/issues/4562 

323 known_marks = {m.name for m in getattr(method, "pytestmark", [])} 

324 opts = { 

325 "firstresult": hasattr(method, "firstresult") 

326 or "firstresult" in known_marks, 

327 "historic": hasattr(method, "historic") 

328 or "historic" in known_marks, 

329 } 

330 return opts 

331 

332 def register(self, plugin, name=None): 

333 if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: 

334 warnings.warn( 

335 PytestConfigWarning( 

336 "{} plugin has been merged into the core, " 

337 "please remove it from your requirements.".format( 

338 name.replace("_", "-") 

339 ) 

340 ) 

341 ) 

342 return 

343 ret = super().register(plugin, name) 

344 if ret: 

345 self.hook.pytest_plugin_registered.call_historic( 

346 kwargs=dict(plugin=plugin, manager=self) 

347 ) 

348 

349 if isinstance(plugin, types.ModuleType): 

350 self.consider_module(plugin) 

351 return ret 

352 

353 def getplugin(self, name): 

354 # support deprecated naming because plugins (xdist e.g.) use it 

355 return self.get_plugin(name) 

356 

357 def hasplugin(self, name): 

358 """Return True if the plugin with the given name is registered.""" 

359 return bool(self.get_plugin(name)) 

360 

361 def pytest_configure(self, config): 

362 # XXX now that the pluginmanager exposes hookimpl(tryfirst...) 

363 # we should remove tryfirst/trylast as markers 

364 config.addinivalue_line( 

365 "markers", 

366 "tryfirst: mark a hook implementation function such that the " 

367 "plugin machinery will try to call it first/as early as possible.", 

368 ) 

369 config.addinivalue_line( 

370 "markers", 

371 "trylast: mark a hook implementation function such that the " 

372 "plugin machinery will try to call it last/as late as possible.", 

373 ) 

374 self._configured = True 

375 

376 # 

377 # internal API for local conftest plugin handling 

378 # 

379 def _set_initial_conftests(self, namespace): 

380 """ load initial conftest files given a preparsed "namespace". 

381 As conftest files may add their own command line options 

382 which have arguments ('--my-opt somepath') we might get some 

383 false positives. All builtin and 3rd party plugins will have 

384 been loaded, however, so common options will not confuse our logic 

385 here. 

386 """ 

387 current = py.path.local() 

388 self._confcutdir = ( 

389 current.join(namespace.confcutdir, abs=True) 

390 if namespace.confcutdir 

391 else None 

392 ) 

393 self._noconftest = namespace.noconftest 

394 self._using_pyargs = namespace.pyargs 

395 testpaths = namespace.file_or_dir 

396 foundanchor = False 

397 for path in testpaths: 

398 path = str(path) 

399 # remove node-id syntax 

400 i = path.find("::") 

401 if i != -1: 

402 path = path[:i] 

403 anchor = current.join(path, abs=1) 

404 if exists(anchor): # we found some file object 

405 self._try_load_conftest(anchor) 

406 foundanchor = True 

407 if not foundanchor: 

408 self._try_load_conftest(current) 

409 

410 def _try_load_conftest(self, anchor): 

411 self._getconftestmodules(anchor) 

412 # let's also consider test* subdirs 

413 if anchor.check(dir=1): 

414 for x in anchor.listdir("test*"): 

415 if x.check(dir=1): 

416 self._getconftestmodules(x) 

417 

418 @lru_cache(maxsize=128) 

419 def _getconftestmodules(self, path): 

420 if self._noconftest: 

421 return [] 

422 

423 if path.isfile(): 

424 directory = path.dirpath() 

425 else: 

426 directory = path 

427 

428 # XXX these days we may rather want to use config.rootdir 

429 # and allow users to opt into looking into the rootdir parent 

430 # directories instead of requiring to specify confcutdir 

431 clist = [] 

432 for parent in directory.realpath().parts(): 

433 if self._confcutdir and self._confcutdir.relto(parent): 

434 continue 

435 conftestpath = parent.join("conftest.py") 

436 if conftestpath.isfile(): 

437 mod = self._importconftest(conftestpath) 

438 clist.append(mod) 

439 self._dirpath2confmods[directory] = clist 

440 return clist 

441 

442 def _rget_with_confmod(self, name, path): 

443 modules = self._getconftestmodules(path) 

444 for mod in reversed(modules): 

445 try: 

446 return mod, getattr(mod, name) 

447 except AttributeError: 

448 continue 

449 raise KeyError(name) 

450 

451 def _importconftest(self, conftestpath): 

452 # Use a resolved Path object as key to avoid loading the same conftest twice 

453 # with build systems that create build directories containing 

454 # symlinks to actual files. 

455 # Using Path().resolve() is better than py.path.realpath because 

456 # it resolves to the correct path/drive in case-insensitive file systems (#5792) 

457 key = Path(str(conftestpath)).resolve() 

458 try: 

459 return self._conftestpath2mod[key] 

460 except KeyError: 

461 pkgpath = conftestpath.pypkgpath() 

462 if pkgpath is None: 

463 _ensure_removed_sysmodule(conftestpath.purebasename) 

464 try: 

465 mod = conftestpath.pyimport() 

466 if ( 

467 hasattr(mod, "pytest_plugins") 

468 and self._configured 

469 and not self._using_pyargs 

470 ): 

471 _fail_on_non_top_pytest_plugins(conftestpath, self._confcutdir) 

472 except Exception: 

473 raise ConftestImportFailure(conftestpath, sys.exc_info()) 

474 

475 self._conftest_plugins.add(mod) 

476 self._conftestpath2mod[key] = mod 

477 dirpath = conftestpath.dirpath() 

478 if dirpath in self._dirpath2confmods: 

479 for path, mods in self._dirpath2confmods.items(): 

480 if path and path.relto(dirpath) or path == dirpath: 

481 assert mod not in mods 

482 mods.append(mod) 

483 self.trace("loaded conftestmodule %r" % (mod)) 

484 self.consider_conftest(mod) 

485 return mod 

486 

487 # 

488 # API for bootstrapping plugin loading 

489 # 

490 # 

491 

492 def consider_preparse(self, args): 

493 i = 0 

494 n = len(args) 

495 while i < n: 

496 opt = args[i] 

497 i += 1 

498 if isinstance(opt, str): 

499 if opt == "-p": 

500 try: 

501 parg = args[i] 

502 except IndexError: 

503 return 

504 i += 1 

505 elif opt.startswith("-p"): 

506 parg = opt[2:] 

507 else: 

508 continue 

509 self.consider_pluginarg(parg) 

510 

511 def consider_pluginarg(self, arg): 

512 if arg.startswith("no:"): 

513 name = arg[3:] 

514 if name in essential_plugins: 

515 raise UsageError("plugin %s cannot be disabled" % name) 

516 

517 # PR #4304 : remove stepwise if cacheprovider is blocked 

518 if name == "cacheprovider": 

519 self.set_blocked("stepwise") 

520 self.set_blocked("pytest_stepwise") 

521 

522 self.set_blocked(name) 

523 if not name.startswith("pytest_"): 

524 self.set_blocked("pytest_" + name) 

525 else: 

526 name = arg 

527 # Unblock the plugin. None indicates that it has been blocked. 

528 # There is no interface with pluggy for this. 

529 if self._name2plugin.get(name, -1) is None: 

530 del self._name2plugin[name] 

531 if not name.startswith("pytest_"): 

532 if self._name2plugin.get("pytest_" + name, -1) is None: 

533 del self._name2plugin["pytest_" + name] 

534 self.import_plugin(arg, consider_entry_points=True) 

535 

536 def consider_conftest(self, conftestmodule): 

537 self.register(conftestmodule, name=conftestmodule.__file__) 

538 

539 def consider_env(self): 

540 self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) 

541 

542 def consider_module(self, mod): 

543 self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) 

544 

545 def _import_plugin_specs(self, spec): 

546 plugins = _get_plugin_specs_as_list(spec) 

547 for import_spec in plugins: 

548 self.import_plugin(import_spec) 

549 

550 def import_plugin(self, modname, consider_entry_points=False): 

551 """ 

552 Imports a plugin with ``modname``. If ``consider_entry_points`` is True, entry point 

553 names are also considered to find a plugin. 

554 """ 

555 # most often modname refers to builtin modules, e.g. "pytester", 

556 # "terminal" or "capture". Those plugins are registered under their 

557 # basename for historic purposes but must be imported with the 

558 # _pytest prefix. 

559 assert isinstance(modname, str), ( 

560 "module name as text required, got %r" % modname 

561 ) 

562 modname = str(modname) 

563 if self.is_blocked(modname) or self.get_plugin(modname) is not None: 

564 return 

565 

566 importspec = "_pytest." + modname if modname in builtin_plugins else modname 

567 self.rewrite_hook.mark_rewrite(importspec) 

568 

569 if consider_entry_points: 

570 loaded = self.load_setuptools_entrypoints("pytest11", name=modname) 

571 if loaded: 

572 return 

573 

574 try: 

575 __import__(importspec) 

576 except ImportError as e: 

577 new_exc_message = 'Error importing plugin "{}": {}'.format( 

578 modname, str(e.args[0]) 

579 ) 

580 new_exc = ImportError(new_exc_message) 

581 tb = sys.exc_info()[2] 

582 

583 raise new_exc.with_traceback(tb) 

584 

585 except Skipped as e: 

586 from _pytest.warnings import _issue_warning_captured 

587 

588 _issue_warning_captured( 

589 PytestConfigWarning("skipped plugin {!r}: {}".format(modname, e.msg)), 

590 self.hook, 

591 stacklevel=1, 

592 ) 

593 else: 

594 mod = sys.modules[importspec] 

595 self.register(mod, modname) 

596 

597 

598def _get_plugin_specs_as_list(specs): 

599 """ 

600 Parses a list of "plugin specs" and returns a list of plugin names. 

601 

602 Plugin specs can be given as a list of strings separated by "," or already as a list/tuple in 

603 which case it is returned as a list. Specs can also be `None` in which case an 

604 empty list is returned. 

605 """ 

606 if specs is not None and not isinstance(specs, types.ModuleType): 

607 if isinstance(specs, str): 

608 specs = specs.split(",") if specs else [] 

609 if not isinstance(specs, (list, tuple)): 

610 raise UsageError( 

611 "Plugin specs must be a ','-separated string or a " 

612 "list/tuple of strings for plugin names. Given: %r" % specs 

613 ) 

614 return list(specs) 

615 return [] 

616 

617 

618def _ensure_removed_sysmodule(modname): 

619 try: 

620 del sys.modules[modname] 

621 except KeyError: 

622 pass 

623 

624 

625class Notset: 

626 def __repr__(self): 

627 return "<NOTSET>" 

628 

629 

630notset = Notset() 

631 

632 

633def _iter_rewritable_modules(package_files): 

634 """ 

635 Given an iterable of file names in a source distribution, return the "names" that should 

636 be marked for assertion rewrite (for example the package "pytest_mock/__init__.py" should 

637 be added as "pytest_mock" in the assertion rewrite mechanism. 

638 

639 This function has to deal with dist-info based distributions and egg based distributions 

640 (which are still very much in use for "editable" installs). 

641 

642 Here are the file names as seen in a dist-info based distribution: 

643 

644 pytest_mock/__init__.py 

645 pytest_mock/_version.py 

646 pytest_mock/plugin.py 

647 pytest_mock.egg-info/PKG-INFO 

648 

649 Here are the file names as seen in an egg based distribution: 

650 

651 src/pytest_mock/__init__.py 

652 src/pytest_mock/_version.py 

653 src/pytest_mock/plugin.py 

654 src/pytest_mock.egg-info/PKG-INFO 

655 LICENSE 

656 setup.py 

657 

658 We have to take in account those two distribution flavors in order to determine which 

659 names should be considered for assertion rewriting. 

660 

661 More information: 

662 https://github.com/pytest-dev/pytest-mock/issues/167 

663 """ 

664 package_files = list(package_files) 

665 seen_some = False 

666 for fn in package_files: 

667 is_simple_module = "/" not in fn and fn.endswith(".py") 

668 is_package = fn.count("/") == 1 and fn.endswith("__init__.py") 

669 if is_simple_module: 

670 module_name, _ = os.path.splitext(fn) 

671 # we ignore "setup.py" at the root of the distribution 

672 if module_name != "setup": 

673 seen_some = True 

674 yield module_name 

675 elif is_package: 

676 package_name = os.path.dirname(fn) 

677 seen_some = True 

678 yield package_name 

679 

680 if not seen_some: 

681 # at this point we did not find any packages or modules suitable for assertion 

682 # rewriting, so we try again by stripping the first path component (to account for 

683 # "src" based source trees for example) 

684 # this approach lets us have the common case continue to be fast, as egg-distributions 

685 # are rarer 

686 new_package_files = [] 

687 for fn in package_files: 

688 parts = fn.split("/") 

689 new_fn = "/".join(parts[1:]) 

690 if new_fn: 

691 new_package_files.append(new_fn) 

692 if new_package_files: 

693 yield from _iter_rewritable_modules(new_package_files) 

694 

695 

696class Config: 

697 """ 

698 Access to configuration values, pluginmanager and plugin hooks. 

699 

700 :ivar PytestPluginManager pluginmanager: the plugin manager handles plugin registration and hook invocation. 

701 

702 :ivar argparse.Namespace option: access to command line option as attributes. 

703 

704 :ivar InvocationParams invocation_params: 

705 

706 Object containing the parameters regarding the ``pytest.main`` 

707 invocation. 

708 

709 Contains the following read-only attributes: 

710 

711 * ``args``: tuple of command-line arguments as passed to ``pytest.main()``. 

712 * ``plugins``: list of extra plugins, might be None. 

713 * ``dir``: directory where ``pytest.main()`` was invoked from. 

714 """ 

715 

716 @attr.s(frozen=True) 

717 class InvocationParams: 

718 """Holds parameters passed during ``pytest.main()`` 

719 

720 .. versionadded:: 5.1 

721 

722 .. note:: 

723 

724 Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts`` 

725 ini option are handled by pytest, not being included in the ``args`` attribute. 

726 

727 Plugins accessing ``InvocationParams`` must be aware of that. 

728 """ 

729 

730 args = attr.ib(converter=tuple) 

731 plugins = attr.ib() 

732 dir = attr.ib(type=Path) 

733 

734 def __init__(self, pluginmanager, *, invocation_params=None): 

735 from .argparsing import Parser, FILE_OR_DIR 

736 

737 if invocation_params is None: 

738 invocation_params = self.InvocationParams( 

739 args=(), plugins=None, dir=Path().resolve() 

740 ) 

741 

742 self.option = argparse.Namespace() 

743 self.invocation_params = invocation_params 

744 

745 _a = FILE_OR_DIR 

746 self._parser = Parser( 

747 usage="%(prog)s [options] [{}] [{}] [...]".format(_a, _a), 

748 processopt=self._processopt, 

749 ) 

750 self.pluginmanager = pluginmanager 

751 self.trace = self.pluginmanager.trace.root.get("config") 

752 self.hook = self.pluginmanager.hook 

753 self._inicache = {} # type: Dict[str, Any] 

754 self._override_ini = () # type: Sequence[str] 

755 self._opt2dest = {} # type: Dict[str, str] 

756 self._cleanup = [] # type: List[Callable[[], None]] 

757 self.pluginmanager.register(self, "pytestconfig") 

758 self._configured = False 

759 self.hook.pytest_addoption.call_historic( 

760 kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) 

761 ) 

762 

763 @property 

764 def invocation_dir(self): 

765 """Backward compatibility""" 

766 return py.path.local(str(self.invocation_params.dir)) 

767 

768 def add_cleanup(self, func): 

769 """ Add a function to be called when the config object gets out of 

770 use (usually coninciding with pytest_unconfigure).""" 

771 self._cleanup.append(func) 

772 

773 def _do_configure(self): 

774 assert not self._configured 

775 self._configured = True 

776 with warnings.catch_warnings(): 

777 warnings.simplefilter("default") 

778 self.hook.pytest_configure.call_historic(kwargs=dict(config=self)) 

779 

780 def _ensure_unconfigure(self): 

781 if self._configured: 

782 self._configured = False 

783 self.hook.pytest_unconfigure(config=self) 

784 self.hook.pytest_configure._call_history = [] 

785 while self._cleanup: 

786 fin = self._cleanup.pop() 

787 fin() 

788 

789 def get_terminal_writer(self): 

790 return self.pluginmanager.get_plugin("terminalreporter")._tw 

791 

792 def pytest_cmdline_parse(self, pluginmanager, args): 

793 try: 

794 self.parse(args) 

795 except UsageError: 

796 

797 # Handle --version and --help here in a minimal fashion. 

798 # This gets done via helpconfig normally, but its 

799 # pytest_cmdline_main is not called in case of errors. 

800 if getattr(self.option, "version", False) or "--version" in args: 

801 from _pytest.helpconfig import showversion 

802 

803 showversion(self) 

804 elif ( 

805 getattr(self.option, "help", False) or "--help" in args or "-h" in args 

806 ): 

807 self._parser._getparser().print_help() 

808 sys.stdout.write( 

809 "\nNOTE: displaying only minimal help due to UsageError.\n\n" 

810 ) 

811 

812 raise 

813 

814 return self 

815 

816 def notify_exception(self, excinfo, option=None): 

817 if option and getattr(option, "fulltrace", False): 

818 style = "long" 

819 else: 

820 style = "native" 

821 excrepr = excinfo.getrepr( 

822 funcargs=True, showlocals=getattr(option, "showlocals", False), style=style 

823 ) 

824 res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo) 

825 if not any(res): 

826 for line in str(excrepr).split("\n"): 

827 sys.stderr.write("INTERNALERROR> %s\n" % line) 

828 sys.stderr.flush() 

829 

830 def cwd_relative_nodeid(self, nodeid): 

831 # nodeid's are relative to the rootpath, compute relative to cwd 

832 if self.invocation_dir != self.rootdir: 

833 fullpath = self.rootdir.join(nodeid) 

834 nodeid = self.invocation_dir.bestrelpath(fullpath) 

835 return nodeid 

836 

837 @classmethod 

838 def fromdictargs(cls, option_dict, args): 

839 """ constructor usable for subprocesses. """ 

840 config = get_config(args) 

841 config.option.__dict__.update(option_dict) 

842 config.parse(args, addopts=False) 

843 for x in config.option.plugins: 

844 config.pluginmanager.consider_pluginarg(x) 

845 return config 

846 

847 def _processopt(self, opt): 

848 for name in opt._short_opts + opt._long_opts: 

849 self._opt2dest[name] = opt.dest 

850 

851 if hasattr(opt, "default") and opt.dest: 

852 if not hasattr(self.option, opt.dest): 

853 setattr(self.option, opt.dest, opt.default) 

854 

855 @hookimpl(trylast=True) 

856 def pytest_load_initial_conftests(self, early_config): 

857 self.pluginmanager._set_initial_conftests(early_config.known_args_namespace) 

858 

859 def _initini(self, args) -> None: 

860 ns, unknown_args = self._parser.parse_known_and_unknown_args( 

861 args, namespace=copy.copy(self.option) 

862 ) 

863 r = determine_setup( 

864 ns.inifilename, 

865 ns.file_or_dir + unknown_args, 

866 rootdir_cmd_arg=ns.rootdir or None, 

867 config=self, 

868 ) 

869 self.rootdir, self.inifile, self.inicfg = r 

870 self._parser.extra_info["rootdir"] = self.rootdir 

871 self._parser.extra_info["inifile"] = self.inifile 

872 self._parser.addini("addopts", "extra command line options", "args") 

873 self._parser.addini("minversion", "minimally required pytest version") 

874 self._override_ini = ns.override_ini or () 

875 

876 def _consider_importhook(self, args): 

877 """Install the PEP 302 import hook if using assertion rewriting. 

878 

879 Needs to parse the --assert=<mode> option from the commandline 

880 and find all the installed plugins to mark them for rewriting 

881 by the importhook. 

882 """ 

883 ns, unknown_args = self._parser.parse_known_and_unknown_args(args) 

884 mode = getattr(ns, "assertmode", "plain") 

885 if mode == "rewrite": 

886 try: 

887 hook = _pytest.assertion.install_importhook(self) 

888 except SystemError: 

889 mode = "plain" 

890 else: 

891 self._mark_plugins_for_rewrite(hook) 

892 _warn_about_missing_assertion(mode) 

893 

894 def _mark_plugins_for_rewrite(self, hook): 

895 """ 

896 Given an importhook, mark for rewrite any top-level 

897 modules or packages in the distribution package for 

898 all pytest plugins. 

899 """ 

900 self.pluginmanager.rewrite_hook = hook 

901 

902 if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): 

903 # We don't autoload from setuptools entry points, no need to continue. 

904 return 

905 

906 package_files = ( 

907 str(file) 

908 for dist in importlib_metadata.distributions() 

909 if any(ep.group == "pytest11" for ep in dist.entry_points) 

910 for file in dist.files or [] 

911 ) 

912 

913 for name in _iter_rewritable_modules(package_files): 

914 hook.mark_rewrite(name) 

915 

916 def _validate_args(self, args, via): 

917 """Validate known args.""" 

918 self._parser._config_source_hint = via 

919 try: 

920 self._parser.parse_known_and_unknown_args( 

921 args, namespace=copy.copy(self.option) 

922 ) 

923 finally: 

924 del self._parser._config_source_hint 

925 

926 return args 

927 

928 def _preparse(self, args, addopts=True): 

929 if addopts: 

930 env_addopts = os.environ.get("PYTEST_ADDOPTS", "") 

931 if len(env_addopts): 

932 args[:] = ( 

933 self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS") 

934 + args 

935 ) 

936 self._initini(args) 

937 if addopts: 

938 args[:] = ( 

939 self._validate_args(self.getini("addopts"), "via addopts config") + args 

940 ) 

941 

942 self._checkversion() 

943 self._consider_importhook(args) 

944 self.pluginmanager.consider_preparse(args) 

945 if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): 

946 # Don't autoload from setuptools entry point. Only explicitly specified 

947 # plugins are going to be loaded. 

948 self.pluginmanager.load_setuptools_entrypoints("pytest11") 

949 self.pluginmanager.consider_env() 

950 self.known_args_namespace = ns = self._parser.parse_known_args( 

951 args, namespace=copy.copy(self.option) 

952 ) 

953 if self.known_args_namespace.confcutdir is None and self.inifile: 

954 confcutdir = py.path.local(self.inifile).dirname 

955 self.known_args_namespace.confcutdir = confcutdir 

956 try: 

957 self.hook.pytest_load_initial_conftests( 

958 early_config=self, args=args, parser=self._parser 

959 ) 

960 except ConftestImportFailure as e: 

961 if ns.help or ns.version: 

962 # we don't want to prevent --help/--version to work 

963 # so just let is pass and print a warning at the end 

964 from _pytest.warnings import _issue_warning_captured 

965 

966 _issue_warning_captured( 

967 PytestConfigWarning( 

968 "could not load initial conftests: {}".format(e.path) 

969 ), 

970 self.hook, 

971 stacklevel=2, 

972 ) 

973 else: 

974 raise 

975 

976 def _checkversion(self): 

977 import pytest 

978 

979 minver = self.inicfg.get("minversion", None) 

980 if minver: 

981 if Version(minver) > Version(pytest.__version__): 

982 raise pytest.UsageError( 

983 "%s:%d: requires pytest-%s, actual pytest-%s'" 

984 % ( 

985 self.inicfg.config.path, 

986 self.inicfg.lineof("minversion"), 

987 minver, 

988 pytest.__version__, 

989 ) 

990 ) 

991 

992 def parse(self, args, addopts=True): 

993 # parse given cmdline arguments into this config object. 

994 assert not hasattr( 

995 self, "args" 

996 ), "can only parse cmdline args at most once per Config object" 

997 self.hook.pytest_addhooks.call_historic( 

998 kwargs=dict(pluginmanager=self.pluginmanager) 

999 ) 

1000 self._preparse(args, addopts=addopts) 

1001 # XXX deprecated hook: 

1002 self.hook.pytest_cmdline_preparse(config=self, args=args) 

1003 self._parser.after_preparse = True 

1004 try: 

1005 args = self._parser.parse_setoption( 

1006 args, self.option, namespace=self.option 

1007 ) 

1008 if not args: 

1009 if self.invocation_dir == self.rootdir: 

1010 args = self.getini("testpaths") 

1011 if not args: 

1012 args = [str(self.invocation_dir)] 

1013 self.args = args 

1014 except PrintHelp: 

1015 pass 

1016 

1017 def addinivalue_line(self, name, line): 

1018 """ add a line to an ini-file option. The option must have been 

1019 declared but might not yet be set in which case the line becomes the 

1020 the first line in its value. """ 

1021 x = self.getini(name) 

1022 assert isinstance(x, list) 

1023 x.append(line) # modifies the cached list inline 

1024 

1025 def getini(self, name: str): 

1026 """ return configuration value from an :ref:`ini file <inifiles>`. If the 

1027 specified name hasn't been registered through a prior 

1028 :py:func:`parser.addini <_pytest.config.argparsing.Parser.addini>` 

1029 call (usually from a plugin), a ValueError is raised. """ 

1030 try: 

1031 return self._inicache[name] 

1032 except KeyError: 

1033 self._inicache[name] = val = self._getini(name) 

1034 return val 

1035 

1036 def _getini(self, name: str) -> Any: 

1037 try: 

1038 description, type, default = self._parser._inidict[name] 

1039 except KeyError: 

1040 raise ValueError("unknown configuration value: {!r}".format(name)) 

1041 value = self._get_override_ini_value(name) 

1042 if value is None: 

1043 try: 

1044 value = self.inicfg[name] 

1045 except KeyError: 

1046 if default is not None: 

1047 return default 

1048 if type is None: 

1049 return "" 

1050 return [] 

1051 if type == "pathlist": 

1052 dp = py.path.local(self.inicfg.config.path).dirpath() 

1053 values = [] 

1054 for relpath in shlex.split(value): 

1055 values.append(dp.join(relpath, abs=True)) 

1056 return values 

1057 elif type == "args": 

1058 return shlex.split(value) 

1059 elif type == "linelist": 

1060 return [t for t in map(lambda x: x.strip(), value.split("\n")) if t] 

1061 elif type == "bool": 

1062 return bool(_strtobool(value.strip())) 

1063 else: 

1064 assert type is None 

1065 return value 

1066 

1067 def _getconftest_pathlist(self, name, path): 

1068 try: 

1069 mod, relroots = self.pluginmanager._rget_with_confmod(name, path) 

1070 except KeyError: 

1071 return None 

1072 modpath = py.path.local(mod.__file__).dirpath() 

1073 values = [] 

1074 for relroot in relroots: 

1075 if not isinstance(relroot, py.path.local): 

1076 relroot = relroot.replace("/", py.path.local.sep) 

1077 relroot = modpath.join(relroot, abs=True) 

1078 values.append(relroot) 

1079 return values 

1080 

1081 def _get_override_ini_value(self, name: str) -> Optional[str]: 

1082 value = None 

1083 # override_ini is a list of "ini=value" options 

1084 # always use the last item if multiple values are set for same ini-name, 

1085 # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2 

1086 for ini_config in self._override_ini: 

1087 try: 

1088 key, user_ini_value = ini_config.split("=", 1) 

1089 except ValueError: 

1090 raise UsageError("-o/--override-ini expects option=value style.") 

1091 else: 

1092 if key == name: 

1093 value = user_ini_value 

1094 return value 

1095 

1096 def getoption(self, name: str, default=notset, skip: bool = False): 

1097 """ return command line option value. 

1098 

1099 :arg name: name of the option. You may also specify 

1100 the literal ``--OPT`` option instead of the "dest" option name. 

1101 :arg default: default value if no option of that name exists. 

1102 :arg skip: if True raise pytest.skip if option does not exists 

1103 or has a None value. 

1104 """ 

1105 name = self._opt2dest.get(name, name) 

1106 try: 

1107 val = getattr(self.option, name) 

1108 if val is None and skip: 

1109 raise AttributeError(name) 

1110 return val 

1111 except AttributeError: 

1112 if default is not notset: 

1113 return default 

1114 if skip: 

1115 import pytest 

1116 

1117 pytest.skip("no {!r} option found".format(name)) 

1118 raise ValueError("no option named {!r}".format(name)) 

1119 

1120 def getvalue(self, name, path=None): 

1121 """ (deprecated, use getoption()) """ 

1122 return self.getoption(name) 

1123 

1124 def getvalueorskip(self, name, path=None): 

1125 """ (deprecated, use getoption(skip=True)) """ 

1126 return self.getoption(name, skip=True) 

1127 

1128 

1129def _assertion_supported(): 

1130 try: 

1131 assert False 

1132 except AssertionError: 

1133 return True 

1134 else: 

1135 return False 

1136 

1137 

1138def _warn_about_missing_assertion(mode): 

1139 if not _assertion_supported(): 

1140 if mode == "plain": 

1141 sys.stderr.write( 

1142 "WARNING: ASSERTIONS ARE NOT EXECUTED" 

1143 " and FAILING TESTS WILL PASS. Are you" 

1144 " using python -O?" 

1145 ) 

1146 else: 

1147 sys.stderr.write( 

1148 "WARNING: assertions not in test modules or" 

1149 " plugins will be ignored" 

1150 " because assert statements are not executed " 

1151 "by the underlying Python interpreter " 

1152 "(are you using python -O?)\n" 

1153 ) 

1154 

1155 

1156def setns(obj, dic): 

1157 import pytest 

1158 

1159 for name, value in dic.items(): 

1160 if isinstance(value, dict): 

1161 mod = getattr(obj, name, None) 

1162 if mod is None: 

1163 modname = "pytest.%s" % name 

1164 mod = types.ModuleType(modname) 

1165 sys.modules[modname] = mod 

1166 mod.__all__ = [] 

1167 setattr(obj, name, mod) 

1168 obj.__all__.append(name) 

1169 setns(mod, value) 

1170 else: 

1171 setattr(obj, name, value) 

1172 obj.__all__.append(name) 

1173 # if obj != pytest: 

1174 # pytest.__all__.append(name) 

1175 setattr(pytest, name, value) 

1176 

1177 

1178def create_terminal_writer(config, *args, **kwargs): 

1179 """Create a TerminalWriter instance configured according to the options 

1180 in the config object. Every code which requires a TerminalWriter object 

1181 and has access to a config object should use this function. 

1182 """ 

1183 tw = py.io.TerminalWriter(*args, **kwargs) 

1184 if config.option.color == "yes": 

1185 tw.hasmarkup = True 

1186 if config.option.color == "no": 

1187 tw.hasmarkup = False 

1188 return tw 

1189 

1190 

1191def _strtobool(val): 

1192 """Convert a string representation of truth to true (1) or false (0). 

1193 

1194 True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values 

1195 are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if 

1196 'val' is anything else. 

1197 

1198 .. note:: copied from distutils.util 

1199 """ 

1200 val = val.lower() 

1201 if val in ("y", "yes", "t", "true", "on", "1"): 

1202 return 1 

1203 elif val in ("n", "no", "f", "false", "off", "0"): 

1204 return 0 

1205 else: 

1206 raise ValueError("invalid truth value {!r}".format(val))