Merge pull request #6870 from fermezz/backport-invocation-args

This commit is contained in:
Bruno Oliveira 2020-05-08 08:04:49 -03:00 committed by GitHub
commit ded772b288
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 85 additions and 9 deletions

View File

@ -92,6 +92,7 @@ Evan Kepner
Fabien Zarifian Fabien Zarifian
Fabio Zadrozny Fabio Zadrozny
Feng Ma Feng Ma
Fernando Mezzabotta Rey
Florian Bruhin Florian Bruhin
Floris Bruynooghe Floris Bruynooghe
Gabriel Reis Gabriel Reis

View File

@ -0,0 +1 @@
New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``.

View File

@ -17,9 +17,9 @@ are available on PyPI.
While pytest ``5.0`` will be the new mainstream and development version, until **January 2020** While pytest ``5.0`` will be the new mainstream and development version, until **January 2020**
the pytest core team plans to make bug-fix releases of the pytest ``4.6`` series by the pytest core team plans to make bug-fix releases of the pytest ``4.6`` series by
back-porting patches to the ``4.6-maintenance`` branch that affect Python 2 users. back-porting patches to the ``4.6.x`` branch that affect Python 2 users.
**After 2020**, the core team will no longer actively backport patches, but the ``4.6-maintenance`` **After 2020**, the core team will no longer actively backport patches, but the ``4.6.x``
branch will continue to exist so the community itself can contribute patches. The core team will branch will continue to exist so the community itself can contribute patches. The core team will
be happy to accept those patches and make new ``4.6`` releases **until mid-2020**. be happy to accept those patches and make new ``4.6`` releases **until mid-2020**.

View File

@ -13,6 +13,7 @@ import sys
import types import types
import warnings import warnings
import attr
import py import py
import six import six
from packaging.version import Version from packaging.version import Version
@ -35,6 +36,7 @@ from _pytest.compat import lru_cache
from _pytest.compat import safe_str from _pytest.compat import safe_str
from _pytest.outcomes import fail from _pytest.outcomes import fail
from _pytest.outcomes import Skipped from _pytest.outcomes import Skipped
from _pytest.pathlib import Path
from _pytest.warning_types import PytestConfigWarning from _pytest.warning_types import PytestConfigWarning
hookimpl = HookimplMarker("pytest") hookimpl = HookimplMarker("pytest")
@ -154,10 +156,15 @@ builtin_plugins = set(default_plugins)
builtin_plugins.add("pytester") builtin_plugins.add("pytester")
def get_config(args=None): def get_config(args=None, plugins=None):
# subsequent calls to main will create a fresh instance # subsequent calls to main will create a fresh instance
pluginmanager = PytestPluginManager() pluginmanager = PytestPluginManager()
config = Config(pluginmanager) config = Config(
pluginmanager,
invocation_params=Config.InvocationParams(
args=args, plugins=plugins, dir=Path().resolve()
),
)
if args is not None: if args is not None:
# Handle any "-p no:plugin" args. # Handle any "-p no:plugin" args.
@ -190,7 +197,7 @@ def _prepareconfig(args=None, plugins=None):
msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})" msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})"
raise TypeError(msg.format(args, type(args))) raise TypeError(msg.format(args, type(args)))
config = get_config(args) config = get_config(args, plugins)
pluginmanager = config.pluginmanager pluginmanager = config.pluginmanager
try: try:
if plugins: if plugins:
@ -686,13 +693,52 @@ def _iter_rewritable_modules(package_files):
class Config(object): class Config(object):
""" access to configuration values, pluginmanager and plugin hooks. """ """
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()
)
def __init__(self, pluginmanager):
#: access to command line option as attributes. #: access to command line option as attributes.
#: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead
self.option = argparse.Namespace() self.option = argparse.Namespace()
from .argparsing import Parser, FILE_OR_DIR
self.invocation_params = invocation_params
_a = FILE_OR_DIR _a = FILE_OR_DIR
self._parser = Parser( self._parser = Parser(
@ -709,9 +755,13 @@ class Config(object):
self._cleanup = [] self._cleanup = []
self.pluginmanager.register(self, "pytestconfig") self.pluginmanager.register(self, "pytestconfig")
self._configured = False self._configured = False
self.invocation_dir = py.path.local()
self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser)) 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): def add_cleanup(self, func):
""" Add a function to be called when the config object gets out of """ Add a function to be called when the config object gets out of
use (usually coninciding with pytest_unconfigure).""" use (usually coninciding with pytest_unconfigure)."""

View File

@ -19,6 +19,7 @@ from _pytest.main import EXIT_NOTESTSCOLLECTED
from _pytest.main import EXIT_OK from _pytest.main import EXIT_OK
from _pytest.main import EXIT_TESTSFAILED from _pytest.main import EXIT_TESTSFAILED
from _pytest.main import EXIT_USAGEERROR from _pytest.main import EXIT_USAGEERROR
from _pytest.pathlib import Path
class TestParseIni(object): class TestParseIni(object):
@ -1222,6 +1223,29 @@ def test_config_does_not_load_blocked_plugin_from_args(testdir):
assert result.ret == EXIT_USAGEERROR assert result.ret == EXIT_USAGEERROR
def test_invocation_args(testdir):
"""Ensure that Config.invocation_* arguments are correctly defined"""
class DummyPlugin(object):
pass
p = testdir.makepyfile("def test(): pass")
plugin = DummyPlugin()
rec = testdir.inline_run(p, "-v", plugins=[plugin])
calls = rec.getcalls("pytest_runtest_protocol")
assert len(calls) == 1
call = calls[0]
config = call.item.config
assert config.invocation_params.args == [p, "-v"]
assert config.invocation_params.dir == Path(str(testdir.tmpdir))
plugins = config.invocation_params.plugins
assert len(plugins) == 2
assert plugins[0] is plugin
assert type(plugins[1]).__name__ == "Collect" # installed by testdir.inline_run()
@pytest.mark.parametrize( @pytest.mark.parametrize(
"plugin", "plugin",
[ [