diff --git a/changelog/4265.bugfix.rst b/changelog/4265.bugfix.rst new file mode 100644 index 00000000000..7b40737c3e4 --- /dev/null +++ b/changelog/4265.bugfix.rst @@ -0,0 +1 @@ +Validate arguments from the ``PYTEST_ADDOPTS`` environment variable and the ``addopts`` ini option separately. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 562b50c3892..f70c91590d3 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -784,12 +784,21 @@ def _mark_plugins_for_rewrite(self, hook): for name in _iter_rewritable_modules(package_files): hook.mark_rewrite(name) + def _validate_args(self, args): + """Validate known args.""" + self._parser.parse_known_and_unknown_args( + args, namespace=copy.copy(self.option) + ) + return args + def _preparse(self, args, addopts=True): if addopts: - args[:] = shlex.split(os.environ.get("PYTEST_ADDOPTS", "")) + args + env_addopts = os.environ.get("PYTEST_ADDOPTS", "") + if len(env_addopts): + args[:] = self._validate_args(shlex.split(env_addopts)) + args self._initini(args) if addopts: - args[:] = self.getini("addopts") + args + args[:] = self._validate_args(self.getini("addopts")) + args self._checkversion() self._consider_importhook(args) self.pluginmanager.consider_preparse(args) diff --git a/testing/test_config.py b/testing/test_config.py index 605d28aa0e4..b7c68ea4dff 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1082,6 +1082,33 @@ def test_addopts_before_initini(self, monkeypatch): config._preparse([], addopts=True) assert config._override_ini == ["cache_dir=%s" % cache_dir] + def test_addopts_from_env_not_concatenated(self, monkeypatch): + """PYTEST_ADDOPTS should not take values from normal args (#4265).""" + from _pytest.config import get_config + + monkeypatch.setenv("PYTEST_ADDOPTS", "-o") + config = get_config() + with pytest.raises(SystemExit) as excinfo: + config._preparse(["cache_dir=ignored"], addopts=True) + assert excinfo.value.args[0] == _pytest.main.EXIT_USAGEERROR + + def test_addopts_from_ini_not_concatenated(self, testdir): + """addopts from ini should not take values from normal args (#4265).""" + testdir.makeini( + """ + [pytest] + addopts=-o + """ + ) + result = testdir.runpytest("cache_dir=ignored") + result.stderr.fnmatch_lines( + [ + "%s: error: argument -o/--override-ini: expected one argument" + % (testdir.request.config._parser.optparser.prog,) + ] + ) + assert result.ret == _pytest.main.EXIT_USAGEERROR + def test_override_ini_does_not_contain_paths(self): """Check that -o no longer swallows all options after it (#3103)""" from _pytest.config import get_config