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 collections.abc 

4import contextlib 

5import copy 

6import enum 

7import inspect 

8import os 

9import shlex 

10import sys 

11import types 

12import warnings 

13from functools import lru_cache 

14from types import TracebackType 

15from typing import Any 

16from typing import Callable 

17from typing import Dict 

18from typing import IO 

19from typing import Iterable 

20from typing import Iterator 

21from typing import List 

22from typing import Optional 

23from typing import Sequence 

24from typing import Set 

25from typing import TextIO 

26from typing import Tuple 

27from typing import Union 

28 

29import attr 

30import py 

31from pluggy import HookimplMarker 

32from pluggy import HookspecMarker 

33from pluggy import PluginManager 

34 

35import _pytest._code 

36import _pytest.deprecated 

37import _pytest.hookspec # the extension point definitions 

38from .exceptions import PrintHelp 

39from .exceptions import UsageError 

40from .findpaths import determine_setup 

41from _pytest._code import ExceptionInfo 

42from _pytest._code import filter_traceback 

43from _pytest._io import TerminalWriter 

44from _pytest.compat import importlib_metadata 

45from _pytest.compat import TYPE_CHECKING 

46from _pytest.outcomes import fail 

47from _pytest.outcomes import Skipped 

48from _pytest.pathlib import import_path 

49from _pytest.pathlib import ImportMode 

50from _pytest.pathlib import Path 

51from _pytest.store import Store 

52from _pytest.warning_types import PytestConfigWarning 

53 

54if TYPE_CHECKING: 

55 from typing import Type 

56 

57 from _pytest._code.code import _TracebackStyle 

58 from _pytest.terminal import TerminalReporter 

59 from .argparsing import Argument 

60 

61 

62_PluggyPlugin = object 

63"""A type to represent plugin objects. 

64Plugins can be any namespace, so we can't narrow it down much, but we use an 

65alias to make the intent clear. 

66Ideally this type would be provided by pluggy itself.""" 

67 

68 

69hookimpl = HookimplMarker("pytest") 

70hookspec = HookspecMarker("pytest") 

71 

72 

73class ExitCode(enum.IntEnum): 

74 """ 

75 .. versionadded:: 5.0 

76 

77 Encodes the valid exit codes by pytest. 

78 

79 Currently users and plugins may supply other exit codes as well. 

80 """ 

81 

82 #: tests passed 

83 OK = 0 

84 #: tests failed 

85 TESTS_FAILED = 1 

86 #: pytest was interrupted 

87 INTERRUPTED = 2 

88 #: an internal error got in the way 

89 INTERNAL_ERROR = 3 

90 #: pytest was misused 

91 USAGE_ERROR = 4 

92 #: pytest couldn't find tests 

93 NO_TESTS_COLLECTED = 5 

94 

95 

96class ConftestImportFailure(Exception): 

97 def __init__( 

98 self, 

99 path: py.path.local, 

100 excinfo: Tuple["Type[Exception]", Exception, TracebackType], 

101 ) -> None: 

102 super().__init__(path, excinfo) 

103 self.path = path 

104 self.excinfo = excinfo 

105 

106 def __str__(self) -> str: 

107 return "{}: {} (from {})".format( 

108 self.excinfo[0].__name__, self.excinfo[1], self.path 

109 ) 

110 

111 

112def filter_traceback_for_conftest_import_failure( 

113 entry: _pytest._code.TracebackEntry, 

114) -> bool: 

115 """filters tracebacks entries which point to pytest internals or importlib. 

116 

117 Make a special case for importlib because we use it to import test modules and conftest files 

118 in _pytest.pathlib.import_path. 

119 """ 

120 return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep) 

121 

122 

123def main( 

124 args: Optional[List[str]] = None, 

125 plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, 

126) -> Union[int, ExitCode]: 

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

128 

129 :arg args: list of command line arguments. 

130 

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

132 initialization. 

133 """ 

134 try: 

135 try: 

136 config = _prepareconfig(args, plugins) 

137 except ConftestImportFailure as e: 

138 exc_info = ExceptionInfo(e.excinfo) 

139 tw = TerminalWriter(sys.stderr) 

140 tw.line( 

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

142 ) 

143 exc_info.traceback = exc_info.traceback.filter( 

144 filter_traceback_for_conftest_import_failure 

145 ) 

146 exc_repr = ( 

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

148 if exc_info.traceback 

149 else exc_info.exconly() 

150 ) 

151 formatted_tb = str(exc_repr) 

152 for line in formatted_tb.splitlines(): 

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

154 return ExitCode.USAGE_ERROR 

155 else: 

156 try: 

157 ret = config.hook.pytest_cmdline_main( 

158 config=config 

159 ) # type: Union[ExitCode, int] 

160 try: 

161 return ExitCode(ret) 

162 except ValueError: 

163 return ret 

164 finally: 

165 config._ensure_unconfigure() 

166 except UsageError as e: 

167 tw = TerminalWriter(sys.stderr) 

168 for msg in e.args: 

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

170 return ExitCode.USAGE_ERROR 

171 

172 

173def console_main() -> int: 

174 """pytest's CLI entry point. 

175 

176 This function is not meant for programmable use; use `main()` instead. 

177 """ 

178 # https://docs.python.org/3/library/signal.html#note-on-sigpipe 

179 try: 

180 code = main() 

181 sys.stdout.flush() 

182 return code 

183 except BrokenPipeError: 

184 # Python flushes standard streams on exit; redirect remaining output 

185 # to devnull to avoid another BrokenPipeError at shutdown 

186 devnull = os.open(os.devnull, os.O_WRONLY) 

187 os.dup2(devnull, sys.stdout.fileno()) 

188 return 1 # Python exits with error code 1 on EPIPE 

189 

190 

191class cmdline: # compatibility namespace 

192 main = staticmethod(main) 

193 

194 

195def filename_arg(path: str, optname: str) -> str: 

196 """ Argparse type validator for filename arguments. 

197 

198 :path: path of filename 

199 :optname: name of the option 

200 """ 

201 if os.path.isdir(path): 

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

203 return path 

204 

205 

206def directory_arg(path: str, optname: str) -> str: 

207 """Argparse type validator for directory arguments. 

208 

209 :path: path of directory 

210 :optname: name of the option 

211 """ 

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

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

214 return path 

215 

216 

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

218essential_plugins = ( 

219 "mark", 

220 "main", 

221 "runner", 

222 "fixtures", 

223 "helpconfig", # Provides -p. 

224) 

225 

226default_plugins = essential_plugins + ( 

227 "python", 

228 "terminal", 

229 "debugging", 

230 "unittest", 

231 "capture", 

232 "skipping", 

233 "tmpdir", 

234 "monkeypatch", 

235 "recwarn", 

236 "pastebin", 

237 "nose", 

238 "assertion", 

239 "junitxml", 

240 "resultlog", 

241 "doctest", 

242 "cacheprovider", 

243 "freeze_support", 

244 "setuponly", 

245 "setupplan", 

246 "stepwise", 

247 "warnings", 

248 "logging", 

249 "reports", 

250 "faulthandler", 

251) 

252 

253builtin_plugins = set(default_plugins) 

254builtin_plugins.add("pytester") 

255 

256 

257def get_config( 

258 args: Optional[List[str]] = None, 

259 plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, 

260) -> "Config": 

261 # subsequent calls to main will create a fresh instance 

262 pluginmanager = PytestPluginManager() 

263 config = Config( 

264 pluginmanager, 

265 invocation_params=Config.InvocationParams( 

266 args=args or (), plugins=plugins, dir=Path.cwd(), 

267 ), 

268 ) 

269 

270 if args is not None: 

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

272 pluginmanager.consider_preparse(args, exclude_only=True) 

273 

274 for spec in default_plugins: 

275 pluginmanager.import_plugin(spec) 

276 

277 return config 

278 

279 

280def get_plugin_manager() -> "PytestPluginManager": 

281 """ 

282 Obtain a new instance of the 

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

284 already loaded. 

285 

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

287 into pytest to run tests into an IDE. 

288 """ 

289 return get_config().pluginmanager 

290 

291 

292def _prepareconfig( 

293 args: Optional[Union[py.path.local, List[str]]] = None, 

294 plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, 

295) -> "Config": 

296 if args is None: 

297 args = sys.argv[1:] 

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

299 args = [str(args)] 

300 elif not isinstance(args, list): 

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

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

303 

304 config = get_config(args, plugins) 

305 pluginmanager = config.pluginmanager 

306 try: 

307 if plugins: 

308 for plugin in plugins: 

309 if isinstance(plugin, str): 

310 pluginmanager.consider_pluginarg(plugin) 

311 else: 

312 pluginmanager.register(plugin) 

313 config = pluginmanager.hook.pytest_cmdline_parse( 

314 pluginmanager=pluginmanager, args=args 

315 ) 

316 return config 

317 except BaseException: 

318 config._ensure_unconfigure() 

319 raise 

320 

321 

322class PytestPluginManager(PluginManager): 

323 """ 

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

325 functionality: 

326 

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

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

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

330 """ 

331 

332 def __init__(self) -> None: 

333 import _pytest.assertion 

334 

335 super().__init__("pytest") 

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

337 self._conftest_plugins = set() # type: Set[types.ModuleType] 

338 

339 # State related to local conftest plugins. 

340 self._dirpath2confmods = {} # type: Dict[py.path.local, List[types.ModuleType]] 

341 self._conftestpath2mod = {} # type: Dict[Path, types.ModuleType] 

342 self._confcutdir = None # type: Optional[py.path.local] 

343 self._noconftest = False 

344 self._duplicatepaths = set() # type: Set[py.path.local] 

345 

346 self.add_hookspecs(_pytest.hookspec) 

347 self.register(self) 

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

349 err = sys.stderr # type: IO[str] 

350 encoding = getattr(err, "encoding", "utf8") # type: str 

351 try: 

352 err = open( 

353 os.dup(err.fileno()), mode=err.mode, buffering=1, encoding=encoding, 

354 ) 

355 except Exception: 

356 pass 

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

358 self.enable_tracing() 

359 

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

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

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

363 self._configured = False 

364 

365 def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str): 

366 # pytest hooks are always prefixed with pytest_ 

367 # so we avoid accessing possibly non-readable attributes 

368 # (see issue #1073) 

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

370 return 

371 # ignore names which can not be hooks 

372 if name == "pytest_plugins": 

373 return 

374 

375 method = getattr(plugin, name) 

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

377 

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

379 if not inspect.isroutine(method): 

380 return 

381 

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

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

384 opts = {} 

385 if opts is not None: 

386 # TODO: DeprecationWarning, people should use hookimpl 

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

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

389 

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

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

392 return opts 

393 

394 def parse_hookspec_opts(self, module_or_class, name: str): 

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

396 if opts is None: 

397 method = getattr(module_or_class, name) 

398 

399 if name.startswith("pytest_"): 

400 # todo: deprecate hookspec hacks 

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

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

403 opts = { 

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

405 or "firstresult" in known_marks, 

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

407 or "historic" in known_marks, 

408 } 

409 return opts 

410 

411 def register( 

412 self, plugin: _PluggyPlugin, name: Optional[str] = None 

413 ) -> Optional[str]: 

414 if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: 

415 warnings.warn( 

416 PytestConfigWarning( 

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

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

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

420 ) 

421 ) 

422 ) 

423 return None 

424 ret = super().register(plugin, name) # type: Optional[str] 

425 if ret: 

426 self.hook.pytest_plugin_registered.call_historic( 

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

428 ) 

429 

430 if isinstance(plugin, types.ModuleType): 

431 self.consider_module(plugin) 

432 return ret 

433 

434 def getplugin(self, name: str): 

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

436 plugin = self.get_plugin(name) # type: Optional[_PluggyPlugin] 

437 return plugin 

438 

439 def hasplugin(self, name: str) -> bool: 

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

441 return bool(self.get_plugin(name)) 

442 

443 def pytest_configure(self, config: "Config") -> None: 

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

445 # we should remove tryfirst/trylast as markers 

446 config.addinivalue_line( 

447 "markers", 

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

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

450 ) 

451 config.addinivalue_line( 

452 "markers", 

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

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

455 ) 

456 self._configured = True 

457 

458 # 

459 # internal API for local conftest plugin handling 

460 # 

461 def _set_initial_conftests(self, namespace: argparse.Namespace) -> None: 

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

463 As conftest files may add their own command line options 

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

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

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

467 here. 

468 """ 

469 current = py.path.local() 

470 self._confcutdir = ( 

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

472 if namespace.confcutdir 

473 else None 

474 ) 

475 self._noconftest = namespace.noconftest 

476 self._using_pyargs = namespace.pyargs 

477 testpaths = namespace.file_or_dir 

478 foundanchor = False 

479 for testpath in testpaths: 

480 path = str(testpath) 

481 # remove node-id syntax 

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

483 if i != -1: 

484 path = path[:i] 

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

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

487 self._try_load_conftest(anchor, namespace.importmode) 

488 foundanchor = True 

489 if not foundanchor: 

490 self._try_load_conftest(current, namespace.importmode) 

491 

492 def _try_load_conftest( 

493 self, anchor: py.path.local, importmode: Union[str, ImportMode] 

494 ) -> None: 

495 self._getconftestmodules(anchor, importmode) 

496 # let's also consider test* subdirs 

497 if anchor.check(dir=1): 

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

499 if x.check(dir=1): 

500 self._getconftestmodules(x, importmode) 

501 

502 @lru_cache(maxsize=128) 

503 def _getconftestmodules( 

504 self, path: py.path.local, importmode: Union[str, ImportMode], 

505 ) -> List[types.ModuleType]: 

506 if self._noconftest: 

507 return [] 

508 

509 if path.isfile(): 

510 directory = path.dirpath() 

511 else: 

512 directory = path 

513 

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

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

516 # directories instead of requiring to specify confcutdir 

517 clist = [] 

518 for parent in directory.parts(): 

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

520 continue 

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

522 if conftestpath.isfile(): 

523 mod = self._importconftest(conftestpath, importmode) 

524 clist.append(mod) 

525 self._dirpath2confmods[directory] = clist 

526 return clist 

527 

528 def _rget_with_confmod( 

529 self, name: str, path: py.path.local, importmode: Union[str, ImportMode], 

530 ) -> Tuple[types.ModuleType, Any]: 

531 modules = self._getconftestmodules(path, importmode) 

532 for mod in reversed(modules): 

533 try: 

534 return mod, getattr(mod, name) 

535 except AttributeError: 

536 continue 

537 raise KeyError(name) 

538 

539 def _importconftest( 

540 self, conftestpath: py.path.local, importmode: Union[str, ImportMode], 

541 ) -> types.ModuleType: 

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

543 # with build systems that create build directories containing 

544 # symlinks to actual files. 

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

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

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

548 

549 with contextlib.suppress(KeyError): 

550 return self._conftestpath2mod[key] 

551 

552 pkgpath = conftestpath.pypkgpath() 

553 if pkgpath is None: 

554 _ensure_removed_sysmodule(conftestpath.purebasename) 

555 

556 try: 

557 mod = import_path(conftestpath, mode=importmode) 

558 except Exception as e: 

559 assert e.__traceback__ is not None 

560 exc_info = (type(e), e, e.__traceback__) 

561 raise ConftestImportFailure(conftestpath, exc_info) from e 

562 

563 self._check_non_top_pytest_plugins(mod, conftestpath) 

564 

565 self._conftest_plugins.add(mod) 

566 self._conftestpath2mod[key] = mod 

567 dirpath = conftestpath.dirpath() 

568 if dirpath in self._dirpath2confmods: 

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

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

571 assert mod not in mods 

572 mods.append(mod) 

573 self.trace("loading conftestmodule {!r}".format(mod)) 

574 self.consider_conftest(mod) 

575 return mod 

576 

577 def _check_non_top_pytest_plugins( 

578 self, mod: types.ModuleType, conftestpath: py.path.local, 

579 ) -> None: 

580 if ( 

581 hasattr(mod, "pytest_plugins") 

582 and self._configured 

583 and not self._using_pyargs 

584 ): 

585 msg = ( 

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

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

588 " {}\n" 

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

590 " {}\n" 

591 "For more information, visit:\n" 

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

593 ) 

594 fail(msg.format(conftestpath, self._confcutdir), pytrace=False) 

595 

596 # 

597 # API for bootstrapping plugin loading 

598 # 

599 # 

600 

601 def consider_preparse( 

602 self, args: Sequence[str], *, exclude_only: bool = False 

603 ) -> None: 

604 i = 0 

605 n = len(args) 

606 while i < n: 

607 opt = args[i] 

608 i += 1 

609 if isinstance(opt, str): 

610 if opt == "-p": 

611 try: 

612 parg = args[i] 

613 except IndexError: 

614 return 

615 i += 1 

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

617 parg = opt[2:] 

618 else: 

619 continue 

620 if exclude_only and not parg.startswith("no:"): 

621 continue 

622 self.consider_pluginarg(parg) 

623 

624 def consider_pluginarg(self, arg: str) -> None: 

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

626 name = arg[3:] 

627 if name in essential_plugins: 

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

629 

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

631 if name == "cacheprovider": 

632 self.set_blocked("stepwise") 

633 self.set_blocked("pytest_stepwise") 

634 

635 self.set_blocked(name) 

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

637 self.set_blocked("pytest_" + name) 

638 else: 

639 name = arg 

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

641 # There is no interface with pluggy for this. 

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

643 del self._name2plugin[name] 

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

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

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

647 self.import_plugin(arg, consider_entry_points=True) 

648 

649 def consider_conftest(self, conftestmodule: types.ModuleType) -> None: 

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

651 

652 def consider_env(self) -> None: 

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

654 

655 def consider_module(self, mod: types.ModuleType) -> None: 

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

657 

658 def _import_plugin_specs( 

659 self, spec: Union[None, types.ModuleType, str, Sequence[str]] 

660 ) -> None: 

661 plugins = _get_plugin_specs_as_list(spec) 

662 for import_spec in plugins: 

663 self.import_plugin(import_spec) 

664 

665 def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None: 

666 """ 

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

668 names are also considered to find a plugin. 

669 """ 

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

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

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

673 # _pytest prefix. 

674 assert isinstance(modname, str), ( 

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

676 ) 

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

678 return 

679 

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

681 self.rewrite_hook.mark_rewrite(importspec) 

682 

683 if consider_entry_points: 

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

685 if loaded: 

686 return 

687 

688 try: 

689 __import__(importspec) 

690 except ImportError as e: 

691 raise ImportError( 

692 'Error importing plugin "{}": {}'.format(modname, str(e.args[0])) 

693 ).with_traceback(e.__traceback__) from e 

694 

695 except Skipped as e: 

696 from _pytest.warnings import _issue_warning_captured 

697 

698 _issue_warning_captured( 

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

700 self.hook, 

701 stacklevel=2, 

702 ) 

703 else: 

704 mod = sys.modules[importspec] 

705 self.register(mod, modname) 

706 

707 

708def _get_plugin_specs_as_list( 

709 specs: Union[None, types.ModuleType, str, Sequence[str]] 

710) -> List[str]: 

711 """Parse a plugins specification into a list of plugin names.""" 

712 # None means empty. 

713 if specs is None: 

714 return [] 

715 # Workaround for #3899 - a submodule which happens to be called "pytest_plugins". 

716 if isinstance(specs, types.ModuleType): 

717 return [] 

718 # Comma-separated list. 

719 if isinstance(specs, str): 

720 return specs.split(",") if specs else [] 

721 # Direct specification. 

722 if isinstance(specs, collections.abc.Sequence): 

723 return list(specs) 

724 raise UsageError( 

725 "Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: %r" 

726 % specs 

727 ) 

728 

729 

730def _ensure_removed_sysmodule(modname: str) -> None: 

731 try: 

732 del sys.modules[modname] 

733 except KeyError: 

734 pass 

735 

736 

737class Notset: 

738 def __repr__(self): 

739 return "<NOTSET>" 

740 

741 

742notset = Notset() 

743 

744 

745def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: 

746 """ 

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

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

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

750 

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

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

753 

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

755 

756 pytest_mock/__init__.py 

757 pytest_mock/_version.py 

758 pytest_mock/plugin.py 

759 pytest_mock.egg-info/PKG-INFO 

760 

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

762 

763 src/pytest_mock/__init__.py 

764 src/pytest_mock/_version.py 

765 src/pytest_mock/plugin.py 

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

767 LICENSE 

768 setup.py 

769 

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

771 names should be considered for assertion rewriting. 

772 

773 More information: 

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

775 """ 

776 package_files = list(package_files) 

777 seen_some = False 

778 for fn in package_files: 

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

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

781 if is_simple_module: 

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

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

784 if module_name != "setup": 

785 seen_some = True 

786 yield module_name 

787 elif is_package: 

788 package_name = os.path.dirname(fn) 

789 seen_some = True 

790 yield package_name 

791 

792 if not seen_some: 

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

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

795 # "src" based source trees for example) 

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

797 # are rarer 

798 new_package_files = [] 

799 for fn in package_files: 

800 parts = fn.split("/") 

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

802 if new_fn: 

803 new_package_files.append(new_fn) 

804 if new_package_files: 

805 yield from _iter_rewritable_modules(new_package_files) 

806 

807 

808def _args_converter(args: Iterable[str]) -> Tuple[str, ...]: 

809 return tuple(args) 

810 

811 

812class Config: 

813 """ 

814 Access to configuration values, pluginmanager and plugin hooks. 

815 

816 :param PytestPluginManager pluginmanager: 

817 

818 :param InvocationParams invocation_params: 

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

820 invocation. 

821 """ 

822 

823 @attr.s(frozen=True) 

824 class InvocationParams: 

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

826 

827 The object attributes are read-only. 

828 

829 .. versionadded:: 5.1 

830 

831 .. note:: 

832 

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

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

835 

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

837 """ 

838 

839 args = attr.ib(type=Tuple[str, ...], converter=_args_converter) 

840 """tuple of command-line arguments as passed to ``pytest.main()``.""" 

841 plugins = attr.ib(type=Optional[Sequence[Union[str, _PluggyPlugin]]]) 

842 """list of extra plugins, might be `None`.""" 

843 dir = attr.ib(type=Path) 

844 """directory where ``pytest.main()`` was invoked from.""" 

845 

846 def __init__( 

847 self, 

848 pluginmanager: PytestPluginManager, 

849 *, 

850 invocation_params: Optional[InvocationParams] = None 

851 ) -> None: 

852 from .argparsing import Parser, FILE_OR_DIR 

853 

854 if invocation_params is None: 

855 invocation_params = self.InvocationParams( 

856 args=(), plugins=None, dir=Path.cwd() 

857 ) 

858 

859 self.option = argparse.Namespace() 

860 """access to command line option as attributes. 

861 

862 :type: argparse.Namespace""" 

863 

864 self.invocation_params = invocation_params 

865 

866 _a = FILE_OR_DIR 

867 self._parser = Parser( 

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

869 processopt=self._processopt, 

870 ) 

871 self.pluginmanager = pluginmanager 

872 """the plugin manager handles plugin registration and hook invocation. 

873 

874 :type: PytestPluginManager""" 

875 

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

877 self.hook = self.pluginmanager.hook 

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

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

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

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

882 # A place where plugins can store information on the config for their 

883 # own use. Currently only intended for internal plugins. 

884 self._store = Store() 

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

886 self._configured = False 

887 self.hook.pytest_addoption.call_historic( 

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

889 ) 

890 

891 if TYPE_CHECKING: 

892 from _pytest.cacheprovider import Cache 

893 

894 self.cache = None # type: Optional[Cache] 

895 

896 @property 

897 def invocation_dir(self) -> py.path.local: 

898 """Backward compatibility""" 

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

900 

901 def add_cleanup(self, func: Callable[[], None]) -> None: 

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

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

904 self._cleanup.append(func) 

905 

906 def _do_configure(self) -> None: 

907 assert not self._configured 

908 self._configured = True 

909 with warnings.catch_warnings(): 

910 warnings.simplefilter("default") 

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

912 

913 def _ensure_unconfigure(self) -> None: 

914 if self._configured: 

915 self._configured = False 

916 self.hook.pytest_unconfigure(config=self) 

917 self.hook.pytest_configure._call_history = [] 

918 while self._cleanup: 

919 fin = self._cleanup.pop() 

920 fin() 

921 

922 def get_terminal_writer(self) -> TerminalWriter: 

923 terminalreporter = self.pluginmanager.get_plugin( 

924 "terminalreporter" 

925 ) # type: TerminalReporter 

926 return terminalreporter._tw 

927 

928 def pytest_cmdline_parse( 

929 self, pluginmanager: PytestPluginManager, args: List[str] 

930 ) -> "Config": 

931 try: 

932 self.parse(args) 

933 except UsageError: 

934 

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

936 # This gets done via helpconfig normally, but its 

937 # pytest_cmdline_main is not called in case of errors. 

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

939 from _pytest.helpconfig import showversion 

940 

941 showversion(self) 

942 elif ( 

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

944 ): 

945 self._parser._getparser().print_help() 

946 sys.stdout.write( 

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

948 ) 

949 

950 raise 

951 

952 return self 

953 

954 def notify_exception( 

955 self, 

956 excinfo: ExceptionInfo[BaseException], 

957 option: Optional[argparse.Namespace] = None, 

958 ) -> None: 

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

960 style = "long" # type: _TracebackStyle 

961 else: 

962 style = "native" 

963 excrepr = excinfo.getrepr( 

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

965 ) 

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

967 if not any(res): 

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

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

970 sys.stderr.flush() 

971 

972 def cwd_relative_nodeid(self, nodeid: str) -> str: 

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

974 if self.invocation_dir != self.rootdir: 

975 fullpath = self.rootdir.join(nodeid) 

976 nodeid = self.invocation_dir.bestrelpath(fullpath) 

977 return nodeid 

978 

979 @classmethod 

980 def fromdictargs(cls, option_dict, args) -> "Config": 

981 """ constructor usable for subprocesses. """ 

982 config = get_config(args) 

983 config.option.__dict__.update(option_dict) 

984 config.parse(args, addopts=False) 

985 for x in config.option.plugins: 

986 config.pluginmanager.consider_pluginarg(x) 

987 return config 

988 

989 def _processopt(self, opt: "Argument") -> None: 

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

991 self._opt2dest[name] = opt.dest 

992 

993 if hasattr(opt, "default"): 

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

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

996 

997 @hookimpl(trylast=True) 

998 def pytest_load_initial_conftests(self, early_config: "Config") -> None: 

999 self.pluginmanager._set_initial_conftests(early_config.known_args_namespace) 

1000 

1001 def _initini(self, args: Sequence[str]) -> None: 

1002 ns, unknown_args = self._parser.parse_known_and_unknown_args( 

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

1004 ) 

1005 self.rootdir, self.inifile, self.inicfg = determine_setup( 

1006 ns.inifilename, 

1007 ns.file_or_dir + unknown_args, 

1008 rootdir_cmd_arg=ns.rootdir or None, 

1009 config=self, 

1010 ) 

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

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

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

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

1015 self._parser.addini( 

1016 "required_plugins", 

1017 "plugins that must be present for pytest to run", 

1018 type="args", 

1019 default=[], 

1020 ) 

1021 self._override_ini = ns.override_ini or () 

1022 

1023 def _consider_importhook(self, args: Sequence[str]) -> None: 

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

1025 

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

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

1028 by the importhook. 

1029 """ 

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

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

1032 if mode == "rewrite": 

1033 import _pytest.assertion 

1034 

1035 try: 

1036 hook = _pytest.assertion.install_importhook(self) 

1037 except SystemError: 

1038 mode = "plain" 

1039 else: 

1040 self._mark_plugins_for_rewrite(hook) 

1041 self._warn_about_missing_assertion(mode) 

1042 

1043 def _mark_plugins_for_rewrite(self, hook) -> None: 

1044 """ 

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

1046 modules or packages in the distribution package for 

1047 all pytest plugins. 

1048 """ 

1049 self.pluginmanager.rewrite_hook = hook 

1050 

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

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

1053 return 

1054 

1055 package_files = ( 

1056 str(file) 

1057 for dist in importlib_metadata.distributions() 

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

1059 for file in dist.files or [] 

1060 ) 

1061 

1062 for name in _iter_rewritable_modules(package_files): 

1063 hook.mark_rewrite(name) 

1064 

1065 def _validate_args(self, args: List[str], via: str) -> List[str]: 

1066 """Validate known args.""" 

1067 self._parser._config_source_hint = via # type: ignore 

1068 try: 

1069 self._parser.parse_known_and_unknown_args( 

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

1071 ) 

1072 finally: 

1073 del self._parser._config_source_hint # type: ignore 

1074 

1075 return args 

1076 

1077 def _preparse(self, args: List[str], addopts: bool = True) -> None: 

1078 if addopts: 

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

1080 if len(env_addopts): 

1081 args[:] = ( 

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

1083 + args 

1084 ) 

1085 self._initini(args) 

1086 if addopts: 

1087 args[:] = ( 

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

1089 ) 

1090 

1091 self._checkversion() 

1092 self._consider_importhook(args) 

1093 self.pluginmanager.consider_preparse(args, exclude_only=False) 

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

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

1096 # plugins are going to be loaded. 

1097 self.pluginmanager.load_setuptools_entrypoints("pytest11") 

1098 self.pluginmanager.consider_env() 

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

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

1101 ) 

1102 self._validate_plugins() 

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

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

1105 self.known_args_namespace.confcutdir = confcutdir 

1106 try: 

1107 self.hook.pytest_load_initial_conftests( 

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

1109 ) 

1110 except ConftestImportFailure as e: 

1111 if ns.help or ns.version: 

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

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

1114 from _pytest.warnings import _issue_warning_captured 

1115 

1116 _issue_warning_captured( 

1117 PytestConfigWarning( 

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

1119 ), 

1120 self.hook, 

1121 stacklevel=2, 

1122 ) 

1123 else: 

1124 raise 

1125 self._validate_keys() 

1126 

1127 def _checkversion(self) -> None: 

1128 import pytest 

1129 

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

1131 if minver: 

1132 # Imported lazily to improve start-up time. 

1133 from packaging.version import Version 

1134 

1135 if not isinstance(minver, str): 

1136 raise pytest.UsageError( 

1137 "%s: 'minversion' must be a single value" % self.inifile 

1138 ) 

1139 

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

1141 raise pytest.UsageError( 

1142 "%s: 'minversion' requires pytest-%s, actual pytest-%s'" 

1143 % (self.inifile, minver, pytest.__version__,) 

1144 ) 

1145 

1146 def _validate_keys(self) -> None: 

1147 for key in sorted(self._get_unknown_ini_keys()): 

1148 self._warn_or_fail_if_strict("Unknown config ini key: {}\n".format(key)) 

1149 

1150 def _validate_plugins(self) -> None: 

1151 required_plugins = sorted(self.getini("required_plugins")) 

1152 if not required_plugins: 

1153 return 

1154 

1155 # Imported lazily to improve start-up time. 

1156 from packaging.version import Version 

1157 from packaging.requirements import InvalidRequirement, Requirement 

1158 

1159 plugin_info = self.pluginmanager.list_plugin_distinfo() 

1160 plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info} 

1161 

1162 missing_plugins = [] 

1163 for required_plugin in required_plugins: 

1164 spec = None 

1165 try: 

1166 spec = Requirement(required_plugin) 

1167 except InvalidRequirement: 

1168 missing_plugins.append(required_plugin) 

1169 continue 

1170 

1171 if spec.name not in plugin_dist_info: 

1172 missing_plugins.append(required_plugin) 

1173 elif Version(plugin_dist_info[spec.name]) not in spec.specifier: 

1174 missing_plugins.append(required_plugin) 

1175 

1176 if missing_plugins: 

1177 fail( 

1178 "Missing required plugins: {}".format(", ".join(missing_plugins)), 

1179 pytrace=False, 

1180 ) 

1181 

1182 def _warn_or_fail_if_strict(self, message: str) -> None: 

1183 if self.known_args_namespace.strict_config: 

1184 fail(message, pytrace=False) 

1185 

1186 from _pytest.warnings import _issue_warning_captured 

1187 

1188 _issue_warning_captured( 

1189 PytestConfigWarning(message), self.hook, stacklevel=3, 

1190 ) 

1191 

1192 def _get_unknown_ini_keys(self) -> List[str]: 

1193 parser_inicfg = self._parser._inidict 

1194 return [name for name in self.inicfg if name not in parser_inicfg] 

1195 

1196 def parse(self, args: List[str], addopts: bool = True) -> None: 

1197 # parse given cmdline arguments into this config object. 

1198 assert not hasattr( 

1199 self, "args" 

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

1201 self.hook.pytest_addhooks.call_historic( 

1202 kwargs=dict(pluginmanager=self.pluginmanager) 

1203 ) 

1204 self._preparse(args, addopts=addopts) 

1205 # XXX deprecated hook: 

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

1207 self._parser.after_preparse = True # type: ignore 

1208 try: 

1209 args = self._parser.parse_setoption( 

1210 args, self.option, namespace=self.option 

1211 ) 

1212 if not args: 

1213 if self.invocation_dir == self.rootdir: 

1214 args = self.getini("testpaths") 

1215 if not args: 

1216 args = [str(self.invocation_dir)] 

1217 self.args = args 

1218 except PrintHelp: 

1219 pass 

1220 

1221 def addinivalue_line(self, name: str, line: str) -> None: 

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

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

1224 the first line in its value. """ 

1225 x = self.getini(name) 

1226 assert isinstance(x, list) 

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

1228 

1229 def getini(self, name: str): 

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

1231 specified name hasn't been registered through a prior 

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

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

1234 try: 

1235 return self._inicache[name] 

1236 except KeyError: 

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

1238 return val 

1239 

1240 def _getini(self, name: str): 

1241 try: 

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

1243 except KeyError as e: 

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

1245 override_value = self._get_override_ini_value(name) 

1246 if override_value is None: 

1247 try: 

1248 value = self.inicfg[name] 

1249 except KeyError: 

1250 if default is not None: 

1251 return default 

1252 if type is None: 

1253 return "" 

1254 return [] 

1255 else: 

1256 value = override_value 

1257 # coerce the values based on types 

1258 # note: some coercions are only required if we are reading from .ini files, because 

1259 # the file format doesn't contain type information, but when reading from toml we will 

1260 # get either str or list of str values (see _parse_ini_config_from_pyproject_toml). 

1261 # for example: 

1262 # 

1263 # ini: 

1264 # a_line_list = "tests acceptance" 

1265 # in this case, we need to split the string to obtain a list of strings 

1266 # 

1267 # toml: 

1268 # a_line_list = ["tests", "acceptance"] 

1269 # in this case, we already have a list ready to use 

1270 # 

1271 if type == "pathlist": 

1272 # TODO: This assert is probably not valid in all cases. 

1273 assert self.inifile is not None 

1274 dp = py.path.local(self.inifile).dirpath() 

1275 input_values = shlex.split(value) if isinstance(value, str) else value 

1276 return [dp.join(x, abs=True) for x in input_values] 

1277 elif type == "args": 

1278 return shlex.split(value) if isinstance(value, str) else value 

1279 elif type == "linelist": 

1280 if isinstance(value, str): 

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

1282 else: 

1283 return value 

1284 elif type == "bool": 

1285 return _strtobool(str(value).strip()) 

1286 else: 

1287 assert type is None 

1288 return value 

1289 

1290 def _getconftest_pathlist( 

1291 self, name: str, path: py.path.local 

1292 ) -> Optional[List[py.path.local]]: 

1293 try: 

1294 mod, relroots = self.pluginmanager._rget_with_confmod( 

1295 name, path, self.getoption("importmode") 

1296 ) 

1297 except KeyError: 

1298 return None 

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

1300 values = [] # type: List[py.path.local] 

1301 for relroot in relroots: 

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

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

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

1305 values.append(relroot) 

1306 return values 

1307 

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

1309 value = None 

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

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

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

1313 for ini_config in self._override_ini: 

1314 try: 

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

1316 except ValueError as e: 

1317 raise UsageError( 

1318 "-o/--override-ini expects option=value style (got: {!r}).".format( 

1319 ini_config 

1320 ) 

1321 ) from e 

1322 else: 

1323 if key == name: 

1324 value = user_ini_value 

1325 return value 

1326 

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

1328 """ return command line option value. 

1329 

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

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

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

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

1334 or has a None value. 

1335 """ 

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

1337 try: 

1338 val = getattr(self.option, name) 

1339 if val is None and skip: 

1340 raise AttributeError(name) 

1341 return val 

1342 except AttributeError as e: 

1343 if default is not notset: 

1344 return default 

1345 if skip: 

1346 import pytest 

1347 

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

1349 raise ValueError("no option named {!r}".format(name)) from e 

1350 

1351 def getvalue(self, name: str, path=None): 

1352 """ (deprecated, use getoption()) """ 

1353 return self.getoption(name) 

1354 

1355 def getvalueorskip(self, name: str, path=None): 

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

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

1358 

1359 def _warn_about_missing_assertion(self, mode: str) -> None: 

1360 if not _assertion_supported(): 

1361 from _pytest.warnings import _issue_warning_captured 

1362 

1363 if mode == "plain": 

1364 warning_text = ( 

1365 "ASSERTIONS ARE NOT EXECUTED" 

1366 " and FAILING TESTS WILL PASS. Are you" 

1367 " using python -O?" 

1368 ) 

1369 else: 

1370 warning_text = ( 

1371 "assertions not in test modules or" 

1372 " plugins will be ignored" 

1373 " because assert statements are not executed " 

1374 "by the underlying Python interpreter " 

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

1376 ) 

1377 _issue_warning_captured( 

1378 PytestConfigWarning(warning_text), self.hook, stacklevel=3, 

1379 ) 

1380 

1381 

1382def _assertion_supported() -> bool: 

1383 try: 

1384 assert False 

1385 except AssertionError: 

1386 return True 

1387 else: 

1388 return False 

1389 

1390 

1391def create_terminal_writer( 

1392 config: Config, file: Optional[TextIO] = None 

1393) -> TerminalWriter: 

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

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

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

1397 """ 

1398 tw = TerminalWriter(file=file) 

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

1400 tw.hasmarkup = True 

1401 elif config.option.color == "no": 

1402 tw.hasmarkup = False 

1403 

1404 if config.option.code_highlight == "yes": 

1405 tw.code_highlight = True 

1406 elif config.option.code_highlight == "no": 

1407 tw.code_highlight = False 

1408 return tw 

1409 

1410 

1411def _strtobool(val: str) -> bool: 

1412 """Convert a string representation of truth to True or False. 

1413 

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

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

1416 'val' is anything else. 

1417 

1418 .. note:: copied from distutils.util 

1419 """ 

1420 val = val.lower() 

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

1422 return True 

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

1424 return False 

1425 else: 

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