Coverage for /usr/local/lib/python3.7/site-packages/_pytest/config/__init__.py : 16%

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