diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index bee2fb22a..eadce78fa 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -49,7 +49,6 @@ from _pytest._code import filter_traceback from _pytest._io import TerminalWriter from _pytest.compat import final from _pytest.compat import importlib_metadata -from _pytest.compat import legacy_path from _pytest.outcomes import fail from _pytest.outcomes import Skipped from _pytest.pathlib import absolutepath @@ -1369,6 +1368,12 @@ class Config: self._inicache[name] = val = self._getini(name) return val + # Meant for easy monkeypatching by legacypath plugin. + # Can be inlined back (with no cover removed) once legacypath is gone. + def _getini_unknown_type(self, name: str, type: str, value: Union[str, List[str]]): + msg = f"unknown configuration type: {type}" + raise ValueError(msg, value) # pragma: no cover + def _getini(self, name: str): try: description, type, default = self._parser._inidict[name] @@ -1401,13 +1406,7 @@ class Config: # a_line_list = ["tests", "acceptance"] # in this case, we already have a list ready to use. # - if type == "pathlist": - # TODO: This assert is probably not valid in all cases. - assert self.inipath is not None - dp = self.inipath.parent - input_values = shlex.split(value) if isinstance(value, str) else value - return [legacy_path(str(dp / x)) for x in input_values] - elif type == "paths": + if type == "paths": # TODO: This assert is probably not valid in all cases. assert self.inipath is not None dp = self.inipath.parent @@ -1422,9 +1421,12 @@ class Config: return value elif type == "bool": return _strtobool(str(value).strip()) - else: - assert type in [None, "string"] + elif type == "string": return value + elif type is None: + return value + else: + return self._getini_unknown_type(name, type, value) def _getconftest_pathlist( self, name: str, path: Path, rootpath: Path diff --git a/src/_pytest/legacypath.py b/src/_pytest/legacypath.py index 3fca65b25..2af20856a 100644 --- a/src/_pytest/legacypath.py +++ b/src/_pytest/legacypath.py @@ -1,4 +1,5 @@ """Add backward compatibility support for the legacy py path type.""" +import shlex import subprocess from pathlib import Path from typing import List @@ -372,6 +373,19 @@ def Session_stardir(self: pytest.Session) -> LEGACY_PATH: return legacy_path(self.startpath) +def Config__getini_unknown_type( + self, name: str, type: str, value: Union[str, List[str]] +): + if type == "pathlist": + # TODO: This assert is probably not valid in all cases. + assert self.inipath is not None + dp = self.inipath.parent + input_values = shlex.split(value) if isinstance(value, str) else value + return [legacy_path(str(dp / x)) for x in input_values] + else: + raise ValueError(f"unknown configuration type: {type}", value) + + def pytest_configure(config: pytest.Config) -> None: mp = pytest.MonkeyPatch() config.add_cleanup(mp.undo) @@ -412,3 +426,6 @@ def pytest_configure(config: pytest.Config) -> None: # Add Session.startdir property. mp.setattr(pytest.Session, "startdir", property(Session_stardir), raising=False) + + # Add pathlist configuration type. + mp.setattr(pytest.Config, "_getini_unknown_type", Config__getini_unknown_type) diff --git a/testing/test_config.py b/testing/test_config.py index 766d5a849..443559116 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -635,14 +635,11 @@ class TestConfigAPI: pytest.raises(ValueError, config.getini, "other") @pytest.mark.parametrize("config_type", ["ini", "pyproject"]) - @pytest.mark.parametrize("ini_type", ["paths", "pathlist"]) - def test_addini_paths( - self, pytester: Pytester, config_type: str, ini_type: str - ) -> None: + def test_addini_paths(self, pytester: Pytester, config_type: str) -> None: pytester.makeconftest( - f""" + """ def pytest_addoption(parser): - parser.addini("paths", "my new ini value", type="{ini_type}") + parser.addini("paths", "my new ini value", type="paths") parser.addini("abc", "abc value") """ ) @@ -1521,12 +1518,11 @@ class TestOverrideIniArgs: assert result.ret == 0 result.stdout.fnmatch_lines(["custom_option:3.0"]) - @pytest.mark.parametrize("ini_type", ["paths", "pathlist"]) - def test_override_ini_paths(self, pytester: Pytester, ini_type: str) -> None: + def test_override_ini_paths(self, pytester: Pytester) -> None: pytester.makeconftest( - f""" + """ def pytest_addoption(parser): - parser.addini("paths", "my new ini value", type="{ini_type}")""" + parser.addini("paths", "my new ini value", type="paths")""" ) pytester.makeini( """ @@ -1534,15 +1530,12 @@ class TestOverrideIniArgs: paths=blah.py""" ) pytester.makepyfile( - rf""" + r""" def test_overriden(pytestconfig): config_paths = pytestconfig.getini("paths") print(config_paths) for cpf in config_paths: - if "{ini_type}" == "pathlist": - print('\nuser_path:%s' % cpf.basename) - else: - print('\nuser_path:%s' % cpf.name) + print('\nuser_path:%s' % cpf.name) """ ) result = pytester.runpytest( diff --git a/testing/test_legacypath.py b/testing/test_legacypath.py index ed0435c1c..08568d8f7 100644 --- a/testing/test_legacypath.py +++ b/testing/test_legacypath.py @@ -94,3 +94,58 @@ class TestFixtureRequestSessionScoped: match="path not available in session-scoped context", ): session_request.fspath + + +@pytest.mark.parametrize("config_type", ["ini", "pyproject"]) +def test_addini_paths(pytester: pytest.Pytester, config_type: str) -> None: + pytester.makeconftest( + """ + def pytest_addoption(parser): + parser.addini("paths", "my new ini value", type="pathlist") + parser.addini("abc", "abc value") + """ + ) + if config_type == "ini": + inipath = pytester.makeini( + """ + [pytest] + paths=hello world/sub.py + """ + ) + elif config_type == "pyproject": + inipath = pytester.makepyprojecttoml( + """ + [tool.pytest.ini_options] + paths=["hello", "world/sub.py"] + """ + ) + config = pytester.parseconfig() + values = config.getini("paths") + assert len(values) == 2 + assert values[0] == inipath.parent.joinpath("hello") + assert values[1] == inipath.parent.joinpath("world/sub.py") + pytest.raises(ValueError, config.getini, "other") + + +def test_override_ini_paths(pytester: pytest.Pytester) -> None: + pytester.makeconftest( + """ + def pytest_addoption(parser): + parser.addini("paths", "my new ini value", type="pathlist")""" + ) + pytester.makeini( + """ + [pytest] + paths=blah.py""" + ) + pytester.makepyfile( + r""" + def test_overriden(pytestconfig): + config_paths = pytestconfig.getini("paths") + print(config_paths) + for cpf in config_paths: + print('\nuser_path:%s' % cpf.basename) + """ + ) + result = pytester.runpytest("--override-ini", "paths=foo/bar1.py foo/bar2.py", "-s") + result.stdout.fnmatch_lines(["user_path:bar1.py", "user_path:bar2.py"])