From 113a860a1fd179bd8323e783a359c742ab87b7ff Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 26 Apr 2021 17:46:26 +0300 Subject: [PATCH] argparsing: support parser.addini(type="paths") which returns pathlib.Paths --- changelog/7259.feature.rst | 5 +++ src/_pytest/config/__init__.py | 6 +++ src/_pytest/config/argparsing.py | 27 +++++++++---- testing/test_config.py | 67 ++++++++++++++++---------------- 4 files changed, 65 insertions(+), 40 deletions(-) diff --git a/changelog/7259.feature.rst b/changelog/7259.feature.rst index e19aaca52..41a213f63 100644 --- a/changelog/7259.feature.rst +++ b/changelog/7259.feature.rst @@ -1,2 +1,7 @@ Added :meth:`cache.mkdir() `, which is similar to the existing :meth:`cache.makedir() `, but returns a :class:`pathlib.Path` instead of a legacy ``py.path.local``. + +Added a ``paths`` type to :meth:`parser.addini() <_pytest.config.argparsing.Parser.addini>`, +as in ``parser.addini("mypaths", "my paths", type="paths")``, +which is similar to the existing ``pathlist``, +but returns a list of :class:`pathlib.Path` instead of legacy ``py.path.local``. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 0fd91c2c9..7f18f62cb 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1427,6 +1427,12 @@ class Config: 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": + # 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 [dp / x for x in input_values] elif type == "args": return shlex.split(value) if isinstance(value, str) else value elif type == "linelist": diff --git a/src/_pytest/config/argparsing.py b/src/_pytest/config/argparsing.py index cf738cc2b..d24696eaf 100644 --- a/src/_pytest/config/argparsing.py +++ b/src/_pytest/config/argparsing.py @@ -163,22 +163,35 @@ class Parser: name: str, help: str, type: Optional[ - "Literal['string', 'pathlist', 'args', 'linelist', 'bool']" + "Literal['string', 'paths', 'pathlist', 'args', 'linelist', 'bool']" ] = None, default=None, ) -> None: """Register an ini-file option. - :name: Name of the ini-variable. - :type: Type of the variable, can be ``string``, ``pathlist``, ``args``, - ``linelist`` or ``bool``. Defaults to ``string`` if ``None`` or - not passed. - :default: Default value if no ini-file option exists but is queried. + :name: + Name of the ini-variable. + :type: + Type of the variable. Can be: + + * ``string``: a string + * ``bool``: a boolean + * ``args``: a list of strings, separated as in a shell + * ``linelist``: a list of strings, separated by line breaks + * ``paths``: a list of :class:`pathlib.Path`, separated as in a shell + * ``pathlist``: a list of ``py.path``, separated as in a shell + + .. versionadded:: 6.3 + The ``paths`` variable type. + + Defaults to ``string`` if ``None`` or not passed. + :default: + Default value if no ini-file option exists but is queried. The value of ini-variables can be retrieved via a call to :py:func:`config.getini(name) <_pytest.config.Config.getini>`. """ - assert type in (None, "string", "pathlist", "args", "linelist", "bool") + assert type in (None, "string", "paths", "pathlist", "args", "linelist", "bool") self._inidict[name] = (help, type, default) self._ininames.append(name) diff --git a/testing/test_config.py b/testing/test_config.py index 8fb1698c6..5dabb7812 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -595,14 +595,14 @@ class TestConfigAPI: def test_getconftest_pathlist(self, pytester: Pytester, tmp_path: Path) -> None: somepath = tmp_path.joinpath("x", "y", "z") p = tmp_path.joinpath("conftest.py") - p.write_text(f"pathlist = ['.', {str(somepath)!r}]") + p.write_text(f"mylist = {['.', os.fspath(somepath)]}") config = pytester.parseconfigure(p) assert ( config._getconftest_pathlist("notexist", path=tmp_path, rootpath=tmp_path) is None ) pl = ( - config._getconftest_pathlist("pathlist", path=tmp_path, rootpath=tmp_path) + config._getconftest_pathlist("mylist", path=tmp_path, rootpath=tmp_path) or [] ) print(pl) @@ -634,41 +634,37 @@ class TestConfigAPI: assert val == "hello" pytest.raises(ValueError, config.getini, "other") - def make_conftest_for_pathlist(self, pytester: Pytester) -> None: + @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: pytester.makeconftest( - """ + f""" def pytest_addoption(parser): - parser.addini("paths", "my new ini value", type="pathlist") + parser.addini("paths", "my new ini value", type="{ini_type}") parser.addini("abc", "abc value") """ ) - - def test_addini_pathlist_ini_files(self, pytester: Pytester) -> None: - self.make_conftest_for_pathlist(pytester) - p = pytester.makeini( + if config_type == "ini": + inipath = pytester.makeini( + """ + [pytest] + paths=hello world/sub.py """ - [pytest] - paths=hello world/sub.py - """ - ) - self.check_config_pathlist(pytester, p) - - def test_addini_pathlist_pyproject_toml(self, pytester: Pytester) -> None: - self.make_conftest_for_pathlist(pytester) - p = pytester.makepyprojecttoml( + ) + elif config_type == "pyproject": + inipath = pytester.makepyprojecttoml( + """ + [tool.pytest.ini_options] + paths=["hello", "world/sub.py"] """ - [tool.pytest.ini_options] - paths=["hello", "world/sub.py"] - """ - ) - self.check_config_pathlist(pytester, p) - - def check_config_pathlist(self, pytester: Pytester, config_path: Path) -> None: + ) config = pytester.parseconfig() values = config.getini("paths") assert len(values) == 2 - assert values[0] == config_path.parent.joinpath("hello") - assert values[1] == config_path.parent.joinpath("world/sub.py") + assert values[0] == inipath.parent.joinpath("hello") + assert values[1] == inipath.parent.joinpath("world/sub.py") pytest.raises(ValueError, config.getini, "other") def make_conftest_for_args(self, pytester: Pytester) -> None: @@ -1519,11 +1515,12 @@ class TestOverrideIniArgs: assert result.ret == 0 result.stdout.fnmatch_lines(["custom_option:3.0"]) - def test_override_ini_pathlist(self, pytester: Pytester) -> None: + @pytest.mark.parametrize("ini_type", ["paths", "pathlist"]) + def test_override_ini_paths(self, pytester: Pytester, ini_type: str) -> None: pytester.makeconftest( - """ + f""" def pytest_addoption(parser): - parser.addini("paths", "my new ini value", type="pathlist")""" + parser.addini("paths", "my new ini value", type="{ini_type}")""" ) pytester.makeini( """ @@ -1531,12 +1528,16 @@ class TestOverrideIniArgs: paths=blah.py""" ) pytester.makepyfile( - """ - def test_pathlist(pytestconfig): + rf""" + def test_overriden(pytestconfig): config_paths = pytestconfig.getini("paths") print(config_paths) for cpf in config_paths: - print('\\nuser_path:%s' % cpf.basename)""" + if "{ini_type}" == "pathlist": + print('\nuser_path:%s' % cpf.basename) + else: + print('\nuser_path:%s' % cpf.name) + """ ) result = pytester.runpytest( "--override-ini", "paths=foo/bar1.py foo/bar2.py", "-s"