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 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",