diff --git a/src/_pytest/config/findpaths.py b/src/_pytest/config/findpaths.py index 3f08c8121a9..baa322e15ee 100644 --- a/src/_pytest/config/findpaths.py +++ b/src/_pytest/config/findpaths.py @@ -5,6 +5,7 @@ import os from pathlib import Path import sys +import tempfile from typing import TypeAlias import iniconfig @@ -184,6 +185,28 @@ def get_dir_from_path(path: Path) -> Path: CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead." +def _is_system_temp_or_parent(path: Path) -> bool: + """Check if path is the system temp directory. + + This prevents pytest from treating /tmp as a valid rootdir when + a /tmp/setup.py file exists on the system. + + Args: + path: The path to check + + Returns: + True if path is the system temp directory, False otherwise + """ + try: + system_temp = Path(tempfile.gettempdir()).resolve() + path = path.resolve() + # Skip exact matches with the system temp directory + return path == system_temp + except (OSError, RuntimeError): + # If we can't resolve paths, play it safe and don't skip + return False + + def determine_setup( *, inifile: str | None, @@ -220,6 +243,9 @@ def determine_setup( ) if rootdir is None and rootdir_cmd_arg is None: for possible_rootdir in (ancestor, *ancestor.parents): + # Skip system temp directories to avoid false positives (issue #13822) + if _is_system_temp_or_parent(possible_rootdir): + continue if (possible_rootdir / "setup.py").is_file(): rootdir = possible_rootdir break diff --git a/testing/test_config.py b/testing/test_config.py index 9efac8739b7..4241de44fe4 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -1830,6 +1830,42 @@ def test_with_config_also_in_parent_directory( assert rootpath == tmp_path / "myproject" assert inipath == tmp_path / "myproject" / "setup.cfg" + def test_tmp_setup_py_does_not_affect_rootdir( + self, tmp_path: Path, monkeypatch: MonkeyPatch + ) -> None: + """Regression test for issue #13822. + + Ensure that setup.py in the system temp directory doesn't + affect rootdir detection. + """ + # Create a fake project structure + project = tmp_path / "my_project" + project.mkdir() + test_dir = project / "tests" + test_dir.mkdir() + + # Create project's own setup.py + (project / "setup.py").write_text("# project setup", "utf-8") + + # Monkeypatch tempfile.gettempdir() to simulate /tmp/setup.py existing + fake_temp = tmp_path / "fake_tmp" + fake_temp.mkdir() + (fake_temp / "setup.py").write_text("# tmp setup", "utf-8") + + monkeypatch.setattr("tempfile.gettempdir", lambda: str(fake_temp)) + monkeypatch.chdir(test_dir) + + rootpath, inipath, *_ = determine_setup( + inifile=None, + args=[str(test_dir)], + rootdir_cmd_arg=None, + invocation_dir=test_dir, + ) + + # Rootpath should be project, not fake temp + assert rootpath == project + assert rootpath != fake_temp + class TestOverrideIniArgs: @pytest.mark.parametrize("name", "setup.cfg tox.ini pytest.ini".split())