From d12cdd3127d5222335c699fd8e52c7ed8257b9e4 Mon Sep 17 00:00:00 2001 From: Bruno Oliveira Date: Sat, 19 Oct 2019 15:54:54 -0300 Subject: [PATCH] Make InvocationParams.args a tuple This avoids mutating the original list to reflect on InvocationParams, which is supposed to be an immutable snapshot of the state of pytest.main() at the moment of invocation (see pytest-dev/pytest-xdist#478). --- changelog/6008.improvement.rst | 2 ++ src/_pytest/config/__init__.py | 11 +++++------ testing/test_config.py | 9 +++++++-- 3 files changed, 14 insertions(+), 8 deletions(-) create mode 100644 changelog/6008.improvement.rst diff --git a/changelog/6008.improvement.rst b/changelog/6008.improvement.rst new file mode 100644 index 00000000000..22ef35cc8dc --- /dev/null +++ b/changelog/6008.improvement.rst @@ -0,0 +1,2 @@ +``Config.InvocationParams.args`` is now always a ``tuple`` to better convey that it should be +immutable and avoid accidental modifications. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 39c8d2cdfa2..cd23281fa73 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -169,7 +169,7 @@ def get_config(args=None, plugins=None): config = Config( pluginmanager, invocation_params=Config.InvocationParams( - args=args, plugins=plugins, dir=Path().resolve() + args=args or (), plugins=plugins, dir=Path().resolve() ), ) @@ -654,7 +654,7 @@ class Config: Contains the following read-only attributes: - * ``args``: list of command-line arguments as passed to ``pytest.main()``. + * ``args``: tuple 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. """ @@ -667,13 +667,13 @@ class InvocationParams: .. note:: - Currently the environment variable PYTEST_ADDOPTS is also handled by - pytest implicitly, not being part of the invocation. + Note that the environment variable ``PYTEST_ADDOPTS`` and the ``addopts`` + ini option are handled by pytest, not being included in the ``args`` attribute. Plugins accessing ``InvocationParams`` must be aware of that. """ - args = attr.ib() + args = attr.ib(converter=tuple) plugins = attr.ib() dir = attr.ib(type=Path) @@ -938,7 +938,6 @@ def parse(self, args, addopts=True): assert not hasattr( self, "args" ), "can only parse cmdline args at most once per Config object" - assert self.invocation_params.args == args self.hook.pytest_addhooks.call_historic( kwargs=dict(pluginmanager=self.pluginmanager) ) diff --git a/testing/test_config.py b/testing/test_config.py index 71dae5c4cdb..0264b029d01 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -7,6 +7,7 @@ import pytest from _pytest.compat import importlib_metadata from _pytest.config import _iter_rewritable_modules +from _pytest.config import Config from _pytest.config.exceptions import UsageError from _pytest.config.findpaths import determine_setup from _pytest.config.findpaths import get_common_ancestor @@ -456,7 +457,7 @@ def test_invocation_params_args(self, _sys_snapshot): config = Config.fromdictargs(option_dict, args) assert config.args == ["a", "b"] - assert config.invocation_params.args == args + assert config.invocation_params.args == tuple(args) assert config.option.verbose == 4 assert config.option.capture == "no" @@ -1235,7 +1236,7 @@ class DummyPlugin: call = calls[0] config = call.item.config - assert config.invocation_params.args == [p, "-v"] + assert config.invocation_params.args == (p, "-v") assert config.invocation_params.dir == Path(str(testdir.tmpdir)) plugins = config.invocation_params.plugins @@ -1243,6 +1244,10 @@ class DummyPlugin: assert plugins[0] is plugin assert type(plugins[1]).__name__ == "Collect" # installed by testdir.inline_run() + # args cannot be None + with pytest.raises(TypeError): + Config.InvocationParams(args=None, plugins=None, dir=Path()) + @pytest.mark.parametrize( "plugin",