1# -*- coding: utf-8 -*-
2""" command line options, ini-file and conftest.py processing. """
3from __future__ import absolute_import
4from __future__ import division
5from __future__ import print_function
6
7import argparse
8import copy
9import inspect
10import os
11import shlex
12import sys
13import types
14import warnings
15
16import attr
17import py
18import six
19from packaging.version import Version
20from pluggy import HookimplMarker
21from pluggy import HookspecMarker
22from pluggy import PluginManager
23
24import _pytest._code
25import _pytest.assertion
26import _pytest.hookspec  # the extension point definitions
27from .exceptions import PrintHelp
28from .exceptions import UsageError
29from .findpaths import determine_setup
30from .findpaths import exists
31from _pytest import deprecated
32from _pytest._code import ExceptionInfo
33from _pytest._code import filter_traceback
34from _pytest.compat import importlib_metadata
35from _pytest.compat import lru_cache
36from _pytest.compat import safe_str
37from _pytest.outcomes import fail
38from _pytest.outcomes import Skipped
39from _pytest.pathlib import Path
40from _pytest.warning_types import PytestConfigWarning
41
42hookimpl = HookimplMarker("pytest")
43hookspec = HookspecMarker("pytest")
44
45
46class ConftestImportFailure(Exception):
47    def __init__(self, path, excinfo):
48        Exception.__init__(self, path, excinfo)
49        self.path = path
50        self.excinfo = excinfo
51
52
53def main(args=None, plugins=None):
54    """ return exit code, after performing an in-process test run.
55
56    :arg args: list of command line arguments.
57
58    :arg plugins: list of plugin objects to be auto-registered during
59                  initialization.
60    """
61    from _pytest.main import EXIT_USAGEERROR
62
63    try:
64        try:
65            config = _prepareconfig(args, plugins)
66        except ConftestImportFailure as e:
67            exc_info = ExceptionInfo(e.excinfo)
68            tw = py.io.TerminalWriter(sys.stderr)
69            tw.line(
70                "ImportError while loading conftest '{e.path}'.".format(e=e), red=True
71            )
72            exc_info.traceback = exc_info.traceback.filter(filter_traceback)
73            exc_repr = (
74                exc_info.getrepr(style="short", chain=False)
75                if exc_info.traceback
76                else exc_info.exconly()
77            )
78            formatted_tb = safe_str(exc_repr)
79            for line in formatted_tb.splitlines():
80                tw.line(line.rstrip(), red=True)
81            return 4
82        else:
83            try:
84                return config.hook.pytest_cmdline_main(config=config)
85            finally:
86                config._ensure_unconfigure()
87    except UsageError as e:
88        tw = py.io.TerminalWriter(sys.stderr)
89        for msg in e.args:
90            tw.line("ERROR: {}\n".format(msg), red=True)
91        return EXIT_USAGEERROR
92
93
94class cmdline(object):  # compatibility namespace
95    main = staticmethod(main)
96
97
98def filename_arg(path, optname):
99    """ Argparse type validator for filename arguments.
100
101    :path: path of filename
102    :optname: name of the option
103    """
104    if os.path.isdir(path):
105        raise UsageError("{} must be a filename, given: {}".format(optname, path))
106    return path
107
108
109def directory_arg(path, optname):
110    """Argparse type validator for directory arguments.
111
112    :path: path of directory
113    :optname: name of the option
114    """
115    if not os.path.isdir(path):
116        raise UsageError("{} must be a directory, given: {}".format(optname, path))
117    return path
118
119
120# Plugins that cannot be disabled via "-p no:X" currently.
121essential_plugins = (
122    "mark",
123    "main",
124    "runner",
125    "fixtures",
126    "helpconfig",  # Provides -p.
127)
128
129default_plugins = essential_plugins + (
130    "python",
131    "terminal",
132    "debugging",
133    "unittest",
134    "capture",
135    "skipping",
136    "tmpdir",
137    "monkeypatch",
138    "recwarn",
139    "pastebin",
140    "nose",
141    "assertion",
142    "junitxml",
143    "resultlog",
144    "doctest",
145    "cacheprovider",
146    "freeze_support",
147    "setuponly",
148    "setupplan",
149    "stepwise",
150    "warnings",
151    "logging",
152    "reports",
153)
154
155builtin_plugins = set(default_plugins)
156builtin_plugins.add("pytester")
157
158
159def get_config(args=None, plugins=None):
160    # subsequent calls to main will create a fresh instance
161    pluginmanager = PytestPluginManager()
162    config = Config(
163        pluginmanager,
164        invocation_params=Config.InvocationParams(
165            args=args, plugins=plugins, dir=Path().resolve()
166        ),
167    )
168
169    if args is not None:
170        # Handle any "-p no:plugin" args.
171        pluginmanager.consider_preparse(args)
172
173    for spec in default_plugins:
174        pluginmanager.import_plugin(spec)
175    return config
176
177
178def get_plugin_manager():
179    """
180    Obtain a new instance of the
181    :py:class:`_pytest.config.PytestPluginManager`, with default plugins
182    already loaded.
183
184    This function can be used by integration with other tools, like hooking
185    into pytest to run tests into an IDE.
186    """
187    return get_config().pluginmanager
188
189
190def _prepareconfig(args=None, plugins=None):
191    warning = None
192    if args is None:
193        args = sys.argv[1:]
194    elif isinstance(args, py.path.local):
195        args = [str(args)]
196    elif not isinstance(args, (tuple, list)):
197        msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})"
198        raise TypeError(msg.format(args, type(args)))
199
200    config = get_config(args, plugins)
201    pluginmanager = config.pluginmanager
202    try:
203        if plugins:
204            for plugin in plugins:
205                if isinstance(plugin, six.string_types):
206                    pluginmanager.consider_pluginarg(plugin)
207                else:
208                    pluginmanager.register(plugin)
209        if warning:
210            from _pytest.warnings import _issue_warning_captured
211
212            _issue_warning_captured(warning, hook=config.hook, stacklevel=4)
213        return pluginmanager.hook.pytest_cmdline_parse(
214            pluginmanager=pluginmanager, args=args
215        )
216    except BaseException:
217        config._ensure_unconfigure()
218        raise
219
220
221class PytestPluginManager(PluginManager):
222    """
223    Overwrites :py:class:`pluggy.PluginManager <pluggy.PluginManager>` to add pytest-specific
224    functionality:
225
226    * loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and
227      ``pytest_plugins`` global variables found in plugins being loaded;
228    * ``conftest.py`` loading during start-up;
229    """
230
231    def __init__(self):
232        super(PytestPluginManager, self).__init__("pytest")
233        self._conftest_plugins = set()
234
235        # state related to local conftest plugins
236        self._dirpath2confmods = {}
237        self._conftestpath2mod = {}
238        self._confcutdir = None
239        self._noconftest = False
240        self._duplicatepaths = set()
241
242        self.add_hookspecs(_pytest.hookspec)
243        self.register(self)
244        if os.environ.get("PYTEST_DEBUG"):
245            err = sys.stderr
246            encoding = getattr(err, "encoding", "utf8")
247            try:
248                err = py.io.dupfile(err, encoding=encoding)
249            except Exception:
250                pass
251            self.trace.root.setwriter(err.write)
252            self.enable_tracing()
253
254        # Config._consider_importhook will set a real object if required.
255        self.rewrite_hook = _pytest.assertion.DummyRewriteHook()
256        # Used to know when we are importing conftests after the pytest_configure stage
257        self._configured = False
258
259    def addhooks(self, module_or_class):
260        """
261        .. deprecated:: 2.8
262
263        Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>`
264        instead.
265        """
266        warnings.warn(deprecated.PLUGIN_MANAGER_ADDHOOKS, stacklevel=2)
267        return self.add_hookspecs(module_or_class)
268
269    def parse_hookimpl_opts(self, plugin, name):
270        # pytest hooks are always prefixed with pytest_
271        # so we avoid accessing possibly non-readable attributes
272        # (see issue #1073)
273        if not name.startswith("pytest_"):
274            return
275        # ignore names which can not be hooks
276        if name == "pytest_plugins":
277            return
278
279        method = getattr(plugin, name)
280        opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
281
282        # consider only actual functions for hooks (#3775)
283        if not inspect.isroutine(method):
284            return
285
286        # collect unmarked hooks as long as they have the `pytest_' prefix
287        if opts is None and name.startswith("pytest_"):
288            opts = {}
289        if opts is not None:
290            # TODO: DeprecationWarning, people should use hookimpl
291            # https://github.com/pytest-dev/pytest/issues/4562
292            known_marks = {m.name for m in getattr(method, "pytestmark", [])}
293
294            for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
295                opts.setdefault(name, hasattr(method, name) or name in known_marks)
296        return opts
297
298    def parse_hookspec_opts(self, module_or_class, name):
299        opts = super(PytestPluginManager, self).parse_hookspec_opts(
300            module_or_class, name
301        )
302        if opts is None:
303            method = getattr(module_or_class, name)
304
305            if name.startswith("pytest_"):
306                # todo: deprecate hookspec hacks
307                # https://github.com/pytest-dev/pytest/issues/4562
308                known_marks = {m.name for m in getattr(method, "pytestmark", [])}
309                opts = {
310                    "firstresult": hasattr(method, "firstresult")
311                    or "firstresult" in known_marks,
312                    "historic": hasattr(method, "historic")
313                    or "historic" in known_marks,
314                }
315        return opts
316
317    def register(self, plugin, name=None):
318        if name in ["pytest_catchlog", "pytest_capturelog"]:
319            warnings.warn(
320                PytestConfigWarning(
321                    "{} plugin has been merged into the core, "
322                    "please remove it from your requirements.".format(
323                        name.replace("_", "-")
324                    )
325                )
326            )
327            return
328        ret = super(PytestPluginManager, self).register(plugin, name)
329        if ret:
330            self.hook.pytest_plugin_registered.call_historic(
331                kwargs=dict(plugin=plugin, manager=self)
332            )
333
334            if isinstance(plugin, types.ModuleType):
335                self.consider_module(plugin)
336        return ret
337
338    def getplugin(self, name):
339        # support deprecated naming because plugins (xdist e.g.) use it
340        return self.get_plugin(name)
341
342    def hasplugin(self, name):
343        """Return True if the plugin with the given name is registered."""
344        return bool(self.get_plugin(name))
345
346    def pytest_configure(self, config):
347        # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
348        # we should remove tryfirst/trylast as markers
349        config.addinivalue_line(
350            "markers",
351            "tryfirst: mark a hook implementation function such that the "
352            "plugin machinery will try to call it first/as early as possible.",
353        )
354        config.addinivalue_line(
355            "markers",
356            "trylast: mark a hook implementation function such that the "
357            "plugin machinery will try to call it last/as late as possible.",
358        )
359        self._configured = True
360
361    #
362    # internal API for local conftest plugin handling
363    #
364    def _set_initial_conftests(self, namespace):
365        """ load initial conftest files given a preparsed "namespace".
366            As conftest files may add their own command line options
367            which have arguments ('--my-opt somepath') we might get some
368            false positives.  All builtin and 3rd party plugins will have
369            been loaded, however, so common options will not confuse our logic
370            here.
371        """
372        current = py.path.local()
373        self._confcutdir = (
374            current.join(namespace.confcutdir, abs=True)
375            if namespace.confcutdir
376            else None
377        )
378        self._noconftest = namespace.noconftest
379        self._using_pyargs = namespace.pyargs
380        testpaths = namespace.file_or_dir
381        foundanchor = False
382        for path in testpaths:
383            path = str(path)
384            # remove node-id syntax
385            i = path.find("::")
386            if i != -1:
387                path = path[:i]
388            anchor = current.join(path, abs=1)
389            if exists(anchor):  # we found some file object
390                self._try_load_conftest(anchor)
391                foundanchor = True
392        if not foundanchor:
393            self._try_load_conftest(current)
394
395    def _try_load_conftest(self, anchor):
396        self._getconftestmodules(anchor)
397        # let's also consider test* subdirs
398        if anchor.check(dir=1):
399            for x in anchor.listdir("test*"):
400                if x.check(dir=1):
401                    self._getconftestmodules(x)
402
403    @lru_cache(maxsize=128)
404    def _getconftestmodules(self, path):
405        if self._noconftest:
406            return []
407
408        if path.isfile():
409            directory = path.dirpath()
410        else:
411            directory = path
412
413        if six.PY2:  # py2 is not using lru_cache.
414            try:
415                return self._dirpath2confmods[directory]
416            except KeyError:
417                pass
418
419        # XXX these days we may rather want to use config.rootdir
420        # and allow users to opt into looking into the rootdir parent
421        # directories instead of requiring to specify confcutdir
422        clist = []
423        for parent in directory.realpath().parts():
424            if self._confcutdir and self._confcutdir.relto(parent):
425                continue
426            conftestpath = parent.join("conftest.py")
427            if conftestpath.isfile():
428                # Use realpath to avoid loading the same conftest twice
429                # with build systems that create build directories containing
430                # symlinks to actual files.
431                mod = self._importconftest(conftestpath.realpath())
432                clist.append(mod)
433        self._dirpath2confmods[directory] = clist
434        return clist
435
436    def _rget_with_confmod(self, name, path):
437        modules = self._getconftestmodules(path)
438        for mod in reversed(modules):
439            try:
440                return mod, getattr(mod, name)
441            except AttributeError:
442                continue
443        raise KeyError(name)
444
445    def _importconftest(self, conftestpath):
446        try:
447            return self._conftestpath2mod[conftestpath]
448        except KeyError:
449            pkgpath = conftestpath.pypkgpath()
450            if pkgpath is None:
451                _ensure_removed_sysmodule(conftestpath.purebasename)
452            try:
453                mod = conftestpath.pyimport()
454                if (
455                    hasattr(mod, "pytest_plugins")
456                    and self._configured
457                    and not self._using_pyargs
458                ):
459                    from _pytest.deprecated import (
460                        PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST,
461                    )
462
463                    fail(
464                        PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST.format(
465                            conftestpath, self._confcutdir
466                        ),
467                        pytrace=False,
468                    )
469            except Exception:
470                raise ConftestImportFailure(conftestpath, sys.exc_info())
471
472            self._conftest_plugins.add(mod)
473            self._conftestpath2mod[conftestpath] = mod
474            dirpath = conftestpath.dirpath()
475            if dirpath in self._dirpath2confmods:
476                for path, mods in self._dirpath2confmods.items():
477                    if path and path.relto(dirpath) or path == dirpath:
478                        assert mod not in mods
479                        mods.append(mod)
480            self.trace("loaded conftestmodule %r" % (mod))
481            self.consider_conftest(mod)
482            return mod
483
484    #
485    # API for bootstrapping plugin loading
486    #
487    #
488
489    def consider_preparse(self, args):
490        i = 0
491        n = len(args)
492        while i < n:
493            opt = args[i]
494            i += 1
495            if isinstance(opt, six.string_types):
496                if opt == "-p":
497                    try:
498                        parg = args[i]
499                    except IndexError:
500                        return
501                    i += 1
502                elif opt.startswith("-p"):
503                    parg = opt[2:]
504                else:
505                    continue
506                self.consider_pluginarg(parg)
507
508    def consider_pluginarg(self, arg):
509        if arg.startswith("no:"):
510            name = arg[3:]
511            if name in essential_plugins:
512                raise UsageError("plugin %s cannot be disabled" % name)
513
514            # PR #4304 : remove stepwise if cacheprovider is blocked
515            if name == "cacheprovider":
516                self.set_blocked("stepwise")
517                self.set_blocked("pytest_stepwise")
518
519            self.set_blocked(name)
520            if not name.startswith("pytest_"):
521                self.set_blocked("pytest_" + name)
522        else:
523            name = arg
524            # Unblock the plugin.  None indicates that it has been blocked.
525            # There is no interface with pluggy for this.
526            if self._name2plugin.get(name, -1) is None:
527                del self._name2plugin[name]
528            if not name.startswith("pytest_"):
529                if self._name2plugin.get("pytest_" + name, -1) is None:
530                    del self._name2plugin["pytest_" + name]
531            self.import_plugin(arg, consider_entry_points=True)
532
533    def consider_conftest(self, conftestmodule):
534        self.register(conftestmodule, name=conftestmodule.__file__)
535
536    def consider_env(self):
537        self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
538
539    def consider_module(self, mod):
540        self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
541
542    def _import_plugin_specs(self, spec):
543        plugins = _get_plugin_specs_as_list(spec)
544        for import_spec in plugins:
545            self.import_plugin(import_spec)
546
547    def import_plugin(self, modname, consider_entry_points=False):
548        """
549        Imports a plugin with ``modname``. If ``consider_entry_points`` is True, entry point
550        names are also considered to find a plugin.
551        """
552        # most often modname refers to builtin modules, e.g. "pytester",
553        # "terminal" or "capture".  Those plugins are registered under their
554        # basename for historic purposes but must be imported with the
555        # _pytest prefix.
556        assert isinstance(modname, six.string_types), (
557            "module name as text required, got %r" % modname
558        )
559        modname = str(modname)
560        if self.is_blocked(modname) or self.get_plugin(modname) is not None:
561            return
562
563        importspec = "_pytest." + modname if modname in builtin_plugins else modname
564        self.rewrite_hook.mark_rewrite(importspec)
565
566        if consider_entry_points:
567            loaded = self.load_setuptools_entrypoints("pytest11", name=modname)
568            if loaded:
569                return
570
571        try:
572            __import__(importspec)
573        except ImportError as e:
574            new_exc_message = 'Error importing plugin "%s": %s' % (
575                modname,
576                safe_str(e.args[0]),
577            )
578            new_exc = ImportError(new_exc_message)
579            tb = sys.exc_info()[2]
580
581            six.reraise(ImportError, new_exc, tb)
582
583        except Skipped as e:
584            from _pytest.warnings import _issue_warning_captured
585
586            _issue_warning_captured(
587                PytestConfigWarning("skipped plugin %r: %s" % (modname, e.msg)),
588                self.hook,
589                stacklevel=1,
590            )
591        else:
592            mod = sys.modules[importspec]
593            self.register(mod, modname)
594
595
596def _get_plugin_specs_as_list(specs):
597    """
598    Parses a list of "plugin specs" and returns a list of plugin names.
599
600    Plugin specs can be given as a list of strings separated by "," or already as a list/tuple in
601    which case it is returned as a list. Specs can also be `None` in which case an
602    empty list is returned.
603    """
604    if specs is not None and not isinstance(specs, types.ModuleType):
605        if isinstance(specs, six.string_types):
606            specs = specs.split(",") if specs else []
607        if not isinstance(specs, (list, tuple)):
608            raise UsageError(
609                "Plugin specs must be a ','-separated string or a "
610                "list/tuple of strings for plugin names. Given: %r" % specs
611            )
612        return list(specs)
613    return []
614
615
616def _ensure_removed_sysmodule(modname):
617    try:
618        del sys.modules[modname]
619    except KeyError:
620        pass
621
622
623class Notset(object):
624    def __repr__(self):
625        return "<NOTSET>"
626
627
628notset = Notset()
629
630
631def _iter_rewritable_modules(package_files):
632    """
633    Given an iterable of file names in a source distribution, return the "names" that should
634    be marked for assertion rewrite (for example the package "pytest_mock/__init__.py" should
635    be added as "pytest_mock" in the assertion rewrite mechanism.
636
637    This function has to deal with dist-info based distributions and egg based distributions
638    (which are still very much in use for "editable" installs).
639
640    Here are the file names as seen in a dist-info based distribution:
641
642        pytest_mock/__init__.py
643        pytest_mock/_version.py
644        pytest_mock/plugin.py
645        pytest_mock.egg-info/PKG-INFO
646
647    Here are the file names as seen in an egg based distribution:
648
649        src/pytest_mock/__init__.py
650        src/pytest_mock/_version.py
651        src/pytest_mock/plugin.py
652        src/pytest_mock.egg-info/PKG-INFO
653        LICENSE
654        setup.py
655
656    We have to take in account those two distribution flavors in order to determine which
657    names should be considered for assertion rewriting.
658
659    More information:
660        https://github.com/pytest-dev/pytest-mock/issues/167
661    """
662    package_files = list(package_files)
663    seen_some = False
664    for fn in package_files:
665        is_simple_module = "/" not in fn and fn.endswith(".py")
666        is_package = fn.count("/") == 1 and fn.endswith("__init__.py")
667        if is_simple_module:
668            module_name, _ = os.path.splitext(fn)
669            # we ignore "setup.py" at the root of the distribution
670            if module_name != "setup":
671                seen_some = True
672                yield module_name
673        elif is_package:
674            package_name = os.path.dirname(fn)
675            seen_some = True
676            yield package_name
677
678    if not seen_some:
679        # at this point we did not find any packages or modules suitable for assertion
680        # rewriting, so we try again by stripping the first path component (to account for
681        # "src" based source trees for example)
682        # this approach lets us have the common case continue to be fast, as egg-distributions
683        # are rarer
684        new_package_files = []
685        for fn in package_files:
686            parts = fn.split("/")
687            new_fn = "/".join(parts[1:])
688            if new_fn:
689                new_package_files.append(new_fn)
690        if new_package_files:
691            for _module in _iter_rewritable_modules(new_package_files):
692                yield _module
693
694
695class Config(object):
696    """
697    Access to configuration values, pluginmanager and plugin hooks.
698
699    :ivar PytestPluginManager pluginmanager: the plugin manager handles plugin registration and hook invocation.
700
701    :ivar argparse.Namespace option: access to command line option as attributes.
702
703    :ivar InvocationParams invocation_params:
704
705        Object containing the parameters regarding the ``pytest.main``
706        invocation.
707        Contains the followinig read-only attributes:
708        * ``args``: list of command-line arguments as passed to ``pytest.main()``.
709        * ``plugins``: list of extra plugins, might be None
710        * ``dir``: directory where ``pytest.main()`` was invoked from.
711    """
712
713    @attr.s(frozen=True)
714    class InvocationParams(object):
715        """Holds parameters passed during ``pytest.main()``
716
717        .. note::
718
719            Currently the environment variable PYTEST_ADDOPTS is also handled by
720            pytest implicitly, not being part of the invocation.
721
722            Plugins accessing ``InvocationParams`` must be aware of that.
723        """
724
725        args = attr.ib()
726        plugins = attr.ib()
727        dir = attr.ib()
728
729    def __init__(self, pluginmanager, invocation_params=None, *args):
730        from .argparsing import Parser, FILE_OR_DIR
731
732        if invocation_params is None:
733            invocation_params = self.InvocationParams(
734                args=(), plugins=None, dir=Path().resolve()
735            )
736
737        #: access to command line option as attributes.
738        #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
739        self.option = argparse.Namespace()
740
741        self.invocation_params = invocation_params
742
743        _a = FILE_OR_DIR
744        self._parser = Parser(
745            usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
746            processopt=self._processopt,
747        )
748        #: a pluginmanager instance
749        self.pluginmanager = pluginmanager
750        self.trace = self.pluginmanager.trace.root.get("config")
751        self.hook = self.pluginmanager.hook
752        self._inicache = {}
753        self._override_ini = ()
754        self._opt2dest = {}
755        self._cleanup = []
756        self.pluginmanager.register(self, "pytestconfig")
757        self._configured = False
758        self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
759
760    @property
761    def invocation_dir(self):
762        """Backward compatibility"""
763        return py.path.local(str(self.invocation_params.dir))
764
765    def add_cleanup(self, func):
766        """ Add a function to be called when the config object gets out of
767        use (usually coninciding with pytest_unconfigure)."""
768        self._cleanup.append(func)
769
770    def _do_configure(self):
771        assert not self._configured
772        self._configured = True
773        self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
774
775    def _ensure_unconfigure(self):
776        if self._configured:
777            self._configured = False
778            self.hook.pytest_unconfigure(config=self)
779            self.hook.pytest_configure._call_history = []
780        while self._cleanup:
781            fin = self._cleanup.pop()
782            fin()
783
784    def get_terminal_writer(self):
785        return self.pluginmanager.get_plugin("terminalreporter")._tw
786
787    def pytest_cmdline_parse(self, pluginmanager, args):
788        try:
789            self.parse(args)
790        except UsageError:
791
792            # Handle --version and --help here in a minimal fashion.
793            # This gets done via helpconfig normally, but its
794            # pytest_cmdline_main is not called in case of errors.
795            if getattr(self.option, "version", False) or "--version" in args:
796                from _pytest.helpconfig import showversion
797
798                showversion(self)
799            elif (
800                getattr(self.option, "help", False) or "--help" in args or "-h" in args
801            ):
802                self._parser._getparser().print_help()
803                sys.stdout.write(
804                    "\nNOTE: displaying only minimal help due to UsageError.\n\n"
805                )
806
807            raise
808
809        return self
810
811    def notify_exception(self, excinfo, option=None):
812        if option and getattr(option, "fulltrace", False):
813            style = "long"
814        else:
815            style = "native"
816        excrepr = excinfo.getrepr(
817            funcargs=True, showlocals=getattr(option, "showlocals", False), style=style
818        )
819        res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo)
820        if not any(res):
821            for line in str(excrepr).split("\n"):
822                sys.stderr.write("INTERNALERROR> %s\n" % line)
823                sys.stderr.flush()
824
825    def cwd_relative_nodeid(self, nodeid):
826        # nodeid's are relative to the rootpath, compute relative to cwd
827        if self.invocation_dir != self.rootdir:
828            fullpath = self.rootdir.join(nodeid)
829            nodeid = self.invocation_dir.bestrelpath(fullpath)
830        return nodeid
831
832    @classmethod
833    def fromdictargs(cls, option_dict, args):
834        """ constructor useable for subprocesses. """
835        config = get_config(args)
836        config.option.__dict__.update(option_dict)
837        config.parse(args, addopts=False)
838        for x in config.option.plugins:
839            config.pluginmanager.consider_pluginarg(x)
840        return config
841
842    def _processopt(self, opt):
843        for name in opt._short_opts + opt._long_opts:
844            self._opt2dest[name] = opt.dest
845
846        if hasattr(opt, "default") and opt.dest:
847            if not hasattr(self.option, opt.dest):
848                setattr(self.option, opt.dest, opt.default)
849
850    @hookimpl(trylast=True)
851    def pytest_load_initial_conftests(self, early_config):
852        self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
853
854    def _initini(self, args):
855        ns, unknown_args = self._parser.parse_known_and_unknown_args(
856            args, namespace=copy.copy(self.option)
857        )
858        r = determine_setup(
859            ns.inifilename,
860            ns.file_or_dir + unknown_args,
861            rootdir_cmd_arg=ns.rootdir or None,
862            config=self,
863        )
864        self.rootdir, self.inifile, self.inicfg = r
865        self._parser.extra_info["rootdir"] = self.rootdir
866        self._parser.extra_info["inifile"] = self.inifile
867        self._parser.addini("addopts", "extra command line options", "args")
868        self._parser.addini("minversion", "minimally required pytest version")
869        self._override_ini = ns.override_ini or ()
870
871    def _consider_importhook(self, args):
872        """Install the PEP 302 import hook if using assertion rewriting.
873
874        Needs to parse the --assert=<mode> option from the commandline
875        and find all the installed plugins to mark them for rewriting
876        by the importhook.
877        """
878        ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
879        mode = getattr(ns, "assertmode", "plain")
880        if mode == "rewrite":
881            try:
882                hook = _pytest.assertion.install_importhook(self)
883            except SystemError:
884                mode = "plain"
885            else:
886                self._mark_plugins_for_rewrite(hook)
887        _warn_about_missing_assertion(mode)
888
889    def _mark_plugins_for_rewrite(self, hook):
890        """
891        Given an importhook, mark for rewrite any top-level
892        modules or packages in the distribution package for
893        all pytest plugins.
894        """
895        self.pluginmanager.rewrite_hook = hook
896
897        if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
898            # We don't autoload from setuptools entry points, no need to continue.
899            return
900
901        package_files = (
902            str(file)
903            for dist in importlib_metadata.distributions()
904            if any(ep.group == "pytest11" for ep in dist.entry_points)
905            for file in dist.files or []
906        )
907
908        for name in _iter_rewritable_modules(package_files):
909            hook.mark_rewrite(name)
910
911    def _validate_args(self, args, via):
912        """Validate known args."""
913        self._parser._config_source_hint = via
914        try:
915            self._parser.parse_known_and_unknown_args(
916                args, namespace=copy.copy(self.option)
917            )
918        finally:
919            del self._parser._config_source_hint
920
921        return args
922
923    def _preparse(self, args, addopts=True):
924        if addopts:
925            env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
926            if len(env_addopts):
927                args[:] = (
928                    self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
929                    + args
930                )
931        self._initini(args)
932        if addopts:
933            args[:] = (
934                self._validate_args(self.getini("addopts"), "via addopts config") + args
935            )
936
937        self._checkversion()
938        self._consider_importhook(args)
939        self.pluginmanager.consider_preparse(args)
940        if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
941            # Don't autoload from setuptools entry point. Only explicitly specified
942            # plugins are going to be loaded.
943            self.pluginmanager.load_setuptools_entrypoints("pytest11")
944        self.pluginmanager.consider_env()
945        self.known_args_namespace = ns = self._parser.parse_known_args(
946            args, namespace=copy.copy(self.option)
947        )
948        if self.known_args_namespace.confcutdir is None and self.inifile:
949            confcutdir = py.path.local(self.inifile).dirname
950            self.known_args_namespace.confcutdir = confcutdir
951        try:
952            self.hook.pytest_load_initial_conftests(
953                early_config=self, args=args, parser=self._parser
954            )
955        except ConftestImportFailure:
956            e = sys.exc_info()[1]
957            if ns.help or ns.version:
958                # we don't want to prevent --help/--version to work
959                # so just let is pass and print a warning at the end
960                from _pytest.warnings import _issue_warning_captured
961
962                _issue_warning_captured(
963                    PytestConfigWarning(
964                        "could not load initial conftests: {}".format(e.path)
965                    ),
966                    self.hook,
967                    stacklevel=2,
968                )
969            else:
970                raise
971
972    def _checkversion(self):
973        import pytest
974
975        minver = self.inicfg.get("minversion", None)
976        if minver:
977            if Version(minver) > Version(pytest.__version__):
978                raise pytest.UsageError(
979                    "%s:%d: requires pytest-%s, actual pytest-%s'"
980                    % (
981                        self.inicfg.config.path,
982                        self.inicfg.lineof("minversion"),
983                        minver,
984                        pytest.__version__,
985                    )
986                )
987
988    def parse(self, args, addopts=True):
989        # parse given cmdline arguments into this config object.
990        assert not hasattr(
991            self, "args"
992        ), "can only parse cmdline args at most once per Config object"
993        self._origargs = args
994        self.hook.pytest_addhooks.call_historic(
995            kwargs=dict(pluginmanager=self.pluginmanager)
996        )
997        self._preparse(args, addopts=addopts)
998        # XXX deprecated hook:
999        self.hook.pytest_cmdline_preparse(config=self, args=args)
1000        self._parser.after_preparse = True
1001        try:
1002            args = self._parser.parse_setoption(
1003                args, self.option, namespace=self.option
1004            )
1005            if not args:
1006                if self.invocation_dir == self.rootdir:
1007                    args = self.getini("testpaths")
1008                if not args:
1009                    args = [str(self.invocation_dir)]
1010            self.args = args
1011        except PrintHelp:
1012            pass
1013
1014    def addinivalue_line(self, name, line):
1015        """ add a line to an ini-file option. The option must have been
1016        declared but might not yet be set in which case the line becomes the
1017        the first line in its value. """
1018        x = self.getini(name)
1019        assert isinstance(x, list)
1020        x.append(line)  # modifies the cached list inline
1021
1022    def getini(self, name):
1023        """ return configuration value from an :ref:`ini file <inifiles>`. If the
1024        specified name hasn't been registered through a prior
1025        :py:func:`parser.addini <_pytest.config.Parser.addini>`
1026        call (usually from a plugin), a ValueError is raised. """
1027        try:
1028            return self._inicache[name]
1029        except KeyError:
1030            self._inicache[name] = val = self._getini(name)
1031            return val
1032
1033    def _getini(self, name):
1034        try:
1035            description, type, default = self._parser._inidict[name]
1036        except KeyError:
1037            raise ValueError("unknown configuration value: %r" % (name,))
1038        value = self._get_override_ini_value(name)
1039        if value is None:
1040            try:
1041                value = self.inicfg[name]
1042            except KeyError:
1043                if default is not None:
1044                    return default
1045                if type is None:
1046                    return ""
1047                return []
1048        if type == "pathlist":
1049            dp = py.path.local(self.inicfg.config.path).dirpath()
1050            values = []
1051            for relpath in shlex.split(value):
1052                values.append(dp.join(relpath, abs=True))
1053            return values
1054        elif type == "args":
1055            return shlex.split(value)
1056        elif type == "linelist":
1057            return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
1058        elif type == "bool":
1059            return bool(_strtobool(value.strip()))
1060        else:
1061            assert type is None
1062            return value
1063
1064    def _getconftest_pathlist(self, name, path):
1065        try:
1066            mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
1067        except KeyError:
1068            return None
1069        modpath = py.path.local(mod.__file__).dirpath()
1070        values = []
1071        for relroot in relroots:
1072            if not isinstance(relroot, py.path.local):
1073                relroot = relroot.replace("/", py.path.local.sep)
1074                relroot = modpath.join(relroot, abs=True)
1075            values.append(relroot)
1076        return values
1077
1078    def _get_override_ini_value(self, name):
1079        value = None
1080        # override_ini is a list of "ini=value" options
1081        # always use the last item if multiple values are set for same ini-name,
1082        # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2
1083        for ini_config in self._override_ini:
1084            try:
1085                key, user_ini_value = ini_config.split("=", 1)
1086            except ValueError:
1087                raise UsageError("-o/--override-ini expects option=value style.")
1088            else:
1089                if key == name:
1090                    value = user_ini_value
1091        return value
1092
1093    def getoption(self, name, default=notset, skip=False):
1094        """ return command line option value.
1095
1096        :arg name: name of the option.  You may also specify
1097            the literal ``--OPT`` option instead of the "dest" option name.
1098        :arg default: default value if no option of that name exists.
1099        :arg skip: if True raise pytest.skip if option does not exists
1100            or has a None value.
1101        """
1102        name = self._opt2dest.get(name, name)
1103        try:
1104            val = getattr(self.option, name)
1105            if val is None and skip:
1106                raise AttributeError(name)
1107            return val
1108        except AttributeError:
1109            if default is not notset:
1110                return default
1111            if skip:
1112                import pytest
1113
1114                pytest.skip("no %r option found" % (name,))
1115            raise ValueError("no option named %r" % (name,))
1116
1117    def getvalue(self, name, path=None):
1118        """ (deprecated, use getoption()) """
1119        return self.getoption(name)
1120
1121    def getvalueorskip(self, name, path=None):
1122        """ (deprecated, use getoption(skip=True)) """
1123        return self.getoption(name, skip=True)
1124
1125
1126def _assertion_supported():
1127    try:
1128        assert False
1129    except AssertionError:
1130        return True
1131    else:
1132        return False
1133
1134
1135def _warn_about_missing_assertion(mode):
1136    if not _assertion_supported():
1137        if mode == "plain":
1138            sys.stderr.write(
1139                "WARNING: ASSERTIONS ARE NOT EXECUTED"
1140                " and FAILING TESTS WILL PASS.  Are you"
1141                " using python -O?"
1142            )
1143        else:
1144            sys.stderr.write(
1145                "WARNING: assertions not in test modules or"
1146                " plugins will be ignored"
1147                " because assert statements are not executed "
1148                "by the underlying Python interpreter "
1149                "(are you using python -O?)\n"
1150            )
1151
1152
1153def setns(obj, dic):
1154    import pytest
1155
1156    for name, value in dic.items():
1157        if isinstance(value, dict):
1158            mod = getattr(obj, name, None)
1159            if mod is None:
1160                modname = "pytest.%s" % name
1161                mod = types.ModuleType(modname)
1162                sys.modules[modname] = mod
1163                mod.__all__ = []
1164                setattr(obj, name, mod)
1165            obj.__all__.append(name)
1166            setns(mod, value)
1167        else:
1168            setattr(obj, name, value)
1169            obj.__all__.append(name)
1170            # if obj != pytest:
1171            #    pytest.__all__.append(name)
1172            setattr(pytest, name, value)
1173
1174
1175def create_terminal_writer(config, *args, **kwargs):
1176    """Create a TerminalWriter instance configured according to the options
1177    in the config object. Every code which requires a TerminalWriter object
1178    and has access to a config object should use this function.
1179    """
1180    tw = py.io.TerminalWriter(*args, **kwargs)
1181    if config.option.color == "yes":
1182        tw.hasmarkup = True
1183    if config.option.color == "no":
1184        tw.hasmarkup = False
1185    return tw
1186
1187
1188def _strtobool(val):
1189    """Convert a string representation of truth to true (1) or false (0).
1190
1191    True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
1192    are 'n', 'no', 'f', 'false', 'off', and '0'.  Raises ValueError if
1193    'val' is anything else.
1194
1195    .. note:: copied from distutils.util
1196    """
1197    val = val.lower()
1198    if val in ("y", "yes", "t", "true", "on", "1"):
1199        return 1
1200    elif val in ("n", "no", "f", "false", "off", "0"):
1201        return 0
1202    else:
1203        raise ValueError("invalid truth value %r" % (val,))
1204