From 091550fe5ddb49991ce30b2ae386fe5c5e908034 Mon Sep 17 00:00:00 2001 From: hariharan077 Date: Tue, 9 Jun 2026 12:06:05 +0530 Subject: [PATCH] Fix rootdir with explicit config and test paths --- AUTHORS | 1 + changelog/9703.bugfix.rst | 1 + doc/en/reference/customize.rst | 5 +- src/_pytest/config/findpaths.py | 6 ++- testing/test_config.py | 91 +++++++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 changelog/9703.bugfix.rst diff --git a/AUTHORS b/AUTHORS index 972f39aa45e..a3855efadc8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -199,6 +199,7 @@ Guido Wesdorp Guoqiang Zhang Hamza Mobeen Harald Armin Massa +Hariharan Ganesh Harshna Henk-Jaap Wagenaar Holger Kohr diff --git a/changelog/9703.bugfix.rst b/changelog/9703.bugfix.rst new file mode 100644 index 00000000000..03aa6c9856e --- /dev/null +++ b/changelog/9703.bugfix.rst @@ -0,0 +1 @@ +Fixed ``rootdir`` determination when an explicit configuration file is used with test paths outside the configuration directory. diff --git a/doc/en/reference/customize.rst b/doc/en/reference/customize.rst index 8f781eab4a5..c1f09fc0812 100644 --- a/doc/en/reference/customize.rst +++ b/doc/en/reference/customize.rst @@ -187,7 +187,10 @@ Finding the ``rootdir`` Here is the algorithm which finds the rootdir from ``args``: -- If :option:`-c` is passed in the command-line, use that as configuration file, and its directory as ``rootdir``. +- If :option:`-c` is passed in the command-line, use that as configuration file. + When test paths are supplied, its directory participates in ``rootdir`` + determination together with those paths; otherwise, its directory becomes the + ``rootdir``. - Determine the common ancestor directory for the specified ``args`` that are recognised as paths that exist in the file system. If no such paths are diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index e74546c6e28..3110cacf0b3 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -308,7 +308,11 @@ 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 = ( + get_common_ancestor(invocation_dir, [inipath_.parent, *dirs]) + if dirs + else inipath_.parent + ) 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 7886610242d..5badf51ca20 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -2068,6 +2068,97 @@ def test_explicit_config_file_sets_rootdir( assert rootpath == tmp_path assert found_inipath == inipath + def test_explicit_config_file_uses_args_for_rootdir( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: + tests_dir = tmp_path / "tests" + tests_dir.mkdir() + 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=[str(tests_dir)], + rootdir_cmd_arg=None, + invocation_dir=Path.cwd(), + ) + assert rootpath == tmp_path + assert found_inipath == inipath + + def test_explicit_config_file_without_args_uses_config_dir( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ) -> None: + 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 == config_dir + assert found_inipath == inipath + + def test_explicit_config_file_uses_args_for_nodeids( + self, pytester: Pytester + ) -> None: + tests_dir = pytester.mkdir("tests") + tests_dir.joinpath("test_file1.py").write_text( + textwrap.dedent( + """ + import pytest + + @pytest.fixture(autouse=True) + def some_fixture(): + print("Fixture called") + + def test_in_file1(): + print("test_in_file1") + """ + ), + encoding="utf-8", + ) + tests_dir.joinpath("test_file2.py").write_text( + textwrap.dedent( + """ + def test_in_file2(): + print("test_in_file2") + """ + ), + encoding="utf-8", + ) + config_dir = pytester.mkdir("config") + config_dir.joinpath("pytest.ini").write_text("[pytest]\n", encoding="utf-8") + + result = pytester.runpytest( + "-c", + "config/pytest.ini", + "-s", + "-v", + "tests/test_file1.py", + "tests/test_file2.py", + ) + + assert result.ret == 0 + result.stdout.fnmatch_lines( + [ + "tests/test_file1.py::test_in_file1 Fixture called", + "tests/test_file2.py::test_in_file2 test_in_file2", + ] + ) + result.stdout.no_fnmatch_line("tests/test_file2.py::test_in_file2 Fixture*") + def test_with_arg_outside_cwd_without_inifile( self, tmp_path: Path, monkeypatch: MonkeyPatch ) -> None: