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 re 10import shlex 11import sys 12import types 13import warnings 14from functools import lru_cache 15from types import TracebackType 16from typing import Any 17from typing import Callable 18from typing import Dict 19from typing import Generator 20from typing import IO 21from typing import Iterable 22from typing import Iterator 23from typing import List 24from typing import Optional 25from typing import Sequence 26from typing import Set 27from typing import TextIO 28from typing import Tuple 29from typing import Union 30 31import attr 32import py 33from pluggy import HookimplMarker 34from pluggy import HookspecMarker 35from pluggy import PluginManager 36 37import _pytest._code 38import _pytest.deprecated 39import _pytest.hookspec 40from .exceptions import PrintHelp as PrintHelp 41from .exceptions import UsageError as UsageError 42from .findpaths import determine_setup 43from _pytest._code import ExceptionInfo 44from _pytest._code import filter_traceback 45from _pytest._io import TerminalWriter 46from _pytest.compat import final 47from _pytest.compat import importlib_metadata 48from _pytest.compat import TYPE_CHECKING 49from _pytest.outcomes import fail 50from _pytest.outcomes import Skipped 51from _pytest.pathlib import bestrelpath 52from _pytest.pathlib import import_path 53from _pytest.pathlib import ImportMode 54from _pytest.pathlib import Path 55from _pytest.store import Store 56from _pytest.warning_types import PytestConfigWarning 57 58if TYPE_CHECKING: 59 from typing import Type 60 61 from _pytest._code.code import _TracebackStyle 62 from _pytest.terminal import TerminalReporter 63 from .argparsing import Argument 64 65 66_PluggyPlugin = object 67"""A type to represent plugin objects. 68 69Plugins can be any namespace, so we can't narrow it down much, but we use an 70alias to make the intent clear. 71 72Ideally this type would be provided by pluggy itself. 73""" 74 75 76hookimpl = HookimplMarker("pytest") 77hookspec = HookspecMarker("pytest") 78 79 80@final 81class ExitCode(enum.IntEnum): 82 """Encodes the valid exit codes by pytest. 83 84 Currently users and plugins may supply other exit codes as well. 85 86 .. versionadded:: 5.0 87 """ 88 89 #: Tests passed. 90 OK = 0 91 #: Tests failed. 92 TESTS_FAILED = 1 93 #: pytest was interrupted. 94 INTERRUPTED = 2 95 #: An internal error got in the way. 96 INTERNAL_ERROR = 3 97 #: pytest was misused. 98 USAGE_ERROR = 4 99 #: pytest couldn't find tests. 100 NO_TESTS_COLLECTED = 5 101 102 103class ConftestImportFailure(Exception): 104 def __init__( 105 self, 106 path: py.path.local, 107 excinfo: Tuple["Type[Exception]", Exception, TracebackType], 108 ) -> None: 109 super().__init__(path, excinfo) 110 self.path = path 111 self.excinfo = excinfo 112 113 def __str__(self) -> str: 114 return "{}: {} (from {})".format( 115 self.excinfo[0].__name__, self.excinfo[1], self.path 116 ) 117 118 119def filter_traceback_for_conftest_import_failure( 120 entry: _pytest._code.TracebackEntry, 121) -> bool: 122 """Filter tracebacks entries which point to pytest internals or importlib. 123 124 Make a special case for importlib because we use it to import test modules and conftest files 125 in _pytest.pathlib.import_path. 126 """ 127 return filter_traceback(entry) and "importlib" not in str(entry.path).split(os.sep) 128 129 130def main( 131 args: Optional[Union[List[str], py.path.local]] = None, 132 plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, 133) -> Union[int, ExitCode]: 134 """Perform an in-process test run. 135 136 :param args: List of command line arguments. 137 :param plugins: List of plugin objects to be auto-registered during initialization. 138 139 :returns: An exit code. 140 """ 141 try: 142 try: 143 config = _prepareconfig(args, plugins) 144 except ConftestImportFailure as e: 145 exc_info = ExceptionInfo(e.excinfo) 146 tw = TerminalWriter(sys.stderr) 147 tw.line( 148 "ImportError while loading conftest '{e.path}'.".format(e=e), red=True 149 ) 150 exc_info.traceback = exc_info.traceback.filter( 151 filter_traceback_for_conftest_import_failure 152 ) 153 exc_repr = ( 154 exc_info.getrepr(style="short", chain=False) 155 if exc_info.traceback 156 else exc_info.exconly() 157 ) 158 formatted_tb = str(exc_repr) 159 for line in formatted_tb.splitlines(): 160 tw.line(line.rstrip(), red=True) 161 return ExitCode.USAGE_ERROR 162 else: 163 try: 164 ret = config.hook.pytest_cmdline_main( 165 config=config 166 ) # type: Union[ExitCode, int] 167 try: 168 return ExitCode(ret) 169 except ValueError: 170 return ret 171 finally: 172 config._ensure_unconfigure() 173 except UsageError as e: 174 tw = TerminalWriter(sys.stderr) 175 for msg in e.args: 176 tw.line("ERROR: {}\n".format(msg), red=True) 177 return ExitCode.USAGE_ERROR 178 179 180def console_main() -> int: 181 """The CLI entry point of pytest. 182 183 This function is not meant for programmable use; use `main()` instead. 184 """ 185 # https://docs.python.org/3/library/signal.html#note-on-sigpipe 186 try: 187 code = main() 188 sys.stdout.flush() 189 return code 190 except BrokenPipeError: 191 # Python flushes standard streams on exit; redirect remaining output 192 # to devnull to avoid another BrokenPipeError at shutdown 193 devnull = os.open(os.devnull, os.O_WRONLY) 194 os.dup2(devnull, sys.stdout.fileno()) 195 return 1 # Python exits with error code 1 on EPIPE 196 197 198class cmdline: # compatibility namespace 199 main = staticmethod(main) 200 201 202def filename_arg(path: str, optname: str) -> str: 203 """Argparse type validator for filename arguments. 204 205 :path: Path of filename. 206 :optname: Name of the option. 207 """ 208 if os.path.isdir(path): 209 raise UsageError("{} must be a filename, given: {}".format(optname, path)) 210 return path 211 212 213def directory_arg(path: str, optname: str) -> str: 214 """Argparse type validator for directory arguments. 215 216 :path: Path of directory. 217 :optname: Name of the option. 218 """ 219 if not os.path.isdir(path): 220 raise UsageError("{} must be a directory, given: {}".format(optname, path)) 221 return path 222 223 224# Plugins that cannot be disabled via "-p no:X" currently. 225essential_plugins = ( 226 "mark", 227 "main", 228 "runner", 229 "fixtures", 230 "helpconfig", # Provides -p. 231) 232 233default_plugins = essential_plugins + ( 234 "python", 235 "terminal", 236 "debugging", 237 "unittest", 238 "capture", 239 "skipping", 240 "tmpdir", 241 "monkeypatch", 242 "recwarn", 243 "pastebin", 244 "nose", 245 "assertion", 246 "junitxml", 247 "doctest", 248 "cacheprovider", 249 "freeze_support", 250 "setuponly", 251 "setupplan", 252 "stepwise", 253 "warnings", 254 "logging", 255 "reports", 256 "faulthandler", 257) 258 259builtin_plugins = set(default_plugins) 260builtin_plugins.add("pytester") 261 262 263def get_config( 264 args: Optional[List[str]] = None, 265 plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, 266) -> "Config": 267 # subsequent calls to main will create a fresh instance 268 pluginmanager = PytestPluginManager() 269 config = Config( 270 pluginmanager, 271 invocation_params=Config.InvocationParams( 272 args=args or (), plugins=plugins, dir=Path.cwd(), 273 ), 274 ) 275 276 if args is not None: 277 # Handle any "-p no:plugin" args. 278 pluginmanager.consider_preparse(args, exclude_only=True) 279 280 for spec in default_plugins: 281 pluginmanager.import_plugin(spec) 282 283 return config 284 285 286def get_plugin_manager() -> "PytestPluginManager": 287 """Obtain a new instance of the 288 :py:class:`_pytest.config.PytestPluginManager`, with default plugins 289 already loaded. 290 291 This function can be used by integration with other tools, like hooking 292 into pytest to run tests into an IDE. 293 """ 294 return get_config().pluginmanager 295 296 297def _prepareconfig( 298 args: Optional[Union[py.path.local, List[str]]] = None, 299 plugins: Optional[Sequence[Union[str, _PluggyPlugin]]] = None, 300) -> "Config": 301 if args is None: 302 args = sys.argv[1:] 303 elif isinstance(args, py.path.local): 304 args = [str(args)] 305 elif not isinstance(args, list): 306 msg = "`args` parameter expected to be a list of strings, got: {!r} (type: {})" 307 raise TypeError(msg.format(args, type(args))) 308 309 config = get_config(args, plugins) 310 pluginmanager = config.pluginmanager 311 try: 312 if plugins: 313 for plugin in plugins: 314 if isinstance(plugin, str): 315 pluginmanager.consider_pluginarg(plugin) 316 else: 317 pluginmanager.register(plugin) 318 config = pluginmanager.hook.pytest_cmdline_parse( 319 pluginmanager=pluginmanager, args=args 320 ) 321 return config 322 except BaseException: 323 config._ensure_unconfigure() 324 raise 325 326 327@final 328class PytestPluginManager(PluginManager): 329 """A :py:class:`pluggy.PluginManager <pluggy.PluginManager>` with 330 additional pytest-specific functionality: 331 332 * Loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and 333 ``pytest_plugins`` global variables found in plugins being loaded. 334 * ``conftest.py`` loading during start-up. 335 """ 336 337 def __init__(self) -> None: 338 import _pytest.assertion 339 340 super().__init__("pytest") 341 # The objects are module objects, only used generically. 342 self._conftest_plugins = set() # type: Set[types.ModuleType] 343 344 # State related to local conftest plugins. 345 self._dirpath2confmods = {} # type: Dict[py.path.local, List[types.ModuleType]] 346 self._conftestpath2mod = {} # type: Dict[Path, types.ModuleType] 347 self._confcutdir = None # type: Optional[py.path.local] 348 self._noconftest = False 349 self._duplicatepaths = set() # type: Set[py.path.local] 350 351 # plugins that were explicitly skipped with pytest.skip 352 # list of (module name, skip reason) 353 # previously we would issue a warning when a plugin was skipped, but 354 # since we refactored warnings as first citizens of Config, they are 355 # just stored here to be used later. 356 self.skipped_plugins = [] # type: List[Tuple[str, str]] 357 358 self.add_hookspecs(_pytest.hookspec) 359 self.register(self) 360 if os.environ.get("PYTEST_DEBUG"): 361 err = sys.stderr # type: IO[str] 362 encoding = getattr(err, "encoding", "utf8") # type: str 363 try: 364 err = open( 365 os.dup(err.fileno()), mode=err.mode, buffering=1, encoding=encoding, 366 ) 367 except Exception: 368 pass 369 self.trace.root.setwriter(err.write) 370 self.enable_tracing() 371 372 # Config._consider_importhook will set a real object if required. 373 self.rewrite_hook = _pytest.assertion.DummyRewriteHook() 374 # Used to know when we are importing conftests after the pytest_configure stage. 375 self._configured = False 376 377 def parse_hookimpl_opts(self, plugin: _PluggyPlugin, name: str): 378 # pytest hooks are always prefixed with "pytest_", 379 # so we avoid accessing possibly non-readable attributes 380 # (see issue #1073). 381 if not name.startswith("pytest_"): 382 return 383 # Ignore names which can not be hooks. 384 if name == "pytest_plugins": 385 return 386 387 method = getattr(plugin, name) 388 opts = super().parse_hookimpl_opts(plugin, name) 389 390 # Consider only actual functions for hooks (#3775). 391 if not inspect.isroutine(method): 392 return 393 394 # Collect unmarked hooks as long as they have the `pytest_' prefix. 395 if opts is None and name.startswith("pytest_"): 396 opts = {} 397 if opts is not None: 398 # TODO: DeprecationWarning, people should use hookimpl 399 # https://github.com/pytest-dev/pytest/issues/4562 400 known_marks = {m.name for m in getattr(method, "pytestmark", [])} 401 402 for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"): 403 opts.setdefault(name, hasattr(method, name) or name in known_marks) 404 return opts 405 406 def parse_hookspec_opts(self, module_or_class, name: str): 407 opts = super().parse_hookspec_opts(module_or_class, name) 408 if opts is None: 409 method = getattr(module_or_class, name) 410 411 if name.startswith("pytest_"): 412 # todo: deprecate hookspec hacks 413 # https://github.com/pytest-dev/pytest/issues/4562 414 known_marks = {m.name for m in getattr(method, "pytestmark", [])} 415 opts = { 416 "firstresult": hasattr(method, "firstresult") 417 or "firstresult" in known_marks, 418 "historic": hasattr(method, "historic") 419 or "historic" in known_marks, 420 } 421 return opts 422 423 def register( 424 self, plugin: _PluggyPlugin, name: Optional[str] = None 425 ) -> Optional[str]: 426 if name in _pytest.deprecated.DEPRECATED_EXTERNAL_PLUGINS: 427 warnings.warn( 428 PytestConfigWarning( 429 "{} plugin has been merged into the core, " 430 "please remove it from your requirements.".format( 431 name.replace("_", "-") 432 ) 433 ) 434 ) 435 return None 436 ret = super().register(plugin, name) # type: Optional[str] 437 if ret: 438 self.hook.pytest_plugin_registered.call_historic( 439 kwargs=dict(plugin=plugin, manager=self) 440 ) 441 442 if isinstance(plugin, types.ModuleType): 443 self.consider_module(plugin) 444 return ret 445 446 def getplugin(self, name: str): 447 # Support deprecated naming because plugins (xdist e.g.) use it. 448 plugin = self.get_plugin(name) # type: Optional[_PluggyPlugin] 449 return plugin 450 451 def hasplugin(self, name: str) -> bool: 452 """Return whether a plugin with the given name is registered.""" 453 return bool(self.get_plugin(name)) 454 455 def pytest_configure(self, config: "Config") -> None: 456 """:meta private:""" 457 # XXX now that the pluginmanager exposes hookimpl(tryfirst...) 458 # we should remove tryfirst/trylast as markers. 459 config.addinivalue_line( 460 "markers", 461 "tryfirst: mark a hook implementation function such that the " 462 "plugin machinery will try to call it first/as early as possible.", 463 ) 464 config.addinivalue_line( 465 "markers", 466 "trylast: mark a hook implementation function such that the " 467 "plugin machinery will try to call it last/as late as possible.", 468 ) 469 self._configured = True 470 471 # 472 # Internal API for local conftest plugin handling. 473 # 474 def _set_initial_conftests(self, namespace: argparse.Namespace) -> None: 475 """Load initial conftest files given a preparsed "namespace". 476 477 As conftest files may add their own command line options which have 478 arguments ('--my-opt somepath') we might get some false positives. 479 All builtin and 3rd party plugins will have been loaded, however, so 480 common options will not confuse our logic here. 481 """ 482 current = py.path.local() 483 self._confcutdir = ( 484 current.join(namespace.confcutdir, abs=True) 485 if namespace.confcutdir 486 else None 487 ) 488 self._noconftest = namespace.noconftest 489 self._using_pyargs = namespace.pyargs 490 testpaths = namespace.file_or_dir 491 foundanchor = False 492 for testpath in testpaths: 493 path = str(testpath) 494 # remove node-id syntax 495 i = path.find("::") 496 if i != -1: 497 path = path[:i] 498 anchor = current.join(path, abs=1) 499 if anchor.exists(): # we found some file object 500 self._try_load_conftest(anchor, namespace.importmode) 501 foundanchor = True 502 if not foundanchor: 503 self._try_load_conftest(current, namespace.importmode) 504 505 def _try_load_conftest( 506 self, anchor: py.path.local, importmode: Union[str, ImportMode] 507 ) -> None: 508 self._getconftestmodules(anchor, importmode) 509 # let's also consider test* subdirs 510 if anchor.check(dir=1): 511 for x in anchor.listdir("test*"): 512 if x.check(dir=1): 513 self._getconftestmodules(x, importmode) 514 515 @lru_cache(maxsize=128) 516 def _getconftestmodules( 517 self, path: py.path.local, importmode: Union[str, ImportMode], 518 ) -> List[types.ModuleType]: 519 if self._noconftest: 520 return [] 521 522 if path.isfile(): 523 directory = path.dirpath() 524 else: 525 directory = path 526 527 # XXX these days we may rather want to use config.rootpath 528 # and allow users to opt into looking into the rootdir parent 529 # directories instead of requiring to specify confcutdir. 530 clist = [] 531 for parent in directory.parts(): 532 if self._confcutdir and self._confcutdir.relto(parent): 533 continue 534 conftestpath = parent.join("conftest.py") 535 if conftestpath.isfile(): 536 mod = self._importconftest(conftestpath, importmode) 537 clist.append(mod) 538 self._dirpath2confmods[directory] = clist 539 return clist 540 541 def _rget_with_confmod( 542 self, name: str, path: py.path.local, importmode: Union[str, ImportMode], 543 ) -> Tuple[types.ModuleType, Any]: 544 modules = self._getconftestmodules(path, importmode) 545 for mod in reversed(modules): 546 try: 547 return mod, getattr(mod, name) 548 except AttributeError: 549 continue 550 raise KeyError(name) 551 552 def _importconftest( 553 self, conftestpath: py.path.local, importmode: Union[str, ImportMode], 554 ) -> types.ModuleType: 555 # Use a resolved Path object as key to avoid loading the same conftest 556 # twice with build systems that create build directories containing 557 # symlinks to actual files. 558 # Using Path().resolve() is better than py.path.realpath because 559 # it resolves to the correct path/drive in case-insensitive file systems (#5792) 560 key = Path(str(conftestpath)).resolve() 561 562 with contextlib.suppress(KeyError): 563 return self._conftestpath2mod[key] 564 565 pkgpath = conftestpath.pypkgpath() 566 if pkgpath is None: 567 _ensure_removed_sysmodule(conftestpath.purebasename) 568 569 try: 570 mod = import_path(conftestpath, mode=importmode) 571 except Exception as e: 572 assert e.__traceback__ is not None 573 exc_info = (type(e), e, e.__traceback__) 574 raise ConftestImportFailure(conftestpath, exc_info) from e 575 576 self._check_non_top_pytest_plugins(mod, conftestpath) 577 578 self._conftest_plugins.add(mod) 579 self._conftestpath2mod[key] = mod 580 dirpath = conftestpath.dirpath() 581 if dirpath in self._dirpath2confmods: 582 for path, mods in self._dirpath2confmods.items(): 583 if path and path.relto(dirpath) or path == dirpath: 584 assert mod not in mods 585 mods.append(mod) 586 self.trace("loading conftestmodule {!r}".format(mod)) 587 self.consider_conftest(mod) 588 return mod 589 590 def _check_non_top_pytest_plugins( 591 self, mod: types.ModuleType, conftestpath: py.path.local, 592 ) -> None: 593 if ( 594 hasattr(mod, "pytest_plugins") 595 and self._configured 596 and not self._using_pyargs 597 ): 598 msg = ( 599 "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n" 600 "It affects the entire test suite instead of just below the conftest as expected.\n" 601 " {}\n" 602 "Please move it to a top level conftest file at the rootdir:\n" 603 " {}\n" 604 "For more information, visit:\n" 605 " https://docs.pytest.org/en/stable/deprecations.html#pytest-plugins-in-non-top-level-conftest-files" 606 ) 607 fail(msg.format(conftestpath, self._confcutdir), pytrace=False) 608 609 # 610 # API for bootstrapping plugin loading 611 # 612 # 613 614 def consider_preparse( 615 self, args: Sequence[str], *, exclude_only: bool = False 616 ) -> None: 617 i = 0 618 n = len(args) 619 while i < n: 620 opt = args[i] 621 i += 1 622 if isinstance(opt, str): 623 if opt == "-p": 624 try: 625 parg = args[i] 626 except IndexError: 627 return 628 i += 1 629 elif opt.startswith("-p"): 630 parg = opt[2:] 631 else: 632 continue 633 if exclude_only and not parg.startswith("no:"): 634 continue 635 self.consider_pluginarg(parg) 636 637 def consider_pluginarg(self, arg: str) -> None: 638 if arg.startswith("no:"): 639 name = arg[3:] 640 if name in essential_plugins: 641 raise UsageError("plugin %s cannot be disabled" % name) 642 643 # PR #4304: remove stepwise if cacheprovider is blocked. 644 if name == "cacheprovider": 645 self.set_blocked("stepwise") 646 self.set_blocked("pytest_stepwise") 647 648 self.set_blocked(name) 649 if not name.startswith("pytest_"): 650 self.set_blocked("pytest_" + name) 651 else: 652 name = arg 653 # Unblock the plugin. None indicates that it has been blocked. 654 # There is no interface with pluggy for this. 655 if self._name2plugin.get(name, -1) is None: 656 del self._name2plugin[name] 657 if not name.startswith("pytest_"): 658 if self._name2plugin.get("pytest_" + name, -1) is None: 659 del self._name2plugin["pytest_" + name] 660 self.import_plugin(arg, consider_entry_points=True) 661 662 def consider_conftest(self, conftestmodule: types.ModuleType) -> None: 663 self.register(conftestmodule, name=conftestmodule.__file__) 664 665 def consider_env(self) -> None: 666 self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) 667 668 def consider_module(self, mod: types.ModuleType) -> None: 669 self._import_plugin_specs(getattr(mod, "pytest_plugins", [])) 670 671 def _import_plugin_specs( 672 self, spec: Union[None, types.ModuleType, str, Sequence[str]] 673 ) -> None: 674 plugins = _get_plugin_specs_as_list(spec) 675 for import_spec in plugins: 676 self.import_plugin(import_spec) 677 678 def import_plugin(self, modname: str, consider_entry_points: bool = False) -> None: 679 """Import a plugin with ``modname``. 680 681 If ``consider_entry_points`` is True, entry point names are also 682 considered to find a plugin. 683 """ 684 # Most often modname refers to builtin modules, e.g. "pytester", 685 # "terminal" or "capture". Those plugins are registered under their 686 # basename for historic purposes but must be imported with the 687 # _pytest prefix. 688 assert isinstance(modname, str), ( 689 "module name as text required, got %r" % modname 690 ) 691 if self.is_blocked(modname) or self.get_plugin(modname) is not None: 692 return 693 694 importspec = "_pytest." + modname if modname in builtin_plugins else modname 695 self.rewrite_hook.mark_rewrite(importspec) 696 697 if consider_entry_points: 698 loaded = self.load_setuptools_entrypoints("pytest11", name=modname) 699 if loaded: 700 return 701 702 try: 703 __import__(importspec) 704 except ImportError as e: 705 raise ImportError( 706 'Error importing plugin "{}": {}'.format(modname, str(e.args[0])) 707 ).with_traceback(e.__traceback__) from e 708 709 except Skipped as e: 710 self.skipped_plugins.append((modname, e.msg or "")) 711 else: 712 mod = sys.modules[importspec] 713 self.register(mod, modname) 714 715 716def _get_plugin_specs_as_list( 717 specs: Union[None, types.ModuleType, str, Sequence[str]] 718) -> List[str]: 719 """Parse a plugins specification into a list of plugin names.""" 720 # None means empty. 721 if specs is None: 722 return [] 723 # Workaround for #3899 - a submodule which happens to be called "pytest_plugins". 724 if isinstance(specs, types.ModuleType): 725 return [] 726 # Comma-separated list. 727 if isinstance(specs, str): 728 return specs.split(",") if specs else [] 729 # Direct specification. 730 if isinstance(specs, collections.abc.Sequence): 731 return list(specs) 732 raise UsageError( 733 "Plugins may be specified as a sequence or a ','-separated string of plugin names. Got: %r" 734 % specs 735 ) 736 737 738def _ensure_removed_sysmodule(modname: str) -> None: 739 try: 740 del sys.modules[modname] 741 except KeyError: 742 pass 743 744 745class Notset: 746 def __repr__(self): 747 return "<NOTSET>" 748 749 750notset = Notset() 751 752 753def _iter_rewritable_modules(package_files: Iterable[str]) -> Iterator[str]: 754 """Given an iterable of file names in a source distribution, return the "names" that should 755 be marked for assertion rewrite. 756 757 For example the package "pytest_mock/__init__.py" should be added as "pytest_mock" in 758 the assertion rewrite mechanism. 759 760 This function has to deal with dist-info based distributions and egg based distributions 761 (which are still very much in use for "editable" installs). 762 763 Here are the file names as seen in a dist-info based distribution: 764 765 pytest_mock/__init__.py 766 pytest_mock/_version.py 767 pytest_mock/plugin.py 768 pytest_mock.egg-info/PKG-INFO 769 770 Here are the file names as seen in an egg based distribution: 771 772 src/pytest_mock/__init__.py 773 src/pytest_mock/_version.py 774 src/pytest_mock/plugin.py 775 src/pytest_mock.egg-info/PKG-INFO 776 LICENSE 777 setup.py 778 779 We have to take in account those two distribution flavors in order to determine which 780 names should be considered for assertion rewriting. 781 782 More information: 783 https://github.com/pytest-dev/pytest-mock/issues/167 784 """ 785 package_files = list(package_files) 786 seen_some = False 787 for fn in package_files: 788 is_simple_module = "/" not in fn and fn.endswith(".py") 789 is_package = fn.count("/") == 1 and fn.endswith("__init__.py") 790 if is_simple_module: 791 module_name, _ = os.path.splitext(fn) 792 # we ignore "setup.py" at the root of the distribution 793 if module_name != "setup": 794 seen_some = True 795 yield module_name 796 elif is_package: 797 package_name = os.path.dirname(fn) 798 seen_some = True 799 yield package_name 800 801 if not seen_some: 802 # At this point we did not find any packages or modules suitable for assertion 803 # rewriting, so we try again by stripping the first path component (to account for 804 # "src" based source trees for example). 805 # This approach lets us have the common case continue to be fast, as egg-distributions 806 # are rarer. 807 new_package_files = [] 808 for fn in package_files: 809 parts = fn.split("/") 810 new_fn = "/".join(parts[1:]) 811 if new_fn: 812 new_package_files.append(new_fn) 813 if new_package_files: 814 yield from _iter_rewritable_modules(new_package_files) 815 816 817def _args_converter(args: Iterable[str]) -> Tuple[str, ...]: 818 return tuple(args) 819 820 821@final 822class Config: 823 """Access to configuration values, pluginmanager and plugin hooks. 824 825 :param PytestPluginManager pluginmanager: 826 827 :param InvocationParams invocation_params: 828 Object containing parameters regarding the :func:`pytest.main` 829 invocation. 830 """ 831 832 @final 833 @attr.s(frozen=True) 834 class InvocationParams: 835 """Holds parameters passed during :func:`pytest.main`. 836 837 The object attributes are read-only. 838 839 .. versionadded:: 5.1 840 841 .. note:: 842 843 Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts`` 844 ini option are handled by pytest, not being included in the ``args`` attribute. 845 846 Plugins accessing ``InvocationParams`` must be aware of that. 847 """ 848 849 args = attr.ib(type=Tuple[str, ...], converter=_args_converter) 850 """The command-line arguments as passed to :func:`pytest.main`. 851 852 :type: Tuple[str, ...] 853 """ 854 plugins = attr.ib(type=Optional[Sequence[Union[str, _PluggyPlugin]]]) 855 """Extra plugins, might be `None`. 856 857 :type: Optional[Sequence[Union[str, plugin]]] 858 """ 859 dir = attr.ib(type=Path) 860 """The directory from which :func:`pytest.main` was invoked. 861 862 :type: pathlib.Path 863 """ 864 865 def __init__( 866 self, 867 pluginmanager: PytestPluginManager, 868 *, 869 invocation_params: Optional[InvocationParams] = None 870 ) -> None: 871 from .argparsing import Parser, FILE_OR_DIR 872 873 if invocation_params is None: 874 invocation_params = self.InvocationParams( 875 args=(), plugins=None, dir=Path.cwd() 876 ) 877 878 self.option = argparse.Namespace() 879 """Access to command line option as attributes. 880 881 :type: argparse.Namespace 882 """ 883 884 self.invocation_params = invocation_params 885 """The parameters with which pytest was invoked. 886 887 :type: InvocationParams 888 """ 889 890 _a = FILE_OR_DIR 891 self._parser = Parser( 892 usage="%(prog)s [options] [{}] [{}] [...]".format(_a, _a), 893 processopt=self._processopt, 894 ) 895 self.pluginmanager = pluginmanager 896 """The plugin manager handles plugin registration and hook invocation. 897 898 :type: PytestPluginManager 899 """ 900 901 self.trace = self.pluginmanager.trace.root.get("config") 902 self.hook = self.pluginmanager.hook 903 self._inicache = {} # type: Dict[str, Any] 904 self._override_ini = () # type: Sequence[str] 905 self._opt2dest = {} # type: Dict[str, str] 906 self._cleanup = [] # type: List[Callable[[], None]] 907 # A place where plugins can store information on the config for their 908 # own use. Currently only intended for internal plugins. 909 self._store = Store() 910 self.pluginmanager.register(self, "pytestconfig") 911 self._configured = False 912 self.hook.pytest_addoption.call_historic( 913 kwargs=dict(parser=self._parser, pluginmanager=self.pluginmanager) 914 ) 915 916 if TYPE_CHECKING: 917 from _pytest.cacheprovider import Cache 918 919 self.cache = None # type: Optional[Cache] 920 921 @property 922 def invocation_dir(self) -> py.path.local: 923 """The directory from which pytest was invoked. 924 925 Prefer to use :attr:`invocation_params.dir <InvocationParams.dir>`, 926 which is a :class:`pathlib.Path`. 927 928 :type: py.path.local 929 """ 930 return py.path.local(str(self.invocation_params.dir)) 931 932 @property 933 def rootpath(self) -> Path: 934 """The path to the :ref:`rootdir <rootdir>`. 935 936 :type: pathlib.Path 937 938 .. versionadded:: 6.1 939 """ 940 return self._rootpath 941 942 @property 943 def rootdir(self) -> py.path.local: 944 """The path to the :ref:`rootdir <rootdir>`. 945 946 Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`. 947 948 :type: py.path.local 949 """ 950 return py.path.local(str(self.rootpath)) 951 952 @property 953 def inipath(self) -> Optional[Path]: 954 """The path to the :ref:`configfile <configfiles>`. 955 956 :type: Optional[pathlib.Path] 957 958 .. versionadded:: 6.1 959 """ 960 return self._inipath 961 962 @property 963 def inifile(self) -> Optional[py.path.local]: 964 """The path to the :ref:`configfile <configfiles>`. 965 966 Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`. 967 968 :type: Optional[py.path.local] 969 """ 970 return py.path.local(str(self.inipath)) if self.inipath else None 971 972 def add_cleanup(self, func: Callable[[], None]) -> None: 973 """Add a function to be called when the config object gets out of 974 use (usually coninciding with pytest_unconfigure).""" 975 self._cleanup.append(func) 976 977 def _do_configure(self) -> None: 978 assert not self._configured 979 self._configured = True 980 with warnings.catch_warnings(): 981 warnings.simplefilter("default") 982 self.hook.pytest_configure.call_historic(kwargs=dict(config=self)) 983 984 def _ensure_unconfigure(self) -> None: 985 if self._configured: 986 self._configured = False 987 self.hook.pytest_unconfigure(config=self) 988 self.hook.pytest_configure._call_history = [] 989 while self._cleanup: 990 fin = self._cleanup.pop() 991 fin() 992 993 def get_terminal_writer(self) -> TerminalWriter: 994 terminalreporter = self.pluginmanager.get_plugin( 995 "terminalreporter" 996 ) # type: TerminalReporter 997 return terminalreporter._tw 998 999 def pytest_cmdline_parse( 1000 self, pluginmanager: PytestPluginManager, args: List[str] 1001 ) -> "Config": 1002 try: 1003 self.parse(args) 1004 except UsageError: 1005 1006 # Handle --version and --help here in a minimal fashion. 1007 # This gets done via helpconfig normally, but its 1008 # pytest_cmdline_main is not called in case of errors. 1009 if getattr(self.option, "version", False) or "--version" in args: 1010 from _pytest.helpconfig import showversion 1011 1012 showversion(self) 1013 elif ( 1014 getattr(self.option, "help", False) or "--help" in args or "-h" in args 1015 ): 1016 self._parser._getparser().print_help() 1017 sys.stdout.write( 1018 "\nNOTE: displaying only minimal help due to UsageError.\n\n" 1019 ) 1020 1021 raise 1022 1023 return self 1024 1025 def notify_exception( 1026 self, 1027 excinfo: ExceptionInfo[BaseException], 1028 option: Optional[argparse.Namespace] = None, 1029 ) -> None: 1030 if option and getattr(option, "fulltrace", False): 1031 style = "long" # type: _TracebackStyle 1032 else: 1033 style = "native" 1034 excrepr = excinfo.getrepr( 1035 funcargs=True, showlocals=getattr(option, "showlocals", False), style=style 1036 ) 1037 res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo) 1038 if not any(res): 1039 for line in str(excrepr).split("\n"): 1040 sys.stderr.write("INTERNALERROR> %s\n" % line) 1041 sys.stderr.flush() 1042 1043 def cwd_relative_nodeid(self, nodeid: str) -> str: 1044 # nodeid's are relative to the rootpath, compute relative to cwd. 1045 if self.invocation_params.dir != self.rootpath: 1046 fullpath = self.rootpath / nodeid 1047 nodeid = bestrelpath(self.invocation_params.dir, fullpath) 1048 return nodeid 1049 1050 @classmethod 1051 def fromdictargs(cls, option_dict, args) -> "Config": 1052 """Constructor usable for subprocesses.""" 1053 config = get_config(args) 1054 config.option.__dict__.update(option_dict) 1055 config.parse(args, addopts=False) 1056 for x in config.option.plugins: 1057 config.pluginmanager.consider_pluginarg(x) 1058 return config 1059 1060 def _processopt(self, opt: "Argument") -> None: 1061 for name in opt._short_opts + opt._long_opts: 1062 self._opt2dest[name] = opt.dest 1063 1064 if hasattr(opt, "default"): 1065 if not hasattr(self.option, opt.dest): 1066 setattr(self.option, opt.dest, opt.default) 1067 1068 @hookimpl(trylast=True) 1069 def pytest_load_initial_conftests(self, early_config: "Config") -> None: 1070 self.pluginmanager._set_initial_conftests(early_config.known_args_namespace) 1071 1072 def _initini(self, args: Sequence[str]) -> None: 1073 ns, unknown_args = self._parser.parse_known_and_unknown_args( 1074 args, namespace=copy.copy(self.option) 1075 ) 1076 rootpath, inipath, inicfg = determine_setup( 1077 ns.inifilename, 1078 ns.file_or_dir + unknown_args, 1079 rootdir_cmd_arg=ns.rootdir or None, 1080 config=self, 1081 ) 1082 self._rootpath = rootpath 1083 self._inipath = inipath 1084 self.inicfg = inicfg 1085 self._parser.extra_info["rootdir"] = str(self.rootpath) 1086 self._parser.extra_info["inifile"] = str(self.inipath) 1087 self._parser.addini("addopts", "extra command line options", "args") 1088 self._parser.addini("minversion", "minimally required pytest version") 1089 self._parser.addini( 1090 "required_plugins", 1091 "plugins that must be present for pytest to run", 1092 type="args", 1093 default=[], 1094 ) 1095 self._override_ini = ns.override_ini or () 1096 1097 def _consider_importhook(self, args: Sequence[str]) -> None: 1098 """Install the PEP 302 import hook if using assertion rewriting. 1099 1100 Needs to parse the --assert=<mode> option from the commandline 1101 and find all the installed plugins to mark them for rewriting 1102 by the importhook. 1103 """ 1104 ns, unknown_args = self._parser.parse_known_and_unknown_args(args) 1105 mode = getattr(ns, "assertmode", "plain") 1106 if mode == "rewrite": 1107 import _pytest.assertion 1108 1109 try: 1110 hook = _pytest.assertion.install_importhook(self) 1111 except SystemError: 1112 mode = "plain" 1113 else: 1114 self._mark_plugins_for_rewrite(hook) 1115 self._warn_about_missing_assertion(mode) 1116 1117 def _mark_plugins_for_rewrite(self, hook) -> None: 1118 """Given an importhook, mark for rewrite any top-level 1119 modules or packages in the distribution package for 1120 all pytest plugins.""" 1121 self.pluginmanager.rewrite_hook = hook 1122 1123 if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): 1124 # We don't autoload from setuptools entry points, no need to continue. 1125 return 1126 1127 package_files = ( 1128 str(file) 1129 for dist in importlib_metadata.distributions() 1130 if any(ep.group == "pytest11" for ep in dist.entry_points) 1131 for file in dist.files or [] 1132 ) 1133 1134 for name in _iter_rewritable_modules(package_files): 1135 hook.mark_rewrite(name) 1136 1137 def _validate_args(self, args: List[str], via: str) -> List[str]: 1138 """Validate known args.""" 1139 self._parser._config_source_hint = via # type: ignore 1140 try: 1141 self._parser.parse_known_and_unknown_args( 1142 args, namespace=copy.copy(self.option) 1143 ) 1144 finally: 1145 del self._parser._config_source_hint # type: ignore 1146 1147 return args 1148 1149 def _preparse(self, args: List[str], addopts: bool = True) -> None: 1150 if addopts: 1151 env_addopts = os.environ.get("PYTEST_ADDOPTS", "") 1152 if len(env_addopts): 1153 args[:] = ( 1154 self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS") 1155 + args 1156 ) 1157 self._initini(args) 1158 if addopts: 1159 args[:] = ( 1160 self._validate_args(self.getini("addopts"), "via addopts config") + args 1161 ) 1162 1163 self.known_args_namespace = self._parser.parse_known_args( 1164 args, namespace=copy.copy(self.option) 1165 ) 1166 self._checkversion() 1167 self._consider_importhook(args) 1168 self.pluginmanager.consider_preparse(args, exclude_only=False) 1169 if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): 1170 # Don't autoload from setuptools entry point. Only explicitly specified 1171 # plugins are going to be loaded. 1172 self.pluginmanager.load_setuptools_entrypoints("pytest11") 1173 self.pluginmanager.consider_env() 1174 1175 self.known_args_namespace = self._parser.parse_known_args( 1176 args, namespace=copy.copy(self.known_args_namespace) 1177 ) 1178 1179 self._validate_plugins() 1180 self._warn_about_skipped_plugins() 1181 1182 if self.known_args_namespace.confcutdir is None and self.inipath is not None: 1183 confcutdir = str(self.inipath.parent) 1184 self.known_args_namespace.confcutdir = confcutdir 1185 try: 1186 self.hook.pytest_load_initial_conftests( 1187 early_config=self, args=args, parser=self._parser 1188 ) 1189 except ConftestImportFailure as e: 1190 if self.known_args_namespace.help or self.known_args_namespace.version: 1191 # we don't want to prevent --help/--version to work 1192 # so just let is pass and print a warning at the end 1193 self.issue_config_time_warning( 1194 PytestConfigWarning( 1195 "could not load initial conftests: {}".format(e.path) 1196 ), 1197 stacklevel=2, 1198 ) 1199 else: 1200 raise 1201 1202 @hookimpl(hookwrapper=True) 1203 def pytest_collection(self) -> Generator[None, None, None]: 1204 """Validate invalid ini keys after collection is done so we take in account 1205 options added by late-loading conftest files.""" 1206 yield 1207 self._validate_config_options() 1208 1209 def _checkversion(self) -> None: 1210 import pytest 1211 1212 minver = self.inicfg.get("minversion", None) 1213 if minver: 1214 # Imported lazily to improve start-up time. 1215 from packaging.version import Version 1216 1217 if not isinstance(minver, str): 1218 raise pytest.UsageError( 1219 "%s: 'minversion' must be a single value" % self.inipath 1220 ) 1221 1222 if Version(minver) > Version(pytest.__version__): 1223 raise pytest.UsageError( 1224 "%s: 'minversion' requires pytest-%s, actual pytest-%s'" 1225 % (self.inipath, minver, pytest.__version__,) 1226 ) 1227 1228 def _validate_config_options(self) -> None: 1229 for key in sorted(self._get_unknown_ini_keys()): 1230 self._warn_or_fail_if_strict("Unknown config option: {}\n".format(key)) 1231 1232 def _validate_plugins(self) -> None: 1233 required_plugins = sorted(self.getini("required_plugins")) 1234 if not required_plugins: 1235 return 1236 1237 # Imported lazily to improve start-up time. 1238 from packaging.version import Version 1239 from packaging.requirements import InvalidRequirement, Requirement 1240 1241 plugin_info = self.pluginmanager.list_plugin_distinfo() 1242 plugin_dist_info = {dist.project_name: dist.version for _, dist in plugin_info} 1243 1244 missing_plugins = [] 1245 for required_plugin in required_plugins: 1246 try: 1247 spec = Requirement(required_plugin) 1248 except InvalidRequirement: 1249 missing_plugins.append(required_plugin) 1250 continue 1251 1252 if spec.name not in plugin_dist_info: 1253 missing_plugins.append(required_plugin) 1254 elif Version(plugin_dist_info[spec.name]) not in spec.specifier: 1255 missing_plugins.append(required_plugin) 1256 1257 if missing_plugins: 1258 raise UsageError( 1259 "Missing required plugins: {}".format(", ".join(missing_plugins)), 1260 ) 1261 1262 def _warn_or_fail_if_strict(self, message: str) -> None: 1263 if self.known_args_namespace.strict_config: 1264 raise UsageError(message) 1265 1266 self.issue_config_time_warning(PytestConfigWarning(message), stacklevel=3) 1267 1268 def _get_unknown_ini_keys(self) -> List[str]: 1269 parser_inicfg = self._parser._inidict 1270 return [name for name in self.inicfg if name not in parser_inicfg] 1271 1272 def parse(self, args: List[str], addopts: bool = True) -> None: 1273 # Parse given cmdline arguments into this config object. 1274 assert not hasattr( 1275 self, "args" 1276 ), "can only parse cmdline args at most once per Config object" 1277 self.hook.pytest_addhooks.call_historic( 1278 kwargs=dict(pluginmanager=self.pluginmanager) 1279 ) 1280 self._preparse(args, addopts=addopts) 1281 # XXX deprecated hook: 1282 self.hook.pytest_cmdline_preparse(config=self, args=args) 1283 self._parser.after_preparse = True # type: ignore 1284 try: 1285 args = self._parser.parse_setoption( 1286 args, self.option, namespace=self.option 1287 ) 1288 if not args: 1289 if self.invocation_params.dir == self.rootpath: 1290 args = self.getini("testpaths") 1291 if not args: 1292 args = [str(self.invocation_params.dir)] 1293 self.args = args 1294 except PrintHelp: 1295 pass 1296 1297 def issue_config_time_warning(self, warning: Warning, stacklevel: int) -> None: 1298 """Issue and handle a warning during the "configure" stage. 1299 1300 During ``pytest_configure`` we can't capture warnings using the ``catch_warnings_for_item`` 1301 function because it is not possible to have hookwrappers around ``pytest_configure``. 1302 1303 This function is mainly intended for plugins that need to issue warnings during 1304 ``pytest_configure`` (or similar stages). 1305 1306 :param warning: The warning instance. 1307 :param stacklevel: stacklevel forwarded to warnings.warn. 1308 """ 1309 if self.pluginmanager.is_blocked("warnings"): 1310 return 1311 1312 cmdline_filters = self.known_args_namespace.pythonwarnings or [] 1313 config_filters = self.getini("filterwarnings") 1314 1315 with warnings.catch_warnings(record=True) as records: 1316 warnings.simplefilter("always", type(warning)) 1317 apply_warning_filters(config_filters, cmdline_filters) 1318 warnings.warn(warning, stacklevel=stacklevel) 1319 1320 if records: 1321 frame = sys._getframe(stacklevel - 1) 1322 location = frame.f_code.co_filename, frame.f_lineno, frame.f_code.co_name 1323 self.hook.pytest_warning_captured.call_historic( 1324 kwargs=dict( 1325 warning_message=records[0], 1326 when="config", 1327 item=None, 1328 location=location, 1329 ) 1330 ) 1331 self.hook.pytest_warning_recorded.call_historic( 1332 kwargs=dict( 1333 warning_message=records[0], 1334 when="config", 1335 nodeid="", 1336 location=location, 1337 ) 1338 ) 1339 1340 def addinivalue_line(self, name: str, line: str) -> None: 1341 """Add a line to an ini-file option. The option must have been 1342 declared but might not yet be set in which case the line becomes 1343 the first line in its value.""" 1344 x = self.getini(name) 1345 assert isinstance(x, list) 1346 x.append(line) # modifies the cached list inline 1347 1348 def getini(self, name: str): 1349 """Return configuration value from an :ref:`ini file <configfiles>`. 1350 1351 If the specified name hasn't been registered through a prior 1352 :py:func:`parser.addini <_pytest.config.argparsing.Parser.addini>` 1353 call (usually from a plugin), a ValueError is raised. 1354 """ 1355 try: 1356 return self._inicache[name] 1357 except KeyError: 1358 self._inicache[name] = val = self._getini(name) 1359 return val 1360 1361 def _getini(self, name: str): 1362 try: 1363 description, type, default = self._parser._inidict[name] 1364 except KeyError as e: 1365 raise ValueError("unknown configuration value: {!r}".format(name)) from e 1366 override_value = self._get_override_ini_value(name) 1367 if override_value is None: 1368 try: 1369 value = self.inicfg[name] 1370 except KeyError: 1371 if default is not None: 1372 return default 1373 if type is None: 1374 return "" 1375 return [] 1376 else: 1377 value = override_value 1378 # Coerce the values based on types. 1379 # 1380 # Note: some coercions are only required if we are reading from .ini files, because 1381 # the file format doesn't contain type information, but when reading from toml we will 1382 # get either str or list of str values (see _parse_ini_config_from_pyproject_toml). 1383 # For example: 1384 # 1385 # ini: 1386 # a_line_list = "tests acceptance" 1387 # in this case, we need to split the string to obtain a list of strings. 1388 # 1389 # toml: 1390 # a_line_list = ["tests", "acceptance"] 1391 # in this case, we already have a list ready to use. 1392 # 1393 if type == "pathlist": 1394 # TODO: This assert is probably not valid in all cases. 1395 assert self.inipath is not None 1396 dp = self.inipath.parent 1397 input_values = shlex.split(value) if isinstance(value, str) else value 1398 return [py.path.local(str(dp / x)) for x in input_values] 1399 elif type == "args": 1400 return shlex.split(value) if isinstance(value, str) else value 1401 elif type == "linelist": 1402 if isinstance(value, str): 1403 return [t for t in map(lambda x: x.strip(), value.split("\n")) if t] 1404 else: 1405 return value 1406 elif type == "bool": 1407 return _strtobool(str(value).strip()) 1408 else: 1409 assert type is None 1410 return value 1411 1412 def _getconftest_pathlist( 1413 self, name: str, path: py.path.local 1414 ) -> Optional[List[py.path.local]]: 1415 try: 1416 mod, relroots = self.pluginmanager._rget_with_confmod( 1417 name, path, self.getoption("importmode") 1418 ) 1419 except KeyError: 1420 return None 1421 modpath = py.path.local(mod.__file__).dirpath() 1422 values = [] # type: List[py.path.local] 1423 for relroot in relroots: 1424 if not isinstance(relroot, py.path.local): 1425 relroot = relroot.replace("/", os.sep) 1426 relroot = modpath.join(relroot, abs=True) 1427 values.append(relroot) 1428 return values 1429 1430 def _get_override_ini_value(self, name: str) -> Optional[str]: 1431 value = None 1432 # override_ini is a list of "ini=value" options. 1433 # Always use the last item if multiple values are set for same ini-name, 1434 # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2. 1435 for ini_config in self._override_ini: 1436 try: 1437 key, user_ini_value = ini_config.split("=", 1) 1438 except ValueError as e: 1439 raise UsageError( 1440 "-o/--override-ini expects option=value style (got: {!r}).".format( 1441 ini_config 1442 ) 1443 ) from e 1444 else: 1445 if key == name: 1446 value = user_ini_value 1447 return value 1448 1449 def getoption(self, name: str, default=notset, skip: bool = False): 1450 """Return command line option value. 1451 1452 :param name: Name of the option. You may also specify 1453 the literal ``--OPT`` option instead of the "dest" option name. 1454 :param default: Default value if no option of that name exists. 1455 :param skip: If True, raise pytest.skip if option does not exists 1456 or has a None value. 1457 """ 1458 name = self._opt2dest.get(name, name) 1459 try: 1460 val = getattr(self.option, name) 1461 if val is None and skip: 1462 raise AttributeError(name) 1463 return val 1464 except AttributeError as e: 1465 if default is not notset: 1466 return default 1467 if skip: 1468 import pytest 1469 1470 pytest.skip("no {!r} option found".format(name)) 1471 raise ValueError("no option named {!r}".format(name)) from e 1472 1473 def getvalue(self, name: str, path=None): 1474 """Deprecated, use getoption() instead.""" 1475 return self.getoption(name) 1476 1477 def getvalueorskip(self, name: str, path=None): 1478 """Deprecated, use getoption(skip=True) instead.""" 1479 return self.getoption(name, skip=True) 1480 1481 def _warn_about_missing_assertion(self, mode: str) -> None: 1482 if not _assertion_supported(): 1483 if mode == "plain": 1484 warning_text = ( 1485 "ASSERTIONS ARE NOT EXECUTED" 1486 " and FAILING TESTS WILL PASS. Are you" 1487 " using python -O?" 1488 ) 1489 else: 1490 warning_text = ( 1491 "assertions not in test modules or" 1492 " plugins will be ignored" 1493 " because assert statements are not executed " 1494 "by the underlying Python interpreter " 1495 "(are you using python -O?)\n" 1496 ) 1497 self.issue_config_time_warning( 1498 PytestConfigWarning(warning_text), stacklevel=3, 1499 ) 1500 1501 def _warn_about_skipped_plugins(self) -> None: 1502 for module_name, msg in self.pluginmanager.skipped_plugins: 1503 self.issue_config_time_warning( 1504 PytestConfigWarning("skipped plugin {!r}: {}".format(module_name, msg)), 1505 stacklevel=2, 1506 ) 1507 1508 1509def _assertion_supported() -> bool: 1510 try: 1511 assert False 1512 except AssertionError: 1513 return True 1514 else: 1515 return False # type: ignore[unreachable] 1516 1517 1518def create_terminal_writer( 1519 config: Config, file: Optional[TextIO] = None 1520) -> TerminalWriter: 1521 """Create a TerminalWriter instance configured according to the options 1522 in the config object. 1523 1524 Every code which requires a TerminalWriter object and has access to a 1525 config object should use this function. 1526 """ 1527 tw = TerminalWriter(file=file) 1528 1529 if config.option.color == "yes": 1530 tw.hasmarkup = True 1531 elif config.option.color == "no": 1532 tw.hasmarkup = False 1533 1534 if config.option.code_highlight == "yes": 1535 tw.code_highlight = True 1536 elif config.option.code_highlight == "no": 1537 tw.code_highlight = False 1538 1539 return tw 1540 1541 1542def _strtobool(val: str) -> bool: 1543 """Convert a string representation of truth to True or False. 1544 1545 True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values 1546 are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if 1547 'val' is anything else. 1548 1549 .. note:: Copied from distutils.util. 1550 """ 1551 val = val.lower() 1552 if val in ("y", "yes", "t", "true", "on", "1"): 1553 return True 1554 elif val in ("n", "no", "f", "false", "off", "0"): 1555 return False 1556 else: 1557 raise ValueError("invalid truth value {!r}".format(val)) 1558 1559 1560@lru_cache(maxsize=50) 1561def parse_warning_filter( 1562 arg: str, *, escape: bool 1563) -> "Tuple[str, str, Type[Warning], str, int]": 1564 """Parse a warnings filter string. 1565 1566 This is copied from warnings._setoption, but does not apply the filter, 1567 only parses it, and makes the escaping optional. 1568 """ 1569 parts = arg.split(":") 1570 if len(parts) > 5: 1571 raise warnings._OptionError("too many fields (max 5): {!r}".format(arg)) 1572 while len(parts) < 5: 1573 parts.append("") 1574 action_, message, category_, module, lineno_ = [s.strip() for s in parts] 1575 action = warnings._getaction(action_) # type: str # type: ignore[attr-defined] 1576 category = warnings._getcategory( 1577 category_ 1578 ) # type: Type[Warning] # type: ignore[attr-defined] 1579 if message and escape: 1580 message = re.escape(message) 1581 if module and escape: 1582 module = re.escape(module) + r"\Z" 1583 if lineno_: 1584 try: 1585 lineno = int(lineno_) 1586 if lineno < 0: 1587 raise ValueError 1588 except (ValueError, OverflowError) as e: 1589 raise warnings._OptionError("invalid lineno {!r}".format(lineno_)) from e 1590 else: 1591 lineno = 0 1592 return action, message, category, module, lineno 1593 1594 1595def apply_warning_filters( 1596 config_filters: Iterable[str], cmdline_filters: Iterable[str] 1597) -> None: 1598 """Applies pytest-configured filters to the warnings module""" 1599 # Filters should have this precedence: cmdline options, config. 1600 # Filters should be applied in the inverse order of precedence. 1601 for arg in config_filters: 1602 warnings.filterwarnings(*parse_warning_filter(arg, escape=False)) 1603 1604 for arg in cmdline_filters: 1605 warnings.filterwarnings(*parse_warning_filter(arg, escape=True)) 1606