diff --git a/AUTHORS b/AUTHORS index ad7fccf6d59..26fa27f9b2a 100644 --- a/AUTHORS +++ b/AUTHORS @@ -454,6 +454,7 @@ Yusuke Kadowaki Yutian Li Yuval Shimon Zac Hatfield-Dodds +Zach Snicker Zachary Kneupper Zachary OBrien Zhouxin Qiu diff --git a/changelog/12544.improvement.rst b/changelog/12544.improvement.rst new file mode 100644 index 00000000000..9edbf7c4fec --- /dev/null +++ b/changelog/12544.improvement.rst @@ -0,0 +1,4 @@ +The _in_venv function now detects Python virtual environments by checking +for a pyvenv.cfg file, ensuring reliable detection on various platforms. + +-- by :user:`zachsnickers`. diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index 7c7b99d81c0..6926cd61bd3 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1702,13 +1702,13 @@ passed multiple times. The expected format is ``name=value``. For example:: This would tell ``pytest`` to not look into typical subversion or sphinx-build directories or into any ``tmp`` prefixed directory. - Additionally, ``pytest`` will attempt to intelligently identify and ignore a - virtualenv by the presence of an activation script. Any directory deemed to - be the root of a virtual environment will not be considered during test - collection unless ``--collect-in-virtualenv`` is given. Note also that - ``norecursedirs`` takes precedence over ``--collect-in-virtualenv``; e.g. if - you intend to run tests in a virtualenv with a base directory that matches - ``'.*'`` you *must* override ``norecursedirs`` in addition to using the + Additionally, ``pytest`` will attempt to intelligently identify and ignore + a virtualenv. Any directory deemed to be the root of a virtual environment + will not be considered during test collection unless + ``--collect-in-virtualenv`` is given. Note also that ``norecursedirs`` + takes precedence over ``--collect-in-virtualenv``; e.g. if you intend to + run tests in a virtualenv with a base directory that matches ``'.*'`` you + *must* override ``norecursedirs`` in addition to using the ``--collect-in-virtualenv`` flag. diff --git a/src/_pytest/main.py b/src/_pytest/main.py index 47ebad4713d..8ec26906003 100644 --- a/src/_pytest/main.py +++ b/src/_pytest/main.py @@ -369,22 +369,12 @@ def pytest_runtestloop(session: Session) -> bool: def _in_venv(path: Path) -> bool: """Attempt to detect if ``path`` is the root of a Virtual Environment by - checking for the existence of the appropriate activate script.""" - bindir = path.joinpath("Scripts" if sys.platform.startswith("win") else "bin") + checking for the existence of the pyvenv.cfg file. + [https://peps.python.org/pep-0405/]""" try: - if not bindir.is_dir(): - return False + return path.joinpath("pyvenv.cfg").is_file() except OSError: return False - activates = ( - "activate", - "activate.csh", - "activate.fish", - "Activate", - "Activate.bat", - "Activate.ps1", - ) - return any(fname.name in activates for fname in bindir.iterdir()) def pytest_ignore_collect(collection_path: Path, config: Config) -> bool | None: diff --git a/testing/test_collection.py b/testing/test_collection.py index 821c424196f..f5822240335 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -152,20 +152,8 @@ def test_ignored_certain_directories(self, pytester: Pytester) -> None: assert "test_notfound" not in s assert "test_found" in s - @pytest.mark.parametrize( - "fname", - ( - "activate", - "activate.csh", - "activate.fish", - "Activate", - "Activate.bat", - "Activate.ps1", - ), - ) - def test_ignored_virtualenvs(self, pytester: Pytester, fname: str) -> None: - bindir = "Scripts" if sys.platform.startswith("win") else "bin" - ensure_file(pytester.path / "virtual" / bindir / fname) + def test_ignored_virtualenvs(self, pytester: Pytester) -> None: + ensure_file(pytester.path / "virtual" / "pyvenv.cfg") testfile = ensure_file(pytester.path / "virtual" / "test_invenv.py") testfile.write_text("def test_hello(): pass", encoding="utf-8") @@ -179,23 +167,11 @@ def test_ignored_virtualenvs(self, pytester: Pytester, fname: str) -> None: result = pytester.runpytest("virtual") assert "test_invenv" in result.stdout.str() - @pytest.mark.parametrize( - "fname", - ( - "activate", - "activate.csh", - "activate.fish", - "Activate", - "Activate.bat", - "Activate.ps1", - ), - ) def test_ignored_virtualenvs_norecursedirs_precedence( - self, pytester: Pytester, fname: str + self, pytester: Pytester ) -> None: - bindir = "Scripts" if sys.platform.startswith("win") else "bin" # norecursedirs takes priority - ensure_file(pytester.path / ".virtual" / bindir / fname) + ensure_file(pytester.path / ".virtual" / "pyvenv.cfg") testfile = ensure_file(pytester.path / ".virtual" / "test_invenv.py") testfile.write_text("def test_hello(): pass", encoding="utf-8") result = pytester.runpytest("--collect-in-virtualenv") @@ -204,27 +180,13 @@ def test_ignored_virtualenvs_norecursedirs_precedence( result = pytester.runpytest("--collect-in-virtualenv", ".virtual") assert "test_invenv" in result.stdout.str() - @pytest.mark.parametrize( - "fname", - ( - "activate", - "activate.csh", - "activate.fish", - "Activate", - "Activate.bat", - "Activate.ps1", - ), - ) - def test__in_venv(self, pytester: Pytester, fname: str) -> None: + def test__in_venv(self, pytester: Pytester) -> None: """Directly test the virtual env detection function""" - bindir = "Scripts" if sys.platform.startswith("win") else "bin" - # no bin/activate, not a virtualenv + # no pyvenv.cfg, not a virtualenv base_path = pytester.mkdir("venv") assert _in_venv(base_path) is False - # with bin/activate, totally a virtualenv - bin_path = base_path.joinpath(bindir) - bin_path.mkdir() - bin_path.joinpath(fname).touch() + # with pyvenv.cfg, totally a virtualenv + base_path.joinpath("pyvenv.cfg").touch() assert _in_venv(base_path) is True def test_custom_norecursedirs(self, pytester: Pytester) -> None: