diff --git a/changelog/5564.feature.rst b/changelog/5564.feature.rst new file mode 100644 index 000000000..afc9f3323 --- /dev/null +++ b/changelog/5564.feature.rst @@ -0,0 +1,3 @@ +New ``Config.invocation_args`` and ``Config.invocation_plugins`` attributes. + +These attributes can be used by plugins to access the unchanged arguments passed to ``pytest.main()``. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index c9310bcb5..688a126d4 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -70,6 +70,8 @@ def main(args=None, plugins=None): tw.line(line.rstrip(), red=True) return 4 else: + config.invocation_args = args + config.invocation_plugins = plugins try: return config.hook.pytest_cmdline_main(config=config) finally: @@ -608,20 +610,33 @@ def _iter_rewritable_modules(package_files): class Config: - """ 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 invocation_args: list of command-line arguments as passed to pytest.main() + + :ivar invocation_plugins: list of extra plugins passed to pytest.main(), might be None + + :ivar py.path.local invocation_dir: directory where pytest.main() was invoked from + """ 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.option = argparse.Namespace() + self.invocation_args = None + self.invocation_plugins = None + self.invocation_dir = py.path.local() + _a = FILE_OR_DIR self._parser = Parser( usage="%(prog)s [options] [{}] [{}] [...]".format(_a, _a), processopt=self._processopt, ) - #: a pluginmanager instance self.pluginmanager = pluginmanager self.trace = self.pluginmanager.trace.root.get("config") self.hook = self.pluginmanager.hook @@ -631,7 +646,6 @@ class Config: 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)) def add_cleanup(self, func): diff --git a/testing/test_config.py b/testing/test_config.py index eb7f95271..7ec3c9087 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1198,6 +1198,28 @@ def test_config_does_not_load_blocked_plugin_from_args(testdir): assert result.ret == ExitCode.USAGE_ERROR +def test_invocation_arguments(testdir): + """Ensure that Config.invocation_* arguments are correctly defined""" + + class DummyPlugin: + 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_args == [p, "-v"] + + plugins = config.invocation_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", [