From a6029ff2b70fa4e21021384c32f9f4152c09f7de Mon Sep 17 00:00:00 2001 From: Fernando Mez Date: Fri, 6 Mar 2020 12:06:34 -0300 Subject: [PATCH] BACKPORT: Introduction of Config.invocation_args --- AUTHORS | 1 + changelog/6870.feature.rst | 1 + doc/en/py27-py34-deprecation.rst | 4 +- src/_pytest/config/__init__.py | 64 ++++++++++++++++++++++++++++---- testing/test_config.py | 24 ++++++++++++ 5 files changed, 85 insertions(+), 9 deletions(-) create mode 100644 changelog/6870.feature.rst diff --git a/AUTHORS b/AUTHORS index a19e38bbf..80fce5392 100644 --- a/AUTHORS +++ b/AUTHORS @@ -92,6 +92,7 @@ Evan Kepner Fabien Zarifian Fabio Zadrozny Feng Ma +Fernando Mezzabotta Rey Florian Bruhin Floris Bruynooghe Gabriel Reis diff --git a/changelog/6870.feature.rst b/changelog/6870.feature.rst new file mode 100644 index 000000000..e2f365a33 --- /dev/null +++ b/changelog/6870.feature.rst @@ -0,0 +1 @@ +New ``Config.invocation_args`` attribute containing the unchanged arguments passed to ``pytest.main()``. diff --git a/doc/en/py27-py34-deprecation.rst b/doc/en/py27-py34-deprecation.rst index 95e96de04..0ca97539c 100644 --- a/doc/en/py27-py34-deprecation.rst +++ b/doc/en/py27-py34-deprecation.rst @@ -17,9 +17,9 @@ are available on PyPI. 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 -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 be happy to accept those patches and make new ``4.6`` releases **until mid-2020**. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 63d121040..0737ff9d5 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -13,6 +13,7 @@ import sys import types import warnings +import attr import py import six from packaging.version import Version @@ -35,6 +36,7 @@ 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") @@ -154,10 +156,15 @@ builtin_plugins = set(default_plugins) 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 pluginmanager = PytestPluginManager() - config = Config(pluginmanager) + 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. @@ -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: {})" raise TypeError(msg.format(args, type(args))) - config = get_config(args) + config = get_config(args, plugins) pluginmanager = config.pluginmanager try: if plugins: @@ -686,13 +693,52 @@ def _iter_rewritable_modules(package_files): 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. #: (deprecated), use :py:func:`getoption() <_pytest.config.Config.getoption>` instead self.option = argparse.Namespace() - from .argparsing import Parser, FILE_OR_DIR + + self.invocation_params = invocation_params _a = FILE_OR_DIR self._parser = Parser( @@ -709,9 +755,13 @@ class Config(object): self._cleanup = [] self.pluginmanager.register(self, "pytestconfig") self._configured = False - self.invocation_dir = py.path.local() 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).""" diff --git a/testing/test_config.py b/testing/test_config.py index d0e593730..d13f119b0 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -19,6 +19,7 @@ from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.main import EXIT_OK from _pytest.main import EXIT_TESTSFAILED from _pytest.main import EXIT_USAGEERROR +from _pytest.pathlib import Path class TestParseIni(object): @@ -1222,6 +1223,29 @@ def test_config_does_not_load_blocked_plugin_from_args(testdir): 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( "plugin", [