From 618337db83a321b8e20ad2a38a4a85ce1702f9a6 Mon Sep 17 00:00:00 2001 From: EternalRights Date: Sun, 10 May 2026 00:21:57 +0800 Subject: [PATCH 1/3] determine_setup: rootdir should be invocation_dir when -c is given Found this while testing a project with pytest -c config/pytest.ini. rootdir ended up pointing to config/ instead of the project root, which broke conftest discovery since conftests are resolved relative to rootdir. Turned out `rootdir = inipath_.parent` was the culprit - it makes rootdir jump to wherever the config file lives, ignoring invocation_dir. Simple fix: use invocation_dir instead. --- changelog/14454.bugfix.rst | 1 + src/_pytest/config/__init__.py | 12 +++++++ src/_pytest/config/findpaths.py | 2 +- testing/test_config.py | 64 ++++++++++++++++++++++++++++++++- 4 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 changelog/14454.bugfix.rst diff --git a/changelog/14454.bugfix.rst b/changelog/14454.bugfix.rst new file mode 100644 index 00000000000..c715956e4d0 --- /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`. \ No newline at end of file diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 44d606b00a4..f856cbf3f55 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -77,6 +77,10 @@ from _pytest.warning_types import PytestConfigWarning from _pytest.warning_types import warn_explicit_for +import logging + +log = logging.getLogger(__name__) + if TYPE_CHECKING: from _pytest.assertion.rewrite import AssertionRewritingHook @@ -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..217fc114655 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -2028,12 +2028,74 @@ 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}), " + f"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: From b399b23164b45fe211e2bb80d111d9c1c5ac7407 Mon Sep 17 00:00:00 2001 From: EternalRights Date: Sun, 10 May 2026 11:43:17 +0800 Subject: [PATCH 2/3] ci: trigger re-run From 8344ded8f522ace904b054ee538442f2114b69d5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 10 May 2026 03:44:24 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- changelog/14454.bugfix.rst | 2 +- src/_pytest/config/__init__.py | 2 +- testing/test_config.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/changelog/14454.bugfix.rst b/changelog/14454.bugfix.rst index c715956e4d0..005a1c29259 100644 --- a/changelog/14454.bugfix.rst +++ b/changelog/14454.bugfix.rst @@ -1 +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`. \ No newline at end of file +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 f856cbf3f55..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 @@ -77,7 +78,6 @@ from _pytest.warning_types import PytestConfigWarning from _pytest.warning_types import warn_explicit_for -import logging log = logging.getLogger(__name__) diff --git a/testing/test_config.py b/testing/test_config.py index 217fc114655..ec976786430 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -2052,8 +2052,7 @@ def test_config_in_subdir_does_not_change_rootdir( invocation_dir=Path.cwd(), ) assert rootpath == tmp_path, ( - f"rootdir should be invocation_dir ({tmp_path}), " - f"got {rootpath}" + f"rootdir should be invocation_dir ({tmp_path}), got {rootpath}" ) assert found_inipath == inipath