diff --git a/changelog/14454.bugfix.rst b/changelog/14454.bugfix.rst new file mode 100644 index 00000000000..005a1c29259 --- /dev/null +++ b/changelog/14454.bugfix.rst @@ -0,0 +1 @@ +Fixed a regression where :option:`-c` pointing to a config file in a sub-directory would set :confval:`rootdir` to the config file's parent directory instead of the invocation directory, breaking conftest discovery. Patch by :user:`EternalRights`. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 44d606b00a4..f7f04117be1 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -22,6 +22,7 @@ import importlib import importlib.metadata import inspect +import logging import os import pathlib import re @@ -78,6 +79,9 @@ from _pytest.warning_types import warn_explicit_for +log = logging.getLogger(__name__) + + if TYPE_CHECKING: from _pytest.assertion.rewrite import AssertionRewritingHook from _pytest.cacheprovider import Cache @@ -1501,6 +1505,14 @@ def parse(self, args: list[str], addopts: bool = True) -> None: self._parser.extra_info["rootdir"] = str(self.rootpath) self._parser.extra_info["inifile"] = str(self.inipath) + if ns.inifilename and not ns.rootdir: + if inipath is not None and inipath.parent != self.invocation_params.dir: + log.warning( + "rootdir was set to %s because -c was given without --rootdir. " + "Use --rootdir to explicitly disambiguate.", + self.invocation_params.dir, + ) + self._parser.addini("addopts", "Extra command line options", "args") self._parser.addini("minversion", "Minimally required pytest version") self._parser.addini( diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index e74546c6e28..3950a895299 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -308,7 +308,7 @@ def determine_setup( inipath: Path | None = inipath_ inicfg = load_config_dict_from_file(inipath_) or {} if rootdir_cmd_arg is None: - rootdir = inipath_.parent + rootdir = invocation_dir else: ancestor = get_common_ancestor(invocation_dir, dirs) rootdir, inipath, inicfg, ignored_config_files = locate_config( diff --git a/testing/test_config.py b/testing/test_config.py index 8026c108db0..ec976786430 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -2028,12 +2028,73 @@ def test_with_specific_inifile( override_ini=None, args=[str(tmp_path)], rootdir_cmd_arg=None, - invocation_dir=Path.cwd(), + invocation_dir=tmp_path, ) assert rootpath == tmp_path assert inipath == p assert ini_config["x"] == ConfigValue("10", origin="file", mode="ini") + def test_config_in_subdir_does_not_change_rootdir( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: + """Config file in a subdir should not move rootdir to that subdir (#13246).""" + config_dir = tmp_path / "config" + config_dir.mkdir() + inipath = config_dir / "pytest.ini" + inipath.touch() + monkeypatch.chdir(tmp_path) + + rootpath, found_inipath, *_ = determine_setup( + inifile=str(inipath), + override_ini=None, + args=[], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) + assert rootpath == tmp_path, ( + f"rootdir should be invocation_dir ({tmp_path}), got {rootpath}" + ) + assert found_inipath == inipath + + def test_rootdir_warning_when_config_in_subdir( + self, tmp_path: Path, caplog: pytest.LogCaptureFixture + ) -> None: + """When -c points to a subdir, a warning should be logged (#13246).""" + import logging + + config_dir = tmp_path / "config" + config_dir.mkdir() + inipath = config_dir / "pytest.ini" + inipath.touch() + + caplog.set_level(logging.WARNING) + Config.fromdictargs( + {"inifilename": str(inipath)}, # -c config/pytest.ini + [], + ) + + assert len(caplog.records) >= 1 + assert "rootdir was set to" in caplog.records[0].message + assert "--rootdir" in caplog.records[0].message + + def test_no_warning_when_config_in_rootdir( + self, tmp_path: Path, monkeypatch: MonkeyPatch, caplog: pytest.LogCaptureFixture + ) -> None: + """When -c points to the invocation dir itself, no warning needed (#13246).""" + import logging + + inipath = tmp_path / "pytest.ini" + inipath.touch() + monkeypatch.chdir(tmp_path) + + caplog.set_level(logging.WARNING) + Config.fromdictargs( + {"inifilename": str(inipath)}, + [], + ) + + assert len(caplog.records) == 0 + def test_explicit_config_file_sets_rootdir( self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: