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