Coverage for /opt/homebrew/lib/python3.11/site-packages/_pytest/config/__init__.py: 58%

877 statements  

« prev     ^ index     » next       coverage.py v7.2.3, created at 2023-05-04 13:14 +0700

1"""Command line options, ini-file and conftest.py processing.""" 

2import argparse 

3import collections.abc 

4import copy 

5import enum 

6import glob 

7import inspect 

8import os 

9import re 

10import shlex 

11import sys 

12import types 

13import warnings 

14from functools import lru_cache 

15from pathlib import Path 

16from textwrap import dedent 

17from types import FunctionType 

18from types import TracebackType 

19from typing import Any 

20from typing import Callable 

21from typing import cast 

22from typing import Dict 

23from typing import Generator 

24from typing import IO 

25from typing import Iterable 

26from typing import Iterator 

27from typing import List 

28from typing import Optional 

29from typing import Sequence 

30from typing import Set 

31from typing import TextIO 

32from typing import Tuple 

33from typing import Type 

34from typing import TYPE_CHECKING 

35from typing import Union 

36 

37import attr 

38from pluggy import HookimplMarker 

39from pluggy import HookspecMarker 

40from pluggy import PluginManager 

41 

42import _pytest._code 

43import _pytest.deprecated 

44import _pytest.hookspec 

45from .exceptions import PrintHelp as PrintHelp 

46from .exceptions import UsageError as UsageError 

47from .findpaths import determine_setup 

48from _pytest._code import ExceptionInfo 

49from _pytest._code import filter_traceback 

50from _pytest._io import TerminalWriter 

51from _pytest.compat import final 

52from _pytest.compat import importlib_metadata 

53from _pytest.outcomes import fail 

54from _pytest.outcomes import Skipped 

55from _pytest.pathlib import absolutepath 

56from _pytest.pathlib import bestrelpath 

57from _pytest.pathlib import import_path 

58from _pytest.pathlib import ImportMode 

59from _pytest.pathlib import resolve_package_path 

60from _pytest.stash import Stash 

61from _pytest.warning_types import PytestConfigWarning 

62from _pytest.warning_types import warn_explicit_for 

63 

64if TYPE_CHECKING: 

65 

66 from _pytest._code.code import _TracebackStyle 

67 from _pytest.terminal import TerminalReporter 

68 from .argparsing import Argument 

69 

70 

71_PluggyPlugin = object 

72"""A type to represent plugin objects. 

73 

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

75alias to make the intent clear. 

76 

77Ideally this type would be provided by pluggy itself. 

78""" 

79 

80 

81hookimpl = HookimplMarker("pytest") 

82hookspec = HookspecMarker("pytest") 

83 

84 

85@final 

86class ExitCode(enum.IntEnum): 

87 """Encodes the valid exit codes by pytest. 

88 

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

90 

91 .. versionadded:: 5.0 

92 """ 

93 

94 #: Tests passed. 

95 OK = 0 

96 #: Tests failed. 

97 TESTS_FAILED = 1 

98 #: pytest was interrupted. 

99 INTERRUPTED = 2 

100 #: An internal error got in the way. 

101 INTERNAL_ERROR = 3 

102 #: pytest was misused. 

103 USAGE_ERROR = 4 

104 #: pytest couldn't find tests. 

105 NO_TESTS_COLLECTED = 5 

106 

107 

108class ConftestImportFailure(Exception): 

109 def __init__( 

110 self, 

111 path: Path, 

112 excinfo: Tuple[Type[Exception], Exception, TracebackType], 

113 ) -> None: 

114 super().__init__(path, excinfo) 

115 self.path = path 

116 self.excinfo = excinfo 

117 

118 def __str__(self) -> str: 

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

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

121 ) 

122 

123 

124def filter_traceback_for_conftest_import_failure( 

125 entry: _pytest._code.TracebackEntry, 

126) -> bool: 

127 """Filter tracebacks entries which point to pytest internals or importlib. 

128 

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

130 in _pytest.pathlib.import_path. 

131 """ 

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

133 

134 

135def main( 

136 args: Optional[Union[List[str], "os.PathLike[str]"]] = None, 

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

138) -> Union[int, ExitCode]: 

139 """Perform an in-process test run. 

140 

141 :param args: List of command line arguments. 

142 :param plugins: List of plugin objects to be auto-registered during initialization. 

143 

144 :returns: An exit code. 

145 """ 

146 try: 

147 try: 

148 config = _prepareconfig(args, plugins) 

149 except ConftestImportFailure as e: 

150 exc_info = ExceptionInfo.from_exc_info(e.excinfo) 

151 tw = TerminalWriter(sys.stderr) 

152 tw.line(f"ImportError while loading conftest '{e.path}'.", red=True) 

153 exc_info.traceback = exc_info.traceback.filter( 

154 filter_traceback_for_conftest_import_failure 

155 ) 

156 exc_repr = ( 

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

158 if exc_info.traceback 

159 else exc_info.exconly() 

160 ) 

161 formatted_tb = str(exc_repr) 

162 for line in formatted_tb.splitlines(): 

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

164 return ExitCode.USAGE_ERROR 

165 else: 

166 try: 

167 ret: Union[ExitCode, int] = config.hook.pytest_cmdline_main( 

168 config=config 

169 ) 

170 try: 

171 return ExitCode(ret) 

172 except ValueError: 

173 return ret 

174 finally: 

175 config._ensure_unconfigure() 

176 except UsageError as e: 

177 tw = TerminalWriter(sys.stderr) 

178 for msg in e.args: 

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

180 return ExitCode.USAGE_ERROR 

181 

182 

183def console_main() -> int: 

184 """The CLI entry point of pytest. 

185 

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

187 """ 

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

189 try: 

190 code = main() 

191 sys.stdout.flush() 

192 return code 

193 except BrokenPipeError: 

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

195 # to devnull to avoid another BrokenPipeError at shutdown 

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

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

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

199 

200 

201class cmdline: # compatibility namespace 

202 main = staticmethod(main) 

203 

204 

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

206 """Argparse type validator for filename arguments. 

207 

208 :path: Path of filename. 

209 :optname: Name of the option. 

210 """ 

211 if os.path.isdir(path): 

212 raise UsageError(f"{optname} must be a filename, given: {path}") 

213 return path 

214 

215 

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

217 """Argparse type validator for directory arguments. 

218 

219 :path: Path of directory. 

220 :optname: Name of the option. 

221 """ 

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

223 raise UsageError(f"{optname} must be a directory, given: {path}") 

224 return path 

225 

226 

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

228essential_plugins = ( 

229 "mark", 

230 "main", 

231 "runner", 

232 "fixtures", 

233 "helpconfig", # Provides -p. 

234) 

235 

236default_plugins = essential_plugins + ( 

237 "python", 

238 "terminal", 

239 "debugging", 

240 "unittest", 

241 "capture", 

242 "skipping", 

243 "legacypath", 

244 "tmpdir", 

245 "monkeypatch", 

246 "recwarn", 

247 "pastebin", 

248 "nose", 

249 "assertion", 

250 "junitxml", 

251 "doctest", 

252 "cacheprovider", 

253 "freeze_support", 

254 "setuponly", 

255 "setupplan", 

256 "stepwise", 

257 "warnings", 

258 "logging", 

259 "reports", 

260 "python_path", 

261 *(["unraisableexception", "threadexception"] if sys.version_info >= (3, 8) else []), 

262 "faulthandler", 

263) 

264 

265builtin_plugins = set(default_plugins) 

266builtin_plugins.add("pytester") 

267builtin_plugins.add("pytester_assertions") 

268 

269 

270def get_config( 

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

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

273) -> "Config": 

274 # subsequent calls to main will create a fresh instance 

275 pluginmanager = PytestPluginManager() 

276 config = Config( 

277 pluginmanager, 

278 invocation_params=Config.InvocationParams( 

279 args=args or (), 

280 plugins=plugins, 

281 dir=Path.cwd(), 

282 ), 

283 ) 

284 

285 if args is not None: 

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

287 pluginmanager.consider_preparse(args, exclude_only=True) 

288 

289 for spec in default_plugins: 

290 pluginmanager.import_plugin(spec) 

291 

292 return config 

293 

294 

295def get_plugin_manager() -> "PytestPluginManager": 

296 """Obtain a new instance of the 

297 :py:class:`pytest.PytestPluginManager`, with default plugins 

298 already loaded. 

299 

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

301 into pytest to run tests into an IDE. 

302 """ 

303 return get_config().pluginmanager 

304 

305 

306def _prepareconfig( 

307 args: Optional[Union[List[str], "os.PathLike[str]"]] = None, 

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

309) -> "Config": 

310 if args is None: 

311 args = sys.argv[1:] 

312 elif isinstance(args, os.PathLike): 

313 args = [os.fspath(args)] 

314 elif not isinstance(args, list): 

315 msg = ( # type:ignore[unreachable] 

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

317 ) 

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

319 

320 config = get_config(args, plugins) 

321 pluginmanager = config.pluginmanager 

322 try: 

323 if plugins: 

324 for plugin in plugins: 

325 if isinstance(plugin, str): 

326 pluginmanager.consider_pluginarg(plugin) 

327 else: 

328 pluginmanager.register(plugin) 

329 config = pluginmanager.hook.pytest_cmdline_parse( 

330 pluginmanager=pluginmanager, args=args 

331 ) 

332 return config 

333 except BaseException: 

334 config._ensure_unconfigure() 

335 raise 

336 

337 

338def _get_directory(path: Path) -> Path: 

339 """Get the directory of a path - itself if already a directory.""" 

340 if path.is_file(): 

341 return path.parent 

342 else: 

343 return path 

344 

345 

346def _get_legacy_hook_marks( 

347 method: Any, 

348 hook_type: str, 

349 opt_names: Tuple[str, ...], 

350) -> Dict[str, bool]: 

351 if TYPE_CHECKING: 

352 # abuse typeguard from importlib to avoid massive method type union thats lacking a alias 

353 assert inspect.isroutine(method) 

354 known_marks: set[str] = {m.name for m in getattr(method, "pytestmark", [])} 

355 must_warn: list[str] = [] 

356 opts: dict[str, bool] = {} 

357 for opt_name in opt_names: 

358 opt_attr = getattr(method, opt_name, AttributeError) 

359 if opt_attr is not AttributeError: 

360 must_warn.append(f"{opt_name}={opt_attr}") 

361 opts[opt_name] = True 

362 elif opt_name in known_marks: 

363 must_warn.append(f"{opt_name}=True") 

364 opts[opt_name] = True 

365 else: 

366 opts[opt_name] = False 

367 if must_warn: 

368 hook_opts = ", ".join(must_warn) 

369 message = _pytest.deprecated.HOOK_LEGACY_MARKING.format( 

370 type=hook_type, 

371 fullname=method.__qualname__, 

372 hook_opts=hook_opts, 

373 ) 

374 warn_explicit_for(cast(FunctionType, method), message) 

375 return opts 

376 

377 

378@final 

379class PytestPluginManager(PluginManager): 

380 """A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with 

381 additional pytest-specific functionality: 

382 

383 * Loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and 

384 ``pytest_plugins`` global variables found in plugins being loaded. 

385 * ``conftest.py`` loading during start-up. 

386 """ 

387 

388 def __init__(self) -> None: 

389 import _pytest.assertion 

390 

391 super().__init__("pytest") 

392 

393 # -- State related to local conftest plugins. 

394 # All loaded conftest modules. 

395 self._conftest_plugins: Set[types.ModuleType] = set() 

396 # All conftest modules applicable for a directory. 

397 # This includes the directory's own conftest modules as well 

398 # as those of its parent directories. 

399 self._dirpath2confmods: Dict[Path, List[types.ModuleType]] = {} 

400 # Cutoff directory above which conftests are no longer discovered. 

401 self._confcutdir: Optional[Path] = None 

402 # If set, conftest loading is skipped. 

403 self._noconftest = False 

404 

405 # _getconftestmodules()'s call to _get_directory() causes a stat 

406 # storm when it's called potentially thousands of times in a test 

407 # session (#9478), often with the same path, so cache it. 

408 self._get_directory = lru_cache(256)(_get_directory) 

409 

410 self._duplicatepaths: Set[Path] = set() 

411 

412 # plugins that were explicitly skipped with pytest.skip 

413 # list of (module name, skip reason) 

414 # previously we would issue a warning when a plugin was skipped, but 

415 # since we refactored warnings as first citizens of Config, they are 

416 # just stored here to be used later. 

417 self.skipped_plugins: List[Tuple[str, str]] = [] 

418 

419 self.add_hookspecs(_pytest.hookspec) 

420 self.register(self) 

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

422 err: IO[str] = sys.stderr 

423 encoding: str = getattr(err, "encoding", "utf8") 

424 try: 

425 err = open( 

426 os.dup(err.fileno()), 

427 mode=err.mode, 

428 buffering=1, 

429 encoding=encoding, 

430 ) 

431 except Exception: 

432 pass 

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

434 self.enable_tracing() 

435 

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

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

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

439 self._configured = False 

440 

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

442 # pytest hooks are always prefixed with "pytest_", 

443 # so we avoid accessing possibly non-readable attributes 

444 # (see issue #1073). 

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

446 return 

447 # Ignore names which can not be hooks. 

448 if name == "pytest_plugins": 

449 return 

450 

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

452 if opts is not None: 

453 return opts 

454 

455 method = getattr(plugin, name) 

456 # Consider only actual functions for hooks (#3775). 

457 if not inspect.isroutine(method): 

458 return 

459 # Collect unmarked hooks as long as they have the `pytest_' prefix. 

460 return _get_legacy_hook_marks( 

461 method, "impl", ("tryfirst", "trylast", "optionalhook", "hookwrapper") 

462 ) 

463 

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

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

466 if opts is None: 

467 method = getattr(module_or_class, name) 

468 if name.startswith("pytest_"): 

469 opts = _get_legacy_hook_marks( 

470 method, 

471 "spec", 

472 ("firstresult", "historic"), 

473 ) 

474 return opts 

475 

476 def register( 

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

478 ) -> Optional[str]: 

479 if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: 

480 warnings.warn( 

481 PytestConfigWarning( 

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

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

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

485 ) 

486 ) 

487 ) 

488 return None 

489 ret: Optional[str] = super().register(plugin, name) 

490 if ret: 

491 self.hook.pytest_plugin_registered.call_historic( 

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

493 ) 

494 

495 if isinstance(plugin, types.ModuleType): 

496 self.consider_module(plugin) 

497 return ret 

498 

499 def getplugin(self, name: str): 

500 # Support deprecated naming because plugins (xdist e.g.) use it. 

501 plugin: Optional[_PluggyPlugin] = self.get_plugin(name) 

502 return plugin 

503 

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

505 """Return whether a plugin with the given name is registered.""" 

506 return bool(self.get_plugin(name)) 

507 

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

509 """:meta private:""" 

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

511 # we should remove tryfirst/trylast as markers. 

512 config.addinivalue_line( 

513 "markers", 

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

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

516 "DEPRECATED, use @pytest.hookimpl(tryfirst=True) instead.", 

517 ) 

518 config.addinivalue_line( 

519 "markers", 

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

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

522 "DEPRECATED, use @pytest.hookimpl(trylast=True) instead.", 

523 ) 

524 self._configured = True 

525 

526 # 

527 # Internal API for local conftest plugin handling. 

528 # 

529 def _set_initial_conftests( 

530 self, namespace: argparse.Namespace, rootpath: Path 

531 ) -> None: 

532 """Load initial conftest files given a preparsed "namespace". 

533 

534 As conftest files may add their own command line options which have 

535 arguments ('--my-opt somepath') we might get some false positives. 

536 All builtin and 3rd party plugins will have been loaded, however, so 

537 common options will not confuse our logic here. 

538 """ 

539 current = Path.cwd() 

540 self._confcutdir = ( 

541 absolutepath(current / namespace.confcutdir) 

542 if namespace.confcutdir 

543 else None 

544 ) 

545 self._noconftest = namespace.noconftest 

546 self._using_pyargs = namespace.pyargs 

547 testpaths = namespace.file_or_dir 

548 foundanchor = False 

549 for testpath in testpaths: 

550 path = str(testpath) 

551 # remove node-id syntax 

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

553 if i != -1: 

554 path = path[:i] 

555 anchor = absolutepath(current / path) 

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

557 self._try_load_conftest(anchor, namespace.importmode, rootpath) 

558 foundanchor = True 

559 if not foundanchor: 

560 self._try_load_conftest(current, namespace.importmode, rootpath) 

561 

562 def _is_in_confcutdir(self, path: Path) -> bool: 

563 """Whether a path is within the confcutdir. 

564 

565 When false, should not load conftest. 

566 """ 

567 if self._confcutdir is None: 

568 return True 

569 return path not in self._confcutdir.parents 

570 

571 def _try_load_conftest( 

572 self, anchor: Path, importmode: Union[str, ImportMode], rootpath: Path 

573 ) -> None: 

574 self._getconftestmodules(anchor, importmode, rootpath) 

575 # let's also consider test* subdirs 

576 if anchor.is_dir(): 

577 for x in anchor.glob("test*"): 

578 if x.is_dir(): 

579 self._getconftestmodules(x, importmode, rootpath) 

580 

581 def _getconftestmodules( 

582 self, path: Path, importmode: Union[str, ImportMode], rootpath: Path 

583 ) -> Sequence[types.ModuleType]: 

584 if self._noconftest: 

585 return [] 

586 

587 directory = self._get_directory(path) 

588 

589 # Optimization: avoid repeated searches in the same directory. 

590 # Assumes always called with same importmode and rootpath. 

591 existing_clist = self._dirpath2confmods.get(directory) 

592 if existing_clist is not None: 

593 return existing_clist 

594 

595 # XXX these days we may rather want to use config.rootpath 

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

597 # directories instead of requiring to specify confcutdir. 

598 clist = [] 

599 for parent in reversed((directory, *directory.parents)): 

600 if self._is_in_confcutdir(parent): 

601 conftestpath = parent / "conftest.py" 

602 if conftestpath.is_file(): 

603 mod = self._importconftest(conftestpath, importmode, rootpath) 

604 clist.append(mod) 

605 self._dirpath2confmods[directory] = clist 

606 return clist 

607 

608 def _rget_with_confmod( 

609 self, 

610 name: str, 

611 path: Path, 

612 importmode: Union[str, ImportMode], 

613 rootpath: Path, 

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

615 modules = self._getconftestmodules(path, importmode, rootpath=rootpath) 

616 for mod in reversed(modules): 

617 try: 

618 return mod, getattr(mod, name) 

619 except AttributeError: 

620 continue 

621 raise KeyError(name) 

622 

623 def _importconftest( 

624 self, conftestpath: Path, importmode: Union[str, ImportMode], rootpath: Path 

625 ) -> types.ModuleType: 

626 existing = self.get_plugin(str(conftestpath)) 

627 if existing is not None: 

628 return cast(types.ModuleType, existing) 

629 

630 pkgpath = resolve_package_path(conftestpath) 

631 if pkgpath is None: 

632 _ensure_removed_sysmodule(conftestpath.stem) 

633 

634 try: 

635 mod = import_path(conftestpath, mode=importmode, root=rootpath) 

636 except Exception as e: 

637 assert e.__traceback__ is not None 

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

639 raise ConftestImportFailure(conftestpath, exc_info) from e 

640 

641 self._check_non_top_pytest_plugins(mod, conftestpath) 

642 

643 self._conftest_plugins.add(mod) 

644 dirpath = conftestpath.parent 

645 if dirpath in self._dirpath2confmods: 

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

647 if dirpath in path.parents or path == dirpath: 

648 assert mod not in mods 

649 mods.append(mod) 

650 self.trace(f"loading conftestmodule {mod!r}") 

651 self.consider_conftest(mod) 

652 return mod 

653 

654 def _check_non_top_pytest_plugins( 

655 self, 

656 mod: types.ModuleType, 

657 conftestpath: Path, 

658 ) -> None: 

659 if ( 

660 hasattr(mod, "pytest_plugins") 

661 and self._configured 

662 and not self._using_pyargs 

663 ): 

664 msg = ( 

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

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

667 " {}\n" 

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

669 " {}\n" 

670 "For more information, visit:\n" 

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

672 ) 

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

674 

675 # 

676 # API for bootstrapping plugin loading 

677 # 

678 # 

679 

680 def consider_preparse( 

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

682 ) -> None: 

683 """:meta private:""" 

684 i = 0 

685 n = len(args) 

686 while i < n: 

687 opt = args[i] 

688 i += 1 

689 if isinstance(opt, str): 

690 if opt == "-p": 

691 try: 

692 parg = args[i] 

693 except IndexError: 

694 return 

695 i += 1 

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

697 parg = opt[2:] 

698 else: 

699 continue 

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

701 continue 

702 self.consider_pluginarg(parg) 

703 

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

705 """:meta private:""" 

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

707 name = arg[3:] 

708 if name in essential_plugins: 

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

710 

711 # PR #4304: remove stepwise if cacheprovider is blocked. 

712 if name == "cacheprovider": 

713 self.set_blocked("stepwise") 

714 self.set_blocked("pytest_stepwise") 

715 

716 self.set_blocked(name) 

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

718 self.set_blocked("pytest_" + name) 

719 else: 

720 name = arg 

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

722 # There is no interface with pluggy for this. 

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

724 del self._name2plugin[name] 

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

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

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

728 self.import_plugin(arg, consider_entry_points=True) 

729 

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

731 """:meta private:""" 

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

733 

734 def consider_env(self) -> None: 

735 """:meta private:""" 

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

737 

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

739 """:meta private:""" 

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

741 

742 def _import_plugin_specs( 

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

744 ) -> None: 

745 plugins = _get_plugin_specs_as_list(spec) 

746 for import_spec in plugins: 

747 self.import_plugin(import_spec) 

748 

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

750 """Import a plugin with ``modname``. 

751 

752 If ``consider_entry_points`` is True, entry point names are also 

753 considered to find a plugin. 

754 """ 

755 # Most often modname refers to builtin modules, e.g. "pytester", 

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

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

758 # _pytest prefix. 

759 assert isinstance(modname, str), ( 

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

761 ) 

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

763 return 

764 

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

766 self.rewrite_hook.mark_rewrite(importspec) 

767 

768 if consider_entry_points: 

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

770 if loaded: 

771 return 

772 

773 try: 

774 __import__(importspec) 

775 except ImportError as e: 

776 raise ImportError( 

777 f'Error importing plugin "{modname}": {e.args[0]}' 

778 ).with_traceback(e.__traceback__) from e 

779 

780 except Skipped as e: 

781 self.skipped_plugins.append((modname, e.msg or "")) 

782 else: 

783 mod = sys.modules[importspec] 

784 self.register(mod, modname) 

785 

786 

787def _get_plugin_specs_as_list( 

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

789) -> List[str]: 

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

791 # None means empty. 

792 if specs is None: 

793 return [] 

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

795 if isinstance(specs, types.ModuleType): 

796 return [] 

797 # Comma-separated list. 

798 if isinstance(specs, str): 

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

800 # Direct specification. 

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

802 return list(specs) 

803 raise UsageError( 

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

805 % specs 

806 ) 

807 

808 

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

810 try: 

811 del sys.modules[modname] 

812 except KeyError: 

813 pass 

814 

815 

816class Notset: 

817 def __repr__(self): 

818 return "<NOTSET>" 

819 

820 

821notset = Notset() 

822 

823 

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

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

826 be marked for assertion rewrite. 

827 

828 For example the package "pytest_mock/__init__.py" should be added as "pytest_mock" in 

829 the assertion rewrite mechanism. 

830 

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

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

833 

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

835 

836 pytest_mock/__init__.py 

837 pytest_mock/_version.py 

838 pytest_mock/plugin.py 

839 pytest_mock.egg-info/PKG-INFO 

840 

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

842 

843 src/pytest_mock/__init__.py 

844 src/pytest_mock/_version.py 

845 src/pytest_mock/plugin.py 

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

847 LICENSE 

848 setup.py 

849 

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

851 names should be considered for assertion rewriting. 

852 

853 More information: 

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

855 """ 

856 package_files = list(package_files) 

857 seen_some = False 

858 for fn in package_files: 

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

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

861 if is_simple_module: 

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

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

864 # as well as editable installation finder modules made by setuptools 

865 if module_name != "setup" and not module_name.startswith("__editable__"): 

866 seen_some = True 

867 yield module_name 

868 elif is_package: 

869 package_name = os.path.dirname(fn) 

870 seen_some = True 

871 yield package_name 

872 

873 if not seen_some: 

874 # At this point we did not find any packages or modules suitable for assertion 

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

876 # "src" based source trees for example). 

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

878 # are rarer. 

879 new_package_files = [] 

880 for fn in package_files: 

881 parts = fn.split("/") 

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

883 if new_fn: 

884 new_package_files.append(new_fn) 

885 if new_package_files: 

886 yield from _iter_rewritable_modules(new_package_files) 

887 

888 

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

890 return tuple(args) 

891 

892 

893@final 

894class Config: 

895 """Access to configuration values, pluginmanager and plugin hooks. 

896 

897 :param PytestPluginManager pluginmanager: 

898 A pytest PluginManager. 

899 

900 :param InvocationParams invocation_params: 

901 Object containing parameters regarding the :func:`pytest.main` 

902 invocation. 

903 """ 

904 

905 @final 

906 @attr.s(frozen=True, auto_attribs=True) 

907 class InvocationParams: 

908 """Holds parameters passed during :func:`pytest.main`. 

909 

910 The object attributes are read-only. 

911 

912 .. versionadded:: 5.1 

913 

914 .. note:: 

915 

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

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

918 

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

920 """ 

921 

922 args: Tuple[str, ...] = attr.ib(converter=_args_converter) 

923 """The command-line arguments as passed to :func:`pytest.main`.""" 

924 plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] 

925 """Extra plugins, might be `None`.""" 

926 dir: Path 

927 """The directory from which :func:`pytest.main` was invoked.""" 

928 

929 class ArgsSource(enum.Enum): 

930 """Indicates the source of the test arguments. 

931 

932 .. versionadded:: 7.2 

933 """ 

934 

935 #: Command line arguments. 

936 ARGS = enum.auto() 

937 #: Invocation directory. 

938 INCOVATION_DIR = enum.auto() 

939 #: 'testpaths' configuration value. 

940 TESTPATHS = enum.auto() 

941 

942 def __init__( 

943 self, 

944 pluginmanager: PytestPluginManager, 

945 *, 

946 invocation_params: Optional[InvocationParams] = None, 

947 ) -> None: 

948 from .argparsing import Parser, FILE_OR_DIR 

949 

950 if invocation_params is None: 

951 invocation_params = self.InvocationParams( 

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

953 ) 

954 

955 self.option = argparse.Namespace() 

956 """Access to command line option as attributes. 

957 

958 :type: argparse.Namespace 

959 """ 

960 

961 self.invocation_params = invocation_params 

962 """The parameters with which pytest was invoked. 

963 

964 :type: InvocationParams 

965 """ 

966 

967 _a = FILE_OR_DIR 

968 self._parser = Parser( 

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

970 processopt=self._processopt, 

971 _ispytest=True, 

972 ) 

973 self.pluginmanager = pluginmanager 

974 """The plugin manager handles plugin registration and hook invocation. 

975 

976 :type: PytestPluginManager 

977 """ 

978 

979 self.stash = Stash() 

980 """A place where plugins can store information on the config for their 

981 own use. 

982 

983 :type: Stash 

984 """ 

985 # Deprecated alias. Was never public. Can be removed in a few releases. 

986 self._store = self.stash 

987 

988 from .compat import PathAwareHookProxy 

989 

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

991 self.hook = PathAwareHookProxy(self.pluginmanager.hook) 

992 self._inicache: Dict[str, Any] = {} 

993 self._override_ini: Sequence[str] = () 

994 self._opt2dest: Dict[str, str] = {} 

995 self._cleanup: List[Callable[[], None]] = [] 

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

997 self._configured = False 

998 self.hook.pytest_addoption.call_historic( 

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

1000 ) 

1001 self.args_source = Config.ArgsSource.ARGS 

1002 self.args: List[str] = [] 

1003 

1004 if TYPE_CHECKING: 

1005 from _pytest.cacheprovider import Cache 

1006 

1007 self.cache: Optional[Cache] = None 

1008 

1009 @property 

1010 def rootpath(self) -> Path: 

1011 """The path to the :ref:`rootdir <rootdir>`. 

1012 

1013 :type: pathlib.Path 

1014 

1015 .. versionadded:: 6.1 

1016 """ 

1017 return self._rootpath 

1018 

1019 @property 

1020 def inipath(self) -> Optional[Path]: 

1021 """The path to the :ref:`configfile <configfiles>`. 

1022 

1023 :type: Optional[pathlib.Path] 

1024 

1025 .. versionadded:: 6.1 

1026 """ 

1027 return self._inipath 

1028 

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

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

1031 use (usually coinciding with pytest_unconfigure).""" 

1032 self._cleanup.append(func) 

1033 

1034 def _do_configure(self) -> None: 

1035 assert not self._configured 

1036 self._configured = True 

1037 with warnings.catch_warnings(): 

1038 warnings.simplefilter("default") 

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

1040 

1041 def _ensure_unconfigure(self) -> None: 

1042 if self._configured: 

1043 self._configured = False 

1044 self.hook.pytest_unconfigure(config=self) 

1045 self.hook.pytest_configure._call_history = [] 

1046 while self._cleanup: 

1047 fin = self._cleanup.pop() 

1048 fin() 

1049 

1050 def get_terminal_writer(self) -> TerminalWriter: 

1051 terminalreporter: TerminalReporter = self.pluginmanager.get_plugin( 

1052 "terminalreporter" 

1053 ) 

1054 return terminalreporter._tw 

1055 

1056 def pytest_cmdline_parse( 

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

1058 ) -> "Config": 

1059 try: 

1060 self.parse(args) 

1061 except UsageError: 

1062 

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

1064 # This gets done via helpconfig normally, but its 

1065 # pytest_cmdline_main is not called in case of errors. 

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

1067 from _pytest.helpconfig import showversion 

1068 

1069 showversion(self) 

1070 elif ( 

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

1072 ): 

1073 self._parser._getparser().print_help() 

1074 sys.stdout.write( 

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

1076 ) 

1077 

1078 raise 

1079 

1080 return self 

1081 

1082 def notify_exception( 

1083 self, 

1084 excinfo: ExceptionInfo[BaseException], 

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

1086 ) -> None: 

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

1088 style: _TracebackStyle = "long" 

1089 else: 

1090 style = "native" 

1091 excrepr = excinfo.getrepr( 

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

1093 ) 

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

1095 if not any(res): 

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

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

1098 sys.stderr.flush() 

1099 

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

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

1102 if self.invocation_params.dir != self.rootpath: 

1103 fullpath = self.rootpath / nodeid 

1104 nodeid = bestrelpath(self.invocation_params.dir, fullpath) 

1105 return nodeid 

1106 

1107 @classmethod 

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

1109 """Constructor usable for subprocesses.""" 

1110 config = get_config(args) 

1111 config.option.__dict__.update(option_dict) 

1112 config.parse(args, addopts=False) 

1113 for x in config.option.plugins: 

1114 config.pluginmanager.consider_pluginarg(x) 

1115 return config 

1116 

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

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

1119 self._opt2dest[name] = opt.dest 

1120 

1121 if hasattr(opt, "default"): 

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

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

1124 

1125 @hookimpl(trylast=True) 

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

1127 self.pluginmanager._set_initial_conftests( 

1128 early_config.known_args_namespace, rootpath=early_config.rootpath 

1129 ) 

1130 

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

1132 ns, unknown_args = self._parser.parse_known_and_unknown_args( 

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

1134 ) 

1135 rootpath, inipath, inicfg = determine_setup( 

1136 ns.inifilename, 

1137 ns.file_or_dir + unknown_args, 

1138 rootdir_cmd_arg=ns.rootdir or None, 

1139 config=self, 

1140 ) 

1141 self._rootpath = rootpath 

1142 self._inipath = inipath 

1143 self.inicfg = inicfg 

1144 self._parser.extra_info["rootdir"] = str(self.rootpath) 

1145 self._parser.extra_info["inifile"] = str(self.inipath) 

1146 self._parser.addini("addopts", "Extra command line options", "args") 

1147 self._parser.addini("minversion", "Minimally required pytest version") 

1148 self._parser.addini( 

1149 "required_plugins", 

1150 "Plugins that must be present for pytest to run", 

1151 type="args", 

1152 default=[], 

1153 ) 

1154 self._override_ini = ns.override_ini or () 

1155 

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

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

1158 

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

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

1161 by the importhook. 

1162 """ 

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

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

1165 if mode == "rewrite": 

1166 import _pytest.assertion 

1167 

1168 try: 

1169 hook = _pytest.assertion.install_importhook(self) 

1170 except SystemError: 

1171 mode = "plain" 

1172 else: 

1173 self._mark_plugins_for_rewrite(hook) 

1174 self._warn_about_missing_assertion(mode) 

1175 

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

1177 """Given an importhook, mark for rewrite any top-level 

1178 modules or packages in the distribution package for 

1179 all pytest plugins.""" 

1180 self.pluginmanager.rewrite_hook = hook 

1181 

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

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

1184 return 

1185 

1186 package_files = ( 

1187 str(file) 

1188 for dist in importlib_metadata.distributions() 

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

1190 for file in dist.files or [] 

1191 ) 

1192 

1193 for name in _iter_rewritable_modules(package_files): 

1194 hook.mark_rewrite(name) 

1195 

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

1197 """Validate known args.""" 

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

1199 try: 

1200 self._parser.parse_known_and_unknown_args( 

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

1202 ) 

1203 finally: 

1204 del self._parser._config_source_hint # type: ignore 

1205 

1206 return args 

1207 

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

1209 if addopts: 

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

1211 if len(env_addopts): 

1212 args[:] = ( 

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

1214 + args 

1215 ) 

1216 self._initini(args) 

1217 if addopts: 

1218 args[:] = ( 

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

1220 ) 

1221 

1222 self.known_args_namespace = self._parser.parse_known_args( 

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

1224 ) 

1225 self._checkversion() 

1226 self._consider_importhook(args) 

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

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

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

1230 # plugins are going to be loaded. 

1231 self.pluginmanager.load_setuptools_entrypoints("pytest11") 

1232 self.pluginmanager.consider_env() 

1233 

1234 self.known_args_namespace = self._parser.parse_known_args( 

1235 args, namespace=copy.copy(self.known_args_namespace) 

1236 ) 

1237 

1238 self._validate_plugins() 

1239 self._warn_about_skipped_plugins() 

1240 

1241 if self.known_args_namespace.strict: 

1242 self.issue_config_time_warning( 

1243 _pytest.deprecated.STRICT_OPTION, stacklevel=2 

1244 ) 

1245 

1246 if self.known_args_namespace.confcutdir is None and self.inipath is not None: 

1247 confcutdir = str(self.inipath.parent) 

1248 self.known_args_namespace.confcutdir = confcutdir 

1249 try: 

1250 self.hook.pytest_load_initial_conftests( 

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

1252 ) 

1253 except ConftestImportFailure as e: 

1254 if self.known_args_namespace.help or self.known_args_namespace.version: 

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

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

1257 self.issue_config_time_warning( 

1258 PytestConfigWarning(f"could not load initial conftests: {e.path}"), 

1259 stacklevel=2, 

1260 ) 

1261 else: 

1262 raise 

1263 

1264 @hookimpl(hookwrapper=True) 

1265 def pytest_collection(self) -> Generator[None, None, None]: 

1266 # Validate invalid ini keys after collection is done so we take in account 

1267 # options added by late-loading conftest files. 

1268 yield 

1269 self._validate_config_options() 

1270 

1271 def _checkversion(self) -> None: 

1272 import pytest 

1273 

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

1275 if minver: 

1276 # Imported lazily to improve start-up time. 

1277 from packaging.version import Version 

1278 

1279 if not isinstance(minver, str): 

1280 raise pytest.UsageError( 

1281 "%s: 'minversion' must be a single value" % self.inipath 

1282 ) 

1283 

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

1285 raise pytest.UsageError( 

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

1287 % ( 

1288 self.inipath, 

1289 minver, 

1290 pytest.__version__, 

1291 ) 

1292 ) 

1293 

1294 def _validate_config_options(self) -> None: 

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

1296 self._warn_or_fail_if_strict(f"Unknown config option: {key}\n") 

1297 

1298 def _validate_plugins(self) -> None: 

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

1300 if not required_plugins: 

1301 return 

1302 

1303 # Imported lazily to improve start-up time. 

1304 from packaging.version import Version 

1305 from packaging.requirements import InvalidRequirement, Requirement 

1306 

1307 plugin_info = self.pluginmanager.list_plugin_distinfo() 

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

1309 

1310 missing_plugins = [] 

1311 for required_plugin in required_plugins: 

1312 try: 

1313 req = Requirement(required_plugin) 

1314 except InvalidRequirement: 

1315 missing_plugins.append(required_plugin) 

1316 continue 

1317 

1318 if req.name not in plugin_dist_info: 

1319 missing_plugins.append(required_plugin) 

1320 elif not req.specifier.contains( 

1321 Version(plugin_dist_info[req.name]), prereleases=True 

1322 ): 

1323 missing_plugins.append(required_plugin) 

1324 

1325 if missing_plugins: 

1326 raise UsageError( 

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

1328 ) 

1329 

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

1331 if self.known_args_namespace.strict_config: 

1332 raise UsageError(message) 

1333 

1334 self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3) 

1335 

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

1337 parser_inicfg = self._parser._inidict 

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

1339 

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

1341 # Parse given cmdline arguments into this config object. 

1342 assert ( 

1343 self.args == [] 

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

1345 self.hook.pytest_addhooks.call_historic( 

1346 kwargs=dict(pluginmanager=self.pluginmanager) 

1347 ) 

1348 self._preparse(args, addopts=addopts) 

1349 # XXX deprecated hook: 

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

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

1352 try: 

1353 source = Config.ArgsSource.ARGS 

1354 args = self._parser.parse_setoption( 

1355 args, self.option, namespace=self.option 

1356 ) 

1357 if not args: 

1358 if self.invocation_params.dir == self.rootpath: 

1359 source = Config.ArgsSource.TESTPATHS 

1360 testpaths: List[str] = self.getini("testpaths") 

1361 if self.known_args_namespace.pyargs: 

1362 args = testpaths 

1363 else: 

1364 args = [] 

1365 for path in testpaths: 

1366 args.extend(sorted(glob.iglob(path, recursive=True))) 

1367 if not args: 

1368 source = Config.ArgsSource.INCOVATION_DIR 

1369 args = [str(self.invocation_params.dir)] 

1370 self.args = args 

1371 self.args_source = source 

1372 except PrintHelp: 

1373 pass 

1374 

1375 def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None: 

1376 """Issue and handle a warning during the "configure" stage. 

1377 

1378 During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item`` 

1379 function because it is not possible to have hookwrappers around ``pytest_configure``. 

1380 

1381 This function is mainly intended for plugins that need to issue warnings during 

1382 ``pytest_configure`` (or similar stages). 

1383 

1384 :param warning: The warning instance. 

1385 :param stacklevel: stacklevel forwarded to warnings.warn. 

1386 """ 

1387 if self.pluginmanager.is_blocked("warnings"): 

1388 return 

1389 

1390 cmdline_filters = self.known_args_namespace.pythonwarnings or [] 

1391 config_filters = self.getini("filterwarnings") 

1392 

1393 with warnings.catch_warnings(record=True) as records: 

1394 warnings.simplefilter("always", type(warning)) 

1395 apply_warning_filters(config_filters, cmdline_filters) 

1396 warnings.warn(warning, stacklevel=stacklevel) 

1397 

1398 if records: 

1399 frame = sys._getframe(stacklevel - 1) 

1400 location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name 

1401 self.hook.pytest_warning_recorded.call_historic( 

1402 kwargs=dict( 

1403 warning_message=records[0], 

1404 when="config", 

1405 nodeid="", 

1406 location=location, 

1407 ) 

1408 ) 

1409 

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

1411 """Add a line to an ini-file option. The option must have been 

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

1413 the first line in its value.""" 

1414 x = self.getini(name) 

1415 assert isinstance(x, list) 

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

1417 

1418 def getini(self, name: str): 

1419 """Return configuration value from an :ref:`ini file <configfiles>`. 

1420 

1421 If the specified name hasn't been registered through a prior 

1422 :func:`parser.addini <pytest.Parser.addini>` call (usually from a 

1423 plugin), a ValueError is raised. 

1424 """ 

1425 try: 

1426 return self._inicache[name] 

1427 except KeyError: 

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

1429 return val 

1430 

1431 # Meant for easy monkeypatching by legacypath plugin. 

1432 # Can be inlined back (with no cover removed) once legacypath is gone. 

1433 def _getini_unknown_type(self, name: str, type: str, value: Union[str, List[str]]): 

1434 msg = f"unknown configuration type: {type}" 

1435 raise ValueError(msg, value) # pragma: no cover 

1436 

1437 def _getini(self, name: str): 

1438 try: 

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

1440 except KeyError as e: 

1441 raise ValueError(f"unknown configuration value: {name!r}") from e 

1442 override_value = self._get_override_ini_value(name) 

1443 if override_value is None: 

1444 try: 

1445 value = self.inicfg[name] 

1446 except KeyError: 

1447 if default is not None: 

1448 return default 

1449 if type is None: 

1450 return "" 

1451 return [] 

1452 else: 

1453 value = override_value 

1454 # Coerce the values based on types. 

1455 # 

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

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

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

1459 # For example: 

1460 # 

1461 # ini: 

1462 # a_line_list = "tests acceptance" 

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

1464 # 

1465 # toml: 

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

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

1468 # 

1469 if type == "paths": 

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

1471 assert self.inipath is not None 

1472 dp = self.inipath.parent 

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

1474 return [dp / x for x in input_values] 

1475 elif type == "args": 

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

1477 elif type == "linelist": 

1478 if isinstance(value, str): 

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

1480 else: 

1481 return value 

1482 elif type == "bool": 

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

1484 elif type == "string": 

1485 return value 

1486 elif type is None: 

1487 return value 

1488 else: 

1489 return self._getini_unknown_type(name, type, value) 

1490 

1491 def _getconftest_pathlist( 

1492 self, name: str, path: Path, rootpath: Path 

1493 ) -> Optional[List[Path]]: 

1494 try: 

1495 mod, relroots = self.pluginmanager._rget_with_confmod( 

1496 name, path, self.getoption("importmode"), rootpath 

1497 ) 

1498 except KeyError: 

1499 return None 

1500 assert mod.__file__ is not None 

1501 modpath = Path(mod.__file__).parent 

1502 values: List[Path] = [] 

1503 for relroot in relroots: 

1504 if isinstance(relroot, os.PathLike): 

1505 relroot = Path(relroot) 

1506 else: 

1507 relroot = relroot.replace("/", os.sep) 

1508 relroot = absolutepath(modpath / relroot) 

1509 values.append(relroot) 

1510 return values 

1511 

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

1513 value = None 

1514 # override_ini is a list of "ini=value" options. 

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

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

1517 for ini_config in self._override_ini: 

1518 try: 

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

1520 except ValueError as e: 

1521 raise UsageError( 

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

1523 ini_config 

1524 ) 

1525 ) from e 

1526 else: 

1527 if key == name: 

1528 value = user_ini_value 

1529 return value 

1530 

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

1532 """Return command line option value. 

1533 

1534 :param name: Name of the option. You may also specify 

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

1536 :param default: Default value if no option of that name exists. 

1537 :param skip: If True, raise pytest.skip if option does not exists 

1538 or has a None value. 

1539 """ 

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

1541 try: 

1542 val = getattr(self.option, name) 

1543 if val is None and skip: 

1544 raise AttributeError(name) 

1545 return val 

1546 except AttributeError as e: 

1547 if default is not notset: 

1548 return default 

1549 if skip: 

1550 import pytest 

1551 

1552 pytest.skip(f"no {name!r} option found") 

1553 raise ValueError(f"no option named {name!r}") from e 

1554 

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

1556 """Deprecated, use getoption() instead.""" 

1557 return self.getoption(name) 

1558 

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

1560 """Deprecated, use getoption(skip=True) instead.""" 

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

1562 

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

1564 if not _assertion_supported(): 

1565 if mode == "plain": 

1566 warning_text = ( 

1567 "ASSERTIONS ARE NOT EXECUTED" 

1568 " and FAILING TESTS WILL PASS. Are you" 

1569 " using python -O?" 

1570 ) 

1571 else: 

1572 warning_text = ( 

1573 "assertions not in test modules or" 

1574 " plugins will be ignored" 

1575 " because assert statements are not executed " 

1576 "by the underlying Python interpreter " 

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

1578 ) 

1579 self.issue_config_time_warning( 

1580 PytestConfigWarning(warning_text), 

1581 stacklevel=3, 

1582 ) 

1583 

1584 def _warn_about_skipped_plugins(self) -> None: 

1585 for module_name, msg in self.pluginmanager.skipped_plugins: 

1586 self.issue_config_time_warning( 

1587 PytestConfigWarning(f"skipped plugin {module_name!r}: {msg}"), 

1588 stacklevel=2, 

1589 ) 

1590 

1591 

1592def _assertion_supported() -> bool: 

1593 try: 

1594 assert False 

1595 except AssertionError: 

1596 return True 

1597 else: 

1598 return False # type: ignore[unreachable] 

1599 

1600 

1601def create_terminal_writer( 

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

1603) -> TerminalWriter: 

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

1605 in the config object. 

1606 

1607 Every code which requires a TerminalWriter object and has access to a 

1608 config object should use this function. 

1609 """ 

1610 tw = TerminalWriter(file=file) 

1611 

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

1613 tw.hasmarkup = True 

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

1615 tw.hasmarkup = False 

1616 

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

1618 tw.code_highlight = True 

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

1620 tw.code_highlight = False 

1621 

1622 return tw 

1623 

1624 

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

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

1627 

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

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

1630 'val' is anything else. 

1631 

1632 .. note:: Copied from distutils.util. 

1633 """ 

1634 val = val.lower() 

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

1636 return True 

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

1638 return False 

1639 else: 

1640 raise ValueError(f"invalid truth value {val!r}") 

1641 

1642 

1643@lru_cache(maxsize=50) 

1644def parse_warning_filter( 

1645 arg: str, *, escape: bool 

1646) -> Tuple["warnings._ActionKind", str, Type[Warning], str, int]: 

1647 """Parse a warnings filter string. 

1648 

1649 This is copied from warnings._setoption with the following changes: 

1650 

1651 * Does not apply the filter. 

1652 * Escaping is optional. 

1653 * Raises UsageError so we get nice error messages on failure. 

1654 """ 

1655 __tracebackhide__ = True 

1656 error_template = dedent( 

1657 f"""\ 

1658 while parsing the following warning configuration: 

1659 

1660 {arg} 

1661 

1662 This error occurred: 

1663 

1664 {{error}} 

1665 """ 

1666 ) 

1667 

1668 parts = arg.split(":") 

1669 if len(parts) > 5: 

1670 doc_url = ( 

1671 "https://docs.python.org/3/library/warnings.html#describing-warning-filters" 

1672 ) 

1673 error = dedent( 

1674 f"""\ 

1675 Too many fields ({len(parts)}), expected at most 5 separated by colons: 

1676 

1677 action:message:category:module:line 

1678 

1679 For more information please consult: {doc_url} 

1680 """ 

1681 ) 

1682 raise UsageError(error_template.format(error=error)) 

1683 

1684 while len(parts) < 5: 

1685 parts.append("") 

1686 action_, message, category_, module, lineno_ = (s.strip() for s in parts) 

1687 try: 

1688 action: "warnings._ActionKind" = warnings._getaction(action_) # type: ignore[attr-defined] 

1689 except warnings._OptionError as e: 

1690 raise UsageError(error_template.format(error=str(e))) 

1691 try: 

1692 category: Type[Warning] = _resolve_warning_category(category_) 

1693 except Exception: 

1694 exc_info = ExceptionInfo.from_current() 

1695 exception_text = exc_info.getrepr(style="native") 

1696 raise UsageError(error_template.format(error=exception_text)) 

1697 if message and escape: 

1698 message = re.escape(message) 

1699 if module and escape: 

1700 module = re.escape(module) + r"\Z" 

1701 if lineno_: 

1702 try: 

1703 lineno = int(lineno_) 

1704 if lineno < 0: 

1705 raise ValueError("number is negative") 

1706 except ValueError as e: 

1707 raise UsageError( 

1708 error_template.format(error=f"invalid lineno {lineno_!r}: {e}") 

1709 ) 

1710 else: 

1711 lineno = 0 

1712 return action, message, category, module, lineno 

1713 

1714 

1715def _resolve_warning_category(category: str) -> Type[Warning]: 

1716 """ 

1717 Copied from warnings._getcategory, but changed so it lets exceptions (specially ImportErrors) 

1718 propagate so we can get access to their tracebacks (#9218). 

1719 """ 

1720 __tracebackhide__ = True 

1721 if not category: 

1722 return Warning 

1723 

1724 if "." not in category: 

1725 import builtins as m 

1726 

1727 klass = category 

1728 else: 

1729 module, _, klass = category.rpartition(".") 

1730 m = __import__(module, None, None, [klass]) 

1731 cat = getattr(m, klass) 

1732 if not issubclass(cat, Warning): 

1733 raise UsageError(f"{cat} is not a Warning subclass") 

1734 return cast(Type[Warning], cat) 

1735 

1736 

1737def apply_warning_filters( 

1738 config_filters: Iterable[str], cmdline_filters: Iterable[str] 

1739) -> None: 

1740 """Applies pytest-configured filters to the warnings module""" 

1741 # Filters should have this precedence: cmdline options, config. 

1742 # Filters should be applied in the inverse order of precedence. 

1743 for arg in config_filters: 

1744 warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) 

1745 

1746 for arg in cmdline_filters: 

1747 warnings.filterwarnings(*parse_warning_filter(arg, escape=True))