123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203 |
- # -*- coding: utf-8 -*-
- """ command line options, ini-file and conftest.py processing. """
- from __future__ import absolute_import
- from __future__ import division
- from __future__ import print_function
- import argparse
- import copy
- import inspect
- import os
- import shlex
- import sys
- import types
- import warnings
- import attr
- import py
- import six
- from packaging.version import Version
- from pluggy import HookimplMarker
- from pluggy import HookspecMarker
- from pluggy import PluginManager
- import _pytest._code
- import _pytest.assertion
- import _pytest.hookspec # the extension point definitions
- from .exceptions import PrintHelp
- from .exceptions import UsageError
- from .findpaths import determine_setup
- from .findpaths import exists
- from _pytest import deprecated
- from _pytest._code import ExceptionInfo
- from _pytest._code import filter_traceback
- from _pytest.compat import importlib_metadata
- from _pytest.compat import lru_cache
- from _pytest.compat import safe_str
- from _pytest.outcomes import fail
- from _pytest.outcomes import Skipped
- from _pytest.pathlib import Path
- from _pytest.warning_types import PytestConfigWarning
- hookimpl = HookimplMarker("pytest")
- hookspec = HookspecMarker("pytest")
- class ConftestImportFailure(Exception):
- def __init__(self, path, excinfo):
- Exception.__init__(self, path, excinfo)
- self.path = path
- self.excinfo = excinfo
- def main(args=None, plugins=None):
- """ return exit code, after performing an in-process test run.
- :arg args: list of command line arguments.
- :arg plugins: list of plugin objects to be auto-registered during
- initialization.
- """
- from _pytest.main import EXIT_USAGEERROR
- try:
- try:
- config = _prepareconfig(args, plugins)
- except ConftestImportFailure as e:
- exc_info = ExceptionInfo(e.excinfo)
- tw = py.io.TerminalWriter(sys.stderr)
- tw.line(
- "ImportError while loading conftest '{e.path}'.".format(e=e), red=True
- )
- exc_info.traceback = exc_info.traceback.filter(filter_traceback)
- exc_repr = (
- exc_info.getrepr(style="short", chain=False)
- if exc_info.traceback
- else exc_info.exconly()
- )
- formatted_tb = safe_str(exc_repr)
- for line in formatted_tb.splitlines():
- tw.line(line.rstrip(), red=True)
- return 4
- else:
- try:
- return config.hook.pytest_cmdline_main(config=config)
- finally:
- config._ensure_unconfigure()
- except UsageError as e:
- tw = py.io.TerminalWriter(sys.stderr)
- for msg in e.args:
- tw.line("ERROR: {}\n".format(msg), red=True)
- return EXIT_USAGEERROR
- class cmdline(object): # compatibility namespace
- main = staticmethod(main)
- def filename_arg(path, optname):
- """ Argparse type validator for filename arguments.
- :path: path of filename
- :optname: name of the option
- """
- if os.path.isdir(path):
- raise UsageError("{} must be a filename, given: {}".format(optname, path))
- return path
- def directory_arg(path, optname):
- """Argparse type validator for directory arguments.
- :path: path of directory
- :optname: name of the option
- """
- if not os.path.isdir(path):
- raise UsageError("{} must be a directory, given: {}".format(optname, path))
- return path
- # Plugins that cannot be disabled via "-p no:X" currently.
- essential_plugins = (
- "mark",
- "main",
- "runner",
- "fixtures",
- "helpconfig", # Provides -p.
- )
- default_plugins = essential_plugins + (
- "python",
- "terminal",
- "debugging",
- "unittest",
- "capture",
- "skipping",
- "tmpdir",
- "monkeypatch",
- "recwarn",
- "pastebin",
- "nose",
- "assertion",
- "junitxml",
- "resultlog",
- "doctest",
- "cacheprovider",
- "freeze_support",
- "setuponly",
- "setupplan",
- "stepwise",
- "warnings",
- "logging",
- "reports",
- )
- builtin_plugins = set(default_plugins)
- builtin_plugins.add("pytester")
- def get_config(args=None, plugins=None):
- # subsequent calls to main will create a fresh instance
- pluginmanager = PytestPluginManager()
- config = Config(
- pluginmanager,
- invocation_params=Config.InvocationParams(
- args=args, plugins=plugins, dir=Path().resolve()
- ),
- )
- if args is not None:
- # Handle any "-p no:plugin" args.
- pluginmanager.consider_preparse(args)
- for spec in default_plugins:
- pluginmanager.import_plugin(spec)
- return config
- def get_plugin_manager():
- """
- Obtain a new instance of the
- :py:class:`_pytest.config.PytestPluginManager`, with default plugins
- already loaded.
- This function can be used by integration with other tools, like hooking
- into pytest to run tests into an IDE.
- """
- return get_config().pluginmanager
- def _prepareconfig(args=None, plugins=None):
- warning = None
- if args is None:
- args = sys.argv[1:]
- elif isinstance(args, py.path.local):
- args = [str(args)]
- elif not isinstance(args, (tuple, list)):
- msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})"
- raise TypeError(msg.format(args, type(args)))
- config = get_config(args, plugins)
- pluginmanager = config.pluginmanager
- try:
- if plugins:
- for plugin in plugins:
- if isinstance(plugin, six.string_types):
- pluginmanager.consider_pluginarg(plugin)
- else:
- pluginmanager.register(plugin)
- if warning:
- from _pytest.warnings import _issue_warning_captured
- _issue_warning_captured(warning, hook=config.hook, stacklevel=4)
- return pluginmanager.hook.pytest_cmdline_parse(
- pluginmanager=pluginmanager, args=args
- )
- except BaseException:
- config._ensure_unconfigure()
- raise
- class PytestPluginManager(PluginManager):
- """
- Overwrites :py:class:`pluggy.PluginManager <pluggy.PluginManager>` to add pytest-specific
- functionality:
- * loading plugins from the command line, ``PYTEST_PLUGINS`` env variable and
- ``pytest_plugins`` global variables found in plugins being loaded;
- * ``conftest.py`` loading during start-up;
- """
- def __init__(self):
- super(PytestPluginManager, self).__init__("pytest")
- self._conftest_plugins = set()
- # state related to local conftest plugins
- self._dirpath2confmods = {}
- self._conftestpath2mod = {}
- self._confcutdir = None
- self._noconftest = False
- self._duplicatepaths = set()
- self.add_hookspecs(_pytest.hookspec)
- self.register(self)
- if os.environ.get("PYTEST_DEBUG"):
- err = sys.stderr
- encoding = getattr(err, "encoding", "utf8")
- try:
- err = py.io.dupfile(err, encoding=encoding)
- except Exception:
- pass
- self.trace.root.setwriter(err.write)
- self.enable_tracing()
- # Config._consider_importhook will set a real object if required.
- self.rewrite_hook = _pytest.assertion.DummyRewriteHook()
- # Used to know when we are importing conftests after the pytest_configure stage
- self._configured = False
- def addhooks(self, module_or_class):
- """
- .. deprecated:: 2.8
- Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>`
- instead.
- """
- warnings.warn(deprecated.PLUGIN_MANAGER_ADDHOOKS, stacklevel=2)
- return self.add_hookspecs(module_or_class)
- def parse_hookimpl_opts(self, plugin, name):
- # pytest hooks are always prefixed with pytest_
- # so we avoid accessing possibly non-readable attributes
- # (see issue #1073)
- if not name.startswith("pytest_"):
- return
- # ignore names which can not be hooks
- if name == "pytest_plugins":
- return
- method = getattr(plugin, name)
- opts = super(PytestPluginManager, self).parse_hookimpl_opts(plugin, name)
- # consider only actual functions for hooks (#3775)
- if not inspect.isroutine(method):
- return
- # collect unmarked hooks as long as they have the `pytest_' prefix
- if opts is None and name.startswith("pytest_"):
- opts = {}
- if opts is not None:
- # TODO: DeprecationWarning, people should use hookimpl
- # https://github.com/pytest-dev/pytest/issues/4562
- known_marks = {m.name for m in getattr(method, "pytestmark", [])}
- for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
- opts.setdefault(name, hasattr(method, name) or name in known_marks)
- return opts
- def parse_hookspec_opts(self, module_or_class, name):
- opts = super(PytestPluginManager, self).parse_hookspec_opts(
- module_or_class, name
- )
- if opts is None:
- method = getattr(module_or_class, name)
- if name.startswith("pytest_"):
- # todo: deprecate hookspec hacks
- # https://github.com/pytest-dev/pytest/issues/4562
- known_marks = {m.name for m in getattr(method, "pytestmark", [])}
- opts = {
- "firstresult": hasattr(method, "firstresult")
- or "firstresult" in known_marks,
- "historic": hasattr(method, "historic")
- or "historic" in known_marks,
- }
- return opts
- def register(self, plugin, name=None):
- if name in ["pytest_catchlog", "pytest_capturelog"]:
- warnings.warn(
- PytestConfigWarning(
- "{} plugin has been merged into the core, "
- "please remove it from your requirements.".format(
- name.replace("_", "-")
- )
- )
- )
- return
- ret = super(PytestPluginManager, self).register(plugin, name)
- if ret:
- self.hook.pytest_plugin_registered.call_historic(
- kwargs=dict(plugin=plugin, manager=self)
- )
- if isinstance(plugin, types.ModuleType):
- self.consider_module(plugin)
- return ret
- def getplugin(self, name):
- # support deprecated naming because plugins (xdist e.g.) use it
- return self.get_plugin(name)
- def hasplugin(self, name):
- """Return True if the plugin with the given name is registered."""
- return bool(self.get_plugin(name))
- def pytest_configure(self, config):
- # XXX now that the pluginmanager exposes hookimpl(tryfirst...)
- # we should remove tryfirst/trylast as markers
- config.addinivalue_line(
- "markers",
- "tryfirst: mark a hook implementation function such that the "
- "plugin machinery will try to call it first/as early as possible.",
- )
- config.addinivalue_line(
- "markers",
- "trylast: mark a hook implementation function such that the "
- "plugin machinery will try to call it last/as late as possible.",
- )
- self._configured = True
- #
- # internal API for local conftest plugin handling
- #
- def _set_initial_conftests(self, namespace):
- """ load initial conftest files given a preparsed "namespace".
- As conftest files may add their own command line options
- which have arguments ('--my-opt somepath') we might get some
- false positives. All builtin and 3rd party plugins will have
- been loaded, however, so common options will not confuse our logic
- here.
- """
- current = py.path.local()
- self._confcutdir = (
- current.join(namespace.confcutdir, abs=True)
- if namespace.confcutdir
- else None
- )
- self._noconftest = namespace.noconftest
- self._using_pyargs = namespace.pyargs
- testpaths = namespace.file_or_dir
- foundanchor = False
- for path in testpaths:
- path = str(path)
- # remove node-id syntax
- i = path.find("::")
- if i != -1:
- path = path[:i]
- anchor = current.join(path, abs=1)
- if exists(anchor): # we found some file object
- self._try_load_conftest(anchor)
- foundanchor = True
- if not foundanchor:
- self._try_load_conftest(current)
- def _try_load_conftest(self, anchor):
- self._getconftestmodules(anchor)
- # let's also consider test* subdirs
- if anchor.check(dir=1):
- for x in anchor.listdir("test*"):
- if x.check(dir=1):
- self._getconftestmodules(x)
- @lru_cache(maxsize=128)
- def _getconftestmodules(self, path):
- if self._noconftest:
- return []
- if path.isfile():
- directory = path.dirpath()
- else:
- directory = path
- if six.PY2: # py2 is not using lru_cache.
- try:
- return self._dirpath2confmods[directory]
- except KeyError:
- pass
- # XXX these days we may rather want to use config.rootdir
- # and allow users to opt into looking into the rootdir parent
- # directories instead of requiring to specify confcutdir
- clist = []
- for parent in directory.realpath().parts():
- if self._confcutdir and self._confcutdir.relto(parent):
- continue
- conftestpath = parent.join("conftest.py")
- if conftestpath.isfile():
- # Use realpath to avoid loading the same conftest twice
- # with build systems that create build directories containing
- # symlinks to actual files.
- mod = self._importconftest(conftestpath.realpath())
- clist.append(mod)
- self._dirpath2confmods[directory] = clist
- return clist
- def _rget_with_confmod(self, name, path):
- modules = self._getconftestmodules(path)
- for mod in reversed(modules):
- try:
- return mod, getattr(mod, name)
- except AttributeError:
- continue
- raise KeyError(name)
- def _importconftest(self, conftestpath):
- try:
- return self._conftestpath2mod[conftestpath]
- except KeyError:
- pkgpath = conftestpath.pypkgpath()
- if pkgpath is None:
- _ensure_removed_sysmodule(conftestpath.purebasename)
- try:
- mod = conftestpath.pyimport()
- if (
- hasattr(mod, "pytest_plugins")
- and self._configured
- and not self._using_pyargs
- ):
- from _pytest.deprecated import (
- PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST,
- )
- fail(
- PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST.format(
- conftestpath, self._confcutdir
- ),
- pytrace=False,
- )
- except Exception:
- raise ConftestImportFailure(conftestpath, sys.exc_info())
- self._conftest_plugins.add(mod)
- self._conftestpath2mod[conftestpath] = mod
- dirpath = conftestpath.dirpath()
- if dirpath in self._dirpath2confmods:
- for path, mods in self._dirpath2confmods.items():
- if path and path.relto(dirpath) or path == dirpath:
- assert mod not in mods
- mods.append(mod)
- self.trace("loaded conftestmodule %r" % (mod))
- self.consider_conftest(mod)
- return mod
- #
- # API for bootstrapping plugin loading
- #
- #
- def consider_preparse(self, args):
- i = 0
- n = len(args)
- while i < n:
- opt = args[i]
- i += 1
- if isinstance(opt, six.string_types):
- if opt == "-p":
- try:
- parg = args[i]
- except IndexError:
- return
- i += 1
- elif opt.startswith("-p"):
- parg = opt[2:]
- else:
- continue
- self.consider_pluginarg(parg)
- def consider_pluginarg(self, arg):
- if arg.startswith("no:"):
- name = arg[3:]
- if name in essential_plugins:
- raise UsageError("plugin %s cannot be disabled" % name)
- # PR #4304 : remove stepwise if cacheprovider is blocked
- if name == "cacheprovider":
- self.set_blocked("stepwise")
- self.set_blocked("pytest_stepwise")
- self.set_blocked(name)
- if not name.startswith("pytest_"):
- self.set_blocked("pytest_" + name)
- else:
- name = arg
- # Unblock the plugin. None indicates that it has been blocked.
- # There is no interface with pluggy for this.
- if self._name2plugin.get(name, -1) is None:
- del self._name2plugin[name]
- if not name.startswith("pytest_"):
- if self._name2plugin.get("pytest_" + name, -1) is None:
- del self._name2plugin["pytest_" + name]
- self.import_plugin(arg, consider_entry_points=True)
- def consider_conftest(self, conftestmodule):
- self.register(conftestmodule, name=conftestmodule.__file__)
- def consider_env(self):
- self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
- def consider_module(self, mod):
- self._import_plugin_specs(getattr(mod, "pytest_plugins", []))
- def _import_plugin_specs(self, spec):
- plugins = _get_plugin_specs_as_list(spec)
- for import_spec in plugins:
- self.import_plugin(import_spec)
- def import_plugin(self, modname, consider_entry_points=False):
- """
- Imports a plugin with ``modname``. If ``consider_entry_points`` is True, entry point
- names are also considered to find a plugin.
- """
- # most often modname refers to builtin modules, e.g. "pytester",
- # "terminal" or "capture". Those plugins are registered under their
- # basename for historic purposes but must be imported with the
- # _pytest prefix.
- assert isinstance(modname, six.string_types), (
- "module name as text required, got %r" % modname
- )
- modname = str(modname)
- if self.is_blocked(modname) or self.get_plugin(modname) is not None:
- return
- importspec = "_pytest." + modname if modname in builtin_plugins else modname
- self.rewrite_hook.mark_rewrite(importspec)
- if consider_entry_points:
- loaded = self.load_setuptools_entrypoints("pytest11", name=modname)
- if loaded:
- return
- try:
- __import__(importspec)
- except ImportError as e:
- new_exc_message = 'Error importing plugin "%s": %s' % (
- modname,
- safe_str(e.args[0]),
- )
- new_exc = ImportError(new_exc_message)
- tb = sys.exc_info()[2]
- six.reraise(ImportError, new_exc, tb)
- except Skipped as e:
- from _pytest.warnings import _issue_warning_captured
- _issue_warning_captured(
- PytestConfigWarning("skipped plugin %r: %s" % (modname, e.msg)),
- self.hook,
- stacklevel=1,
- )
- else:
- mod = sys.modules[importspec]
- self.register(mod, modname)
- def _get_plugin_specs_as_list(specs):
- """
- Parses a list of "plugin specs" and returns a list of plugin names.
- Plugin specs can be given as a list of strings separated by "," or already as a list/tuple in
- which case it is returned as a list. Specs can also be `None` in which case an
- empty list is returned.
- """
- if specs is not None and not isinstance(specs, types.ModuleType):
- if isinstance(specs, six.string_types):
- specs = specs.split(",") if specs else []
- if not isinstance(specs, (list, tuple)):
- raise UsageError(
- "Plugin specs must be a ','-separated string or a "
- "list/tuple of strings for plugin names. Given: %r" % specs
- )
- return list(specs)
- return []
- def _ensure_removed_sysmodule(modname):
- try:
- del sys.modules[modname]
- except KeyError:
- pass
- class Notset(object):
- def __repr__(self):
- return "<NOTSET>"
- notset = Notset()
- def _iter_rewritable_modules(package_files):
- """
- Given an iterable of file names in a source distribution, return the "names" that should
- be marked for assertion rewrite (for example the package "pytest_mock/__init__.py" should
- be added as "pytest_mock" in the assertion rewrite mechanism.
- This function has to deal with dist-info based distributions and egg based distributions
- (which are still very much in use for "editable" installs).
- Here are the file names as seen in a dist-info based distribution:
- pytest_mock/__init__.py
- pytest_mock/_version.py
- pytest_mock/plugin.py
- pytest_mock.egg-info/PKG-INFO
- Here are the file names as seen in an egg based distribution:
- src/pytest_mock/__init__.py
- src/pytest_mock/_version.py
- src/pytest_mock/plugin.py
- src/pytest_mock.egg-info/PKG-INFO
- LICENSE
- setup.py
- We have to take in account those two distribution flavors in order to determine which
- names should be considered for assertion rewriting.
- More information:
- https://github.com/pytest-dev/pytest-mock/issues/167
- """
- package_files = list(package_files)
- seen_some = False
- for fn in package_files:
- is_simple_module = "/" not in fn and fn.endswith(".py")
- is_package = fn.count("/") == 1 and fn.endswith("__init__.py")
- if is_simple_module:
- module_name, _ = os.path.splitext(fn)
- # we ignore "setup.py" at the root of the distribution
- if module_name != "setup":
- seen_some = True
- yield module_name
- elif is_package:
- package_name = os.path.dirname(fn)
- seen_some = True
- yield package_name
- if not seen_some:
- # at this point we did not find any packages or modules suitable for assertion
- # rewriting, so we try again by stripping the first path component (to account for
- # "src" based source trees for example)
- # this approach lets us have the common case continue to be fast, as egg-distributions
- # are rarer
- new_package_files = []
- for fn in package_files:
- parts = fn.split("/")
- new_fn = "/".join(parts[1:])
- if new_fn:
- new_package_files.append(new_fn)
- if new_package_files:
- for _module in _iter_rewritable_modules(new_package_files):
- yield _module
- class Config(object):
- """
- Access to configuration values, pluginmanager and plugin hooks.
- :ivar PytestPluginManager pluginmanager: the plugin manager handles plugin registration and hook invocation.
- :ivar argparse.Namespace option: access to command line option as attributes.
- :ivar InvocationParams invocation_params:
- Object containing the parameters regarding the ``pytest.main``
- invocation.
- Contains the followinig read-only attributes:
- * ``args``: list of command-line arguments as passed to ``pytest.main()``.
- * ``plugins``: list of extra plugins, might be None
- * ``dir``: directory where ``pytest.main()`` was invoked from.
- """
- @attr.s(frozen=True)
- class InvocationParams(object):
- """Holds parameters passed during ``pytest.main()``
- .. note::
- Currently the environment variable PYTEST_ADDOPTS is also handled by
- pytest implicitly, not being part of the invocation.
- Plugins accessing ``InvocationParams`` must be aware of that.
- """
- args = attr.ib()
- plugins = attr.ib()
- dir = attr.ib()
- def __init__(self, pluginmanager, invocation_params=None, *args):
- from .argparsing import Parser, FILE_OR_DIR
- if invocation_params is None:
- invocation_params = self.InvocationParams(
- args=(), plugins=None, dir=Path().resolve()
- )
- #: access to command line option as attributes.
- #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
- self.option = argparse.Namespace()
- self.invocation_params = invocation_params
- _a = FILE_OR_DIR
- self._parser = Parser(
- usage="%%(prog)s [options] [%s] [%s] [...]" % (_a, _a),
- processopt=self._processopt,
- )
- #: a pluginmanager instance
- self.pluginmanager = pluginmanager
- self.trace = self.pluginmanager.trace.root.get("config")
- self.hook = self.pluginmanager.hook
- self._inicache = {}
- self._override_ini = ()
- self._opt2dest = {}
- self._cleanup = []
- self.pluginmanager.register(self, "pytestconfig")
- self._configured = False
- self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
- @property
- def invocation_dir(self):
- """Backward compatibility"""
- return py.path.local(str(self.invocation_params.dir))
- def add_cleanup(self, func):
- """ Add a function to be called when the config object gets out of
- use (usually coninciding with pytest_unconfigure)."""
- self._cleanup.append(func)
- def _do_configure(self):
- assert not self._configured
- self._configured = True
- self.hook.pytest_configure.call_historic(kwargs=dict(config=self))
- def _ensure_unconfigure(self):
- if self._configured:
- self._configured = False
- self.hook.pytest_unconfigure(config=self)
- self.hook.pytest_configure._call_history = []
- while self._cleanup:
- fin = self._cleanup.pop()
- fin()
- def get_terminal_writer(self):
- return self.pluginmanager.get_plugin("terminalreporter")._tw
- def pytest_cmdline_parse(self, pluginmanager, args):
- try:
- self.parse(args)
- except UsageError:
- # Handle --version and --help here in a minimal fashion.
- # This gets done via helpconfig normally, but its
- # pytest_cmdline_main is not called in case of errors.
- if getattr(self.option, "version", False) or "--version" in args:
- from _pytest.helpconfig import showversion
- showversion(self)
- elif (
- getattr(self.option, "help", False) or "--help" in args or "-h" in args
- ):
- self._parser._getparser().print_help()
- sys.stdout.write(
- "\nNOTE: displaying only minimal help due to UsageError.\n\n"
- )
- raise
- return self
- def notify_exception(self, excinfo, option=None):
- if option and getattr(option, "fulltrace", False):
- style = "long"
- else:
- style = "native"
- excrepr = excinfo.getrepr(
- funcargs=True, showlocals=getattr(option, "showlocals", False), style=style
- )
- res = self.hook.pytest_internalerror(excrepr=excrepr, excinfo=excinfo)
- if not any(res):
- for line in str(excrepr).split("\n"):
- sys.stderr.write("INTERNALERROR> %s\n" % line)
- sys.stderr.flush()
- def cwd_relative_nodeid(self, nodeid):
- # nodeid's are relative to the rootpath, compute relative to cwd
- if self.invocation_dir != self.rootdir:
- fullpath = self.rootdir.join(nodeid)
- nodeid = self.invocation_dir.bestrelpath(fullpath)
- return nodeid
- @classmethod
- def fromdictargs(cls, option_dict, args):
- """ constructor useable for subprocesses. """
- config = get_config(args)
- config.option.__dict__.update(option_dict)
- config.parse(args, addopts=False)
- for x in config.option.plugins:
- config.pluginmanager.consider_pluginarg(x)
- return config
- def _processopt(self, opt):
- for name in opt._short_opts + opt._long_opts:
- self._opt2dest[name] = opt.dest
- if hasattr(opt, "default") and opt.dest:
- if not hasattr(self.option, opt.dest):
- setattr(self.option, opt.dest, opt.default)
- @hookimpl(trylast=True)
- def pytest_load_initial_conftests(self, early_config):
- self.pluginmanager._set_initial_conftests(early_config.known_args_namespace)
- def _initini(self, args):
- ns, unknown_args = self._parser.parse_known_and_unknown_args(
- args, namespace=copy.copy(self.option)
- )
- r = determine_setup(
- ns.inifilename,
- ns.file_or_dir + unknown_args,
- rootdir_cmd_arg=ns.rootdir or None,
- config=self,
- )
- self.rootdir, self.inifile, self.inicfg = r
- self._parser.extra_info["rootdir"] = self.rootdir
- self._parser.extra_info["inifile"] = self.inifile
- self._parser.addini("addopts", "extra command line options", "args")
- self._parser.addini("minversion", "minimally required pytest version")
- self._override_ini = ns.override_ini or ()
- def _consider_importhook(self, args):
- """Install the PEP 302 import hook if using assertion rewriting.
- Needs to parse the --assert=<mode> option from the commandline
- and find all the installed plugins to mark them for rewriting
- by the importhook.
- """
- ns, unknown_args = self._parser.parse_known_and_unknown_args(args)
- mode = getattr(ns, "assertmode", "plain")
- if mode == "rewrite":
- try:
- hook = _pytest.assertion.install_importhook(self)
- except SystemError:
- mode = "plain"
- else:
- self._mark_plugins_for_rewrite(hook)
- _warn_about_missing_assertion(mode)
- def _mark_plugins_for_rewrite(self, hook):
- """
- Given an importhook, mark for rewrite any top-level
- modules or packages in the distribution package for
- all pytest plugins.
- """
- self.pluginmanager.rewrite_hook = hook
- if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
- # We don't autoload from setuptools entry points, no need to continue.
- return
- package_files = (
- str(file)
- for dist in importlib_metadata.distributions()
- if any(ep.group == "pytest11" for ep in dist.entry_points)
- for file in dist.files or []
- )
- for name in _iter_rewritable_modules(package_files):
- hook.mark_rewrite(name)
- def _validate_args(self, args, via):
- """Validate known args."""
- self._parser._config_source_hint = via
- try:
- self._parser.parse_known_and_unknown_args(
- args, namespace=copy.copy(self.option)
- )
- finally:
- del self._parser._config_source_hint
- return args
- def _preparse(self, args, addopts=True):
- if addopts:
- env_addopts = os.environ.get("PYTEST_ADDOPTS", "")
- if len(env_addopts):
- args[:] = (
- self._validate_args(shlex.split(env_addopts), "via PYTEST_ADDOPTS")
- + args
- )
- self._initini(args)
- if addopts:
- args[:] = (
- self._validate_args(self.getini("addopts"), "via addopts config") + args
- )
- self._checkversion()
- self._consider_importhook(args)
- self.pluginmanager.consider_preparse(args)
- if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
- # Don't autoload from setuptools entry point. Only explicitly specified
- # plugins are going to be loaded.
- self.pluginmanager.load_setuptools_entrypoints("pytest11")
- self.pluginmanager.consider_env()
- self.known_args_namespace = ns = self._parser.parse_known_args(
- args, namespace=copy.copy(self.option)
- )
- if self.known_args_namespace.confcutdir is None and self.inifile:
- confcutdir = py.path.local(self.inifile).dirname
- self.known_args_namespace.confcutdir = confcutdir
- try:
- self.hook.pytest_load_initial_conftests(
- early_config=self, args=args, parser=self._parser
- )
- except ConftestImportFailure:
- e = sys.exc_info()[1]
- if ns.help or ns.version:
- # we don't want to prevent --help/--version to work
- # so just let is pass and print a warning at the end
- from _pytest.warnings import _issue_warning_captured
- _issue_warning_captured(
- PytestConfigWarning(
- "could not load initial conftests: {}".format(e.path)
- ),
- self.hook,
- stacklevel=2,
- )
- else:
- raise
- def _checkversion(self):
- import pytest
- minver = self.inicfg.get("minversion", None)
- if minver:
- if Version(minver) > Version(pytest.__version__):
- raise pytest.UsageError(
- "%s:%d: requires pytest-%s, actual pytest-%s'"
- % (
- self.inicfg.config.path,
- self.inicfg.lineof("minversion"),
- minver,
- pytest.__version__,
- )
- )
- def parse(self, args, addopts=True):
- # parse given cmdline arguments into this config object.
- assert not hasattr(
- self, "args"
- ), "can only parse cmdline args at most once per Config object"
- self._origargs = args
- self.hook.pytest_addhooks.call_historic(
- kwargs=dict(pluginmanager=self.pluginmanager)
- )
- self._preparse(args, addopts=addopts)
- # XXX deprecated hook:
- self.hook.pytest_cmdline_preparse(config=self, args=args)
- self._parser.after_preparse = True
- try:
- args = self._parser.parse_setoption(
- args, self.option, namespace=self.option
- )
- if not args:
- if self.invocation_dir == self.rootdir:
- args = self.getini("testpaths")
- if not args:
- args = [str(self.invocation_dir)]
- self.args = args
- except PrintHelp:
- pass
- def addinivalue_line(self, name, line):
- """ add a line to an ini-file option. The option must have been
- declared but might not yet be set in which case the line becomes the
- the first line in its value. """
- x = self.getini(name)
- assert isinstance(x, list)
- x.append(line) # modifies the cached list inline
- def getini(self, name):
- """ return configuration value from an :ref:`ini file <inifiles>`. If the
- specified name hasn't been registered through a prior
- :py:func:`parser.addini <_pytest.config.Parser.addini>`
- call (usually from a plugin), a ValueError is raised. """
- try:
- return self._inicache[name]
- except KeyError:
- self._inicache[name] = val = self._getini(name)
- return val
- def _getini(self, name):
- try:
- description, type, default = self._parser._inidict[name]
- except KeyError:
- raise ValueError("unknown configuration value: %r" % (name,))
- value = self._get_override_ini_value(name)
- if value is None:
- try:
- value = self.inicfg[name]
- except KeyError:
- if default is not None:
- return default
- if type is None:
- return ""
- return []
- if type == "pathlist":
- dp = py.path.local(self.inicfg.config.path).dirpath()
- values = []
- for relpath in shlex.split(value):
- values.append(dp.join(relpath, abs=True))
- return values
- elif type == "args":
- return shlex.split(value)
- elif type == "linelist":
- return [t for t in map(lambda x: x.strip(), value.split("\n")) if t]
- elif type == "bool":
- return bool(_strtobool(value.strip()))
- else:
- assert type is None
- return value
- def _getconftest_pathlist(self, name, path):
- try:
- mod, relroots = self.pluginmanager._rget_with_confmod(name, path)
- except KeyError:
- return None
- modpath = py.path.local(mod.__file__).dirpath()
- values = []
- for relroot in relroots:
- if not isinstance(relroot, py.path.local):
- relroot = relroot.replace("/", py.path.local.sep)
- relroot = modpath.join(relroot, abs=True)
- values.append(relroot)
- return values
- def _get_override_ini_value(self, name):
- value = None
- # override_ini is a list of "ini=value" options
- # always use the last item if multiple values are set for same ini-name,
- # e.g. -o foo=bar1 -o foo=bar2 will set foo to bar2
- for ini_config in self._override_ini:
- try:
- key, user_ini_value = ini_config.split("=", 1)
- except ValueError:
- raise UsageError("-o/--override-ini expects option=value style.")
- else:
- if key == name:
- value = user_ini_value
- return value
- def getoption(self, name, default=notset, skip=False):
- """ return command line option value.
- :arg name: name of the option. You may also specify
- the literal ``--OPT`` option instead of the "dest" option name.
- :arg default: default value if no option of that name exists.
- :arg skip: if True raise pytest.skip if option does not exists
- or has a None value.
- """
- name = self._opt2dest.get(name, name)
- try:
- val = getattr(self.option, name)
- if val is None and skip:
- raise AttributeError(name)
- return val
- except AttributeError:
- if default is not notset:
- return default
- if skip:
- import pytest
- pytest.skip("no %r option found" % (name,))
- raise ValueError("no option named %r" % (name,))
- def getvalue(self, name, path=None):
- """ (deprecated, use getoption()) """
- return self.getoption(name)
- def getvalueorskip(self, name, path=None):
- """ (deprecated, use getoption(skip=True)) """
- return self.getoption(name, skip=True)
- def _assertion_supported():
- try:
- assert False
- except AssertionError:
- return True
- else:
- return False
- def _warn_about_missing_assertion(mode):
- if not _assertion_supported():
- if mode == "plain":
- sys.stderr.write(
- "WARNING: ASSERTIONS ARE NOT EXECUTED"
- " and FAILING TESTS WILL PASS. Are you"
- " using python -O?"
- )
- else:
- sys.stderr.write(
- "WARNING: assertions not in test modules or"
- " plugins will be ignored"
- " because assert statements are not executed "
- "by the underlying Python interpreter "
- "(are you using python -O?)\n"
- )
- def setns(obj, dic):
- import pytest
- for name, value in dic.items():
- if isinstance(value, dict):
- mod = getattr(obj, name, None)
- if mod is None:
- modname = "pytest.%s" % name
- mod = types.ModuleType(modname)
- sys.modules[modname] = mod
- mod.__all__ = []
- setattr(obj, name, mod)
- obj.__all__.append(name)
- setns(mod, value)
- else:
- setattr(obj, name, value)
- obj.__all__.append(name)
- # if obj != pytest:
- # pytest.__all__.append(name)
- setattr(pytest, name, value)
- def create_terminal_writer(config, *args, **kwargs):
- """Create a TerminalWriter instance configured according to the options
- in the config object. Every code which requires a TerminalWriter object
- and has access to a config object should use this function.
- """
- tw = py.io.TerminalWriter(*args, **kwargs)
- if config.option.color == "yes":
- tw.hasmarkup = True
- if config.option.color == "no":
- tw.hasmarkup = False
- return tw
- def _strtobool(val):
- """Convert a string representation of truth to true (1) or false (0).
- True values are 'y', 'yes', 't', 'true', 'on', and '1'; false values
- are 'n', 'no', 'f', 'false', 'off', and '0'. Raises ValueError if
- 'val' is anything else.
- .. note:: copied from distutils.util
- """
- val = val.lower()
- if val in ("y", "yes", "t", "true", "on", "1"):
- return 1
- elif val in ("n", "no", "f", "false", "off", "0"):
- return 0
- else:
- raise ValueError("invalid truth value %r" % (val,))
|