diff --git a/changelog/6008.improvement.rst b/changelog/6008.improvement.rst new file mode 100644 index 000000000..22ef35cc8 --- /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 39c8d2cdf..cd23281fa 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 Config: .. 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 @@ class Config: 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 71dae5c4c..0264b029d 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -7,6 +7,7 @@ import _pytest._code 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 @@ class TestConfigFromdictargs: 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 @@ def test_invocation_args(testdir): 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 @@ def test_invocation_args(testdir): 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",