From 89c73582caf9dda84f237bd6d4986d4db7d11a2e Mon Sep 17 00:00:00 2001 From: John Still Date: Tue, 11 Jul 2017 11:52:16 -0500 Subject: [PATCH 1/5] ignore the active python installation unless told otherwise --- _pytest/main.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/_pytest/main.py b/_pytest/main.py index 1a6ba2781..5b6409664 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -70,6 +70,8 @@ def pytest_addoption(parser): group.addoption('--keepduplicates', '--keep-duplicates', action="store_true", dest="keepduplicates", default=False, help="Keep duplicate tests.") + group.addoption('--collect-in-virtualenv', action='store_true', + help="Collect tests in the current Python installation (default False)") group = parser.getgroup("debugconfig", "test session debugging and configuration") @@ -177,6 +179,16 @@ def pytest_ignore_collect(path, config): if py.path.local(path) in ignore_paths: return True + invenv = py.path.local(sys.prefix) == path + allow_invenv = config.getoption("collect_in_virtualenv") + if invenv and not allow_invenv: + config.warn(RuntimeWarning, + 'Path "%s" appears to be a Python installation; skipping\n' + 'Pass --collect-in-virtualenv to force collection of tests in "%s"\n' + 'Use --ignore="%s" to silence this warning' % (path, path, path) + ) + return True + # Skip duplicate paths. keepduplicates = config.getoption("keepduplicates") duplicate_paths = config.pluginmanager._duplicatepaths From c2d49e39a2603f3ee8ec3a0e13be4afc3303aca1 Mon Sep 17 00:00:00 2001 From: John Still Date: Tue, 11 Jul 2017 13:01:56 -0500 Subject: [PATCH 2/5] add news item --- changelog/2518.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/2518.feature diff --git a/changelog/2518.feature b/changelog/2518.feature new file mode 100644 index 000000000..3b97cc18f --- /dev/null +++ b/changelog/2518.feature @@ -0,0 +1 @@ +Collection ignores the currently active Python installation by default; `--collect-in-virtualenv` overrides this behavior. From 676c4f970d2d2f3dcece052adc65491e1f9e1588 Mon Sep 17 00:00:00 2001 From: John Still Date: Tue, 11 Jul 2017 13:31:11 -0500 Subject: [PATCH 3/5] trim trailing ws --- _pytest/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_pytest/main.py b/_pytest/main.py index 5b6409664..bd33ab95f 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -179,7 +179,7 @@ def pytest_ignore_collect(path, config): if py.path.local(path) in ignore_paths: return True - invenv = py.path.local(sys.prefix) == path + invenv = py.path.local(sys.prefix) == path allow_invenv = config.getoption("collect_in_virtualenv") if invenv and not allow_invenv: config.warn(RuntimeWarning, From b32cfc88daad55f6518fc828db7aa770d4e4c80a Mon Sep 17 00:00:00 2001 From: John Still Date: Tue, 11 Jul 2017 14:32:09 -0500 Subject: [PATCH 4/5] use presence of activate script rather than sys.prefix to determine if a dir is a virtualenv --- _pytest/main.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/_pytest/main.py b/_pytest/main.py index bd33ab95f..caf2ca813 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -169,6 +169,17 @@ def pytest_runtestloop(session): return True +def _in_venv(path): + """Attempts to detect if ``path`` is the root of a Virtual Environment by + checking for the existence of the appropriate activate script""" + bindir = path.join('Scripts' if sys.platform.startswith('win') else 'bin') + if not bindir.exists(): + return False + activates = ('activate', 'activate.csh', 'activate.fish', + 'Activate', 'Activate.bat', 'Activate.ps1') + return any([fname.basename in activates for fname in bindir.listdir()]) + + def pytest_ignore_collect(path, config): ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath()) ignore_paths = ignore_paths or [] @@ -179,11 +190,10 @@ def pytest_ignore_collect(path, config): if py.path.local(path) in ignore_paths: return True - invenv = py.path.local(sys.prefix) == path - allow_invenv = config.getoption("collect_in_virtualenv") - if invenv and not allow_invenv: + allow_in_venv = config.getoption("collect_in_virtualenv") + if _in_venv(path) and not allow_in_venv: config.warn(RuntimeWarning, - 'Path "%s" appears to be a Python installation; skipping\n' + 'Path "%s" appears to be a Python virtual installation; skipping\n' 'Pass --collect-in-virtualenv to force collection of tests in "%s"\n' 'Use --ignore="%s" to silence this warning' % (path, path, path) ) From 67fca040503b30ff7d23f6962c1387c8f621c80c Mon Sep 17 00:00:00 2001 From: John Still Date: Tue, 11 Jul 2017 23:14:38 -0500 Subject: [PATCH 5/5] update docs and note; add virtualenv collection tests --- _pytest/main.py | 8 ++----- changelog/2518.feature | 2 +- doc/en/customize.rst | 11 ++++++++- testing/test_collection.py | 49 +++++++++++++++++++++++++++++++++++++- 4 files changed, 61 insertions(+), 9 deletions(-) diff --git a/_pytest/main.py b/_pytest/main.py index caf2ca813..a7ecc5149 100644 --- a/_pytest/main.py +++ b/_pytest/main.py @@ -71,7 +71,8 @@ def pytest_addoption(parser): dest="keepduplicates", default=False, help="Keep duplicate tests.") group.addoption('--collect-in-virtualenv', action='store_true', - help="Collect tests in the current Python installation (default False)") + dest='collect_in_virtualenv', default=False, + help="Don't ignore tests in a local virtualenv directory") group = parser.getgroup("debugconfig", "test session debugging and configuration") @@ -192,11 +193,6 @@ def pytest_ignore_collect(path, config): allow_in_venv = config.getoption("collect_in_virtualenv") if _in_venv(path) and not allow_in_venv: - config.warn(RuntimeWarning, - 'Path "%s" appears to be a Python virtual installation; skipping\n' - 'Pass --collect-in-virtualenv to force collection of tests in "%s"\n' - 'Use --ignore="%s" to silence this warning' % (path, path, path) - ) return True # Skip duplicate paths. diff --git a/changelog/2518.feature b/changelog/2518.feature index 3b97cc18f..2f6597a97 100644 --- a/changelog/2518.feature +++ b/changelog/2518.feature @@ -1 +1 @@ -Collection ignores the currently active Python installation by default; `--collect-in-virtualenv` overrides this behavior. +Collection ignores local virtualenvs by default; `--collect-in-virtualenv` overrides this behavior. diff --git a/doc/en/customize.rst b/doc/en/customize.rst index ce0a36c11..1920028a1 100644 --- a/doc/en/customize.rst +++ b/doc/en/customize.rst @@ -171,7 +171,16 @@ Builtin configuration file options norecursedirs = .svn _build tmp* This would tell ``pytest`` to not look into typical subversion or - sphinx-build directories or into any ``tmp`` prefixed directory. + 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 + ``‑‑collect‑in‑virtualenv`` flag. .. confval:: testpaths diff --git a/testing/test_collection.py b/testing/test_collection.py index a90269789..a3c323e61 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1,7 +1,7 @@ from __future__ import absolute_import, division, print_function import pytest, py -from _pytest.main import Session, EXIT_NOTESTSCOLLECTED +from _pytest.main import Session, EXIT_NOTESTSCOLLECTED, _in_venv class TestCollector(object): def test_collect_versus_item(self): @@ -121,6 +121,53 @@ class TestCollectFS(object): 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, testdir, fname): + bindir = "Scripts" if py.std.sys.platform.startswith("win") else "bin" + testdir.tmpdir.ensure("virtual", bindir, fname) + testfile = testdir.tmpdir.ensure("virtual", "test_invenv.py") + testfile.write("def test_hello(): pass") + + # by default, ignore tests inside a virtualenv + result = testdir.runpytest() + assert "test_invenv" not in result.stdout.str() + # allow test collection if user insists + result = testdir.runpytest("--collect-in-virtualenv") + assert "test_invenv" in result.stdout.str() + # allow test collection if user directly passes in the directory + result = testdir.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, testdir, fname): + bindir = "Scripts" if py.std.sys.platform.startswith("win") else "bin" + # norecursedirs takes priority + testdir.tmpdir.ensure(".virtual", bindir, fname) + testfile = testdir.tmpdir.ensure(".virtual", "test_invenv.py") + testfile.write("def test_hello(): pass") + result = testdir.runpytest("--collect-in-virtualenv") + assert "test_invenv" not in result.stdout.str() + # ...unless the virtualenv is explicitly given on the CLI + result = testdir.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, testdir, fname): + """Directly test the virtual env detection function""" + bindir = "Scripts" if py.std.sys.platform.startswith("win") else "bin" + # no bin/activate, not a virtualenv + base_path = testdir.tmpdir.mkdir('venv') + assert _in_venv(base_path) is False + # with bin/activate, totally a virtualenv + base_path.ensure(bindir, fname) + assert _in_venv(base_path) is True + def test_custom_norecursedirs(self, testdir): testdir.makeini(""" [pytest]