Skip to content

Commit 26ee235

Browse files
committed
Merge remote-tracking branch 'upstream/features' into fix-flake8-errors
2 parents 7b1870a + eb79fa7 commit 26ee235

File tree

11 files changed

+243
-30
lines changed

11 files changed

+243
-30
lines changed

_pytest/main.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ def pytest_addoption(parser):
7272
group.addoption('--keepduplicates', '--keep-duplicates', action="store_true",
7373
dest="keepduplicates", default=False,
7474
help="Keep duplicate tests.")
75+
group.addoption('--collect-in-virtualenv', action='store_true',
76+
dest='collect_in_virtualenv', default=False,
77+
help="Don't ignore tests in a local virtualenv directory")
7578

7679
group = parser.getgroup("debugconfig",
7780
"test session debugging and configuration")
@@ -168,6 +171,17 @@ def pytest_runtestloop(session):
168171
return True
169172

170173

174+
def _in_venv(path):
175+
"""Attempts to detect if ``path`` is the root of a Virtual Environment by
176+
checking for the existence of the appropriate activate script"""
177+
bindir = path.join('Scripts' if sys.platform.startswith('win') else 'bin')
178+
if not bindir.exists():
179+
return False
180+
activates = ('activate', 'activate.csh', 'activate.fish',
181+
'Activate', 'Activate.bat', 'Activate.ps1')
182+
return any([fname.basename in activates for fname in bindir.listdir()])
183+
184+
171185
def pytest_ignore_collect(path, config):
172186
ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath())
173187
ignore_paths = ignore_paths or []
@@ -178,6 +192,10 @@ def pytest_ignore_collect(path, config):
178192
if py.path.local(path) in ignore_paths:
179193
return True
180194

195+
allow_in_venv = config.getoption("collect_in_virtualenv")
196+
if _in_venv(path) and not allow_in_venv:
197+
return True
198+
181199
# Skip duplicate paths.
182200
keepduplicates = config.getoption("keepduplicates")
183201
duplicate_paths = config.pluginmanager._duplicatepaths

_pytest/mark.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from collections import namedtuple
77
from operator import attrgetter
88
from .compat import imap
9-
from .deprecated import MARK_INFO_ATTRIBUTE, MARK_PARAMETERSET_UNPACKING
9+
from .deprecated import MARK_PARAMETERSET_UNPACKING
1010

1111

1212
def alias(name, warning=None):
@@ -407,9 +407,9 @@ def __init__(self, mark):
407407
self.combined = mark
408408
self._marks = [mark]
409409

410-
name = alias('combined.name', warning=MARK_INFO_ATTRIBUTE)
411-
args = alias('combined.args', warning=MARK_INFO_ATTRIBUTE)
412-
kwargs = alias('combined.kwargs', warning=MARK_INFO_ATTRIBUTE)
410+
name = alias('combined.name')
411+
args = alias('combined.args')
412+
kwargs = alias('combined.kwargs')
413413

414414
def __repr__(self):
415415
return "<MarkInfo {0!r}>".format(self.combined)

_pytest/runner.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from __future__ import absolute_import, division, print_function
33

44
import bdb
5+
import os
56
import sys
67
from time import time
78

@@ -99,10 +100,12 @@ def show_test_item(item):
99100

100101

101102
def pytest_runtest_setup(item):
103+
_update_current_test_var(item, 'setup')
102104
item.session._setupstate.prepare(item)
103105

104106

105107
def pytest_runtest_call(item):
108+
_update_current_test_var(item, 'call')
106109
try:
107110
item.runtest()
108111
except Exception:
@@ -117,7 +120,22 @@ def pytest_runtest_call(item):
117120

118121

119122
def pytest_runtest_teardown(item, nextitem):
123+
_update_current_test_var(item, 'teardown')
120124
item.session._setupstate.teardown_exact(item, nextitem)
125+
_update_current_test_var(item, None)
126+
127+
128+
def _update_current_test_var(item, when):
129+
"""
130+
Update PYTEST_CURRENT_TEST to reflect the current item and stage.
131+
132+
If ``when`` is None, delete PYTEST_CURRENT_TEST from the environment.
133+
"""
134+
var_name = 'PYTEST_CURRENT_TEST'
135+
if when:
136+
os.environ[var_name] = '{0} ({1})'.format(item.nodeid, when)
137+
else:
138+
os.environ.pop(var_name)
121139

122140

123141
def pytest_report_teststatus(report):

changelog/2518.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Collection ignores local virtualenvs by default; `--collect-in-virtualenv` overrides this behavior.

changelog/2583.feature

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Introduce the ``PYTEST_CURRENT_TEST`` environment variable that is set with the ``nodeid`` and stage (``setup``, ``call`` and
2+
``teardown``) of the test being currently executed. See the `documentation <https://docs.pytest.org/en/latest/example/simple.html#pytest-current-test-environment-variable>`_ for more info.

doc/en/customize.rst

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -112,15 +112,27 @@ progress output, you can write it into a configuration file:
112112
# content of pytest.ini
113113
# (or tox.ini or setup.cfg)
114114
[pytest]
115-
addopts = -rsxX -q
115+
addopts = -ra -q
116116
117-
Alternatively, you can set a PYTEST_ADDOPTS environment variable to add command
117+
Alternatively, you can set a ``PYTEST_ADDOPTS`` environment variable to add command
118118
line options while the environment is in use::
119119

120-
export PYTEST_ADDOPTS="-rsxX -q"
120+
export PYTEST_ADDOPTS="-v"
121121

122-
From now on, running ``pytest`` will add the specified options.
122+
Here's how the command-line is built in the presence of ``addopts`` or the environment variable::
123123

124+
<pytest.ini:addopts> $PYTEST_ADDOTPS <extra command-line arguments>
125+
126+
So if the user executes in the command-line::
127+
128+
pytest -m slow
129+
130+
The actual command line executed is::
131+
132+
pytest -ra -q -v -m slow
133+
134+
Note that as usual for other command-line applications, in case of conflicting options the last one wins, so the example
135+
above will show verbose output because ``-v`` overwrites ``-q``.
124136

125137

126138
Builtin configuration file options
@@ -171,7 +183,16 @@ Builtin configuration file options
171183
norecursedirs = .svn _build tmp*
172184
173185
This would tell ``pytest`` to not look into typical subversion or
174-
sphinx-build directories or into any ``tmp`` prefixed directory.
186+
sphinx-build directories or into any ``tmp`` prefixed directory.
187+
188+
Additionally, ``pytest`` will attempt to intelligently identify and ignore a
189+
virtualenv by the presence of an activation script. Any directory deemed to
190+
be the root of a virtual environment will not be considered during test
191+
collection unless ``‑‑collect‑in‑virtualenv`` is given. Note also that
192+
``norecursedirs`` takes precedence over ``‑‑collect‑in‑virtualenv``; e.g. if
193+
you intend to run tests in a virtualenv with a base directory that matches
194+
``'.*'`` you *must* override ``norecursedirs`` in addition to using the
195+
``‑‑collect‑in‑virtualenv`` flag.
175196

176197
.. confval:: testpaths
177198

doc/en/example/simple.rst

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,47 @@ and run it::
761761
You'll see that the fixture finalizers could use the precise reporting
762762
information.
763763

764+
``PYTEST_CURRENT_TEST`` environment variable
765+
--------------------------------------------
766+
767+
.. versionadded:: 3.2
768+
769+
Sometimes a test session might get stuck and there might be no easy way to figure out
770+
which test got stuck, for example if pytest was run in quiet mode (``-q``) or you don't have access to the console
771+
output. This is particularly a problem if the problem helps only sporadically, the famous "flaky" kind of tests.
772+
773+
``pytest`` sets a ``PYTEST_CURRENT_TEST`` environment variable when running tests, which can be inspected
774+
by process monitoring utilities or libraries like `psutil <https://pypi.python.org/pypi/psutil>`_ to discover which
775+
test got stuck if necessary:
776+
777+
.. code-block:: python
778+
779+
import psutil
780+
781+
for pid in psutil.pids():
782+
environ = psutil.Process(pid).environ()
783+
if 'PYTEST_CURRENT_TEST' in environ:
784+
print(f'pytest process {pid} running: {environ["PYTEST_CURRENT_TEST"]}')
785+
786+
During the test session pytest will set ``PYTEST_CURRENT_TEST`` to the current test
787+
:ref:`nodeid <nodeids>` and the current stage, which can be ``setup``, ``call``
788+
and ``teardown``.
789+
790+
For example, when running a single test function named ``test_foo`` from ``foo_module.py``,
791+
``PYTEST_CURRENT_TEST`` will be set to:
792+
793+
#. ``foo_module.py::test_foo (setup)``
794+
#. ``foo_module.py::test_foo (call)``
795+
#. ``foo_module.py::test_foo (teardown)``
796+
797+
In that order.
798+
799+
.. note::
800+
801+
The contents of ``PYTEST_CURRENT_TEST`` is meant to be human readable and the actual format
802+
can be changed between releases (even bug fixes) so it shouldn't be relied on for scripting
803+
or automation.
804+
764805
Freezing pytest
765806
---------------
766807

doc/en/usage.rst

Lines changed: 58 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,64 @@ To stop the testing process after the first (N) failures::
5252
Specifying tests / selecting tests
5353
---------------------------------------------------
5454

55-
Several test run options::
56-
57-
pytest test_mod.py # run tests in module
58-
pytest somepath # run all tests below somepath
59-
pytest -k stringexpr # only run tests with names that match the
60-
# "string expression", e.g. "MyClass and not method"
61-
# will select TestMyClass.test_something
62-
# but not TestMyClass.test_method_simple
63-
pytest test_mod.py::test_func # only run tests that match the "node ID",
64-
# e.g. "test_mod.py::test_func" will select
65-
# only test_func in test_mod.py
66-
pytest test_mod.py::TestClass::test_method # run a single method in
67-
# a single class
68-
69-
Import 'pkg' and use its filesystem location to find and run tests::
70-
71-
pytest --pyargs pkg # run all tests found below directory of pkg
55+
Pytest supports several ways to run and select tests from the command-line.
56+
57+
**Run tests in a module**
58+
59+
::
60+
61+
pytest test_mod.py
62+
63+
**Run tests in a directory**
64+
65+
::
66+
67+
pytest testing/
68+
69+
**Run tests by keyword expressions**
70+
71+
::
72+
73+
pytest -k "MyClass and not method"
74+
75+
This will run tests which contain names that match the given *string expression*, which can
76+
include Python operators that use filenames, class names and function names as variables.
77+
The example above will run ``TestMyClass.test_something`` but not ``TestMyClass.test_method_simple``.
78+
79+
.. _nodeids:
80+
81+
**Run tests by node ids**
82+
83+
Each collected test is assigned a unique ``nodeid`` which consist of the module filename followed
84+
by specifiers like class names, function names and parameters from parametrization, separated by ``::`` characters.
85+
86+
To run a specific test within a module::
87+
88+
pytest test_mod.py::test_func
89+
90+
91+
Another example specifying a test method in the command line::
92+
93+
pytest test_mod.py::TestClass::test_method
94+
95+
**Run tests by marker expressions**
96+
97+
::
98+
99+
pytest -m slow
100+
101+
Will run all tests which are decorated with the ``@pytest.mark.slow`` decorator.
102+
103+
For more information see :ref:`marks <mark>`.
104+
105+
**Run tests from packages**
106+
107+
::
108+
109+
pytest --pyargs pkg.testing
110+
111+
This will import ``pkg.testing`` and use its filesystem location to find and run tests from.
112+
72113

73114
Modifying Python traceback printing
74115
----------------------------------------------

testing/test_collection.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import pytest
33
import py
44

5-
from _pytest.main import Session, EXIT_NOTESTSCOLLECTED
5+
from _pytest.main import Session, EXIT_NOTESTSCOLLECTED, _in_venv
66

77

88
class TestCollector(object):
@@ -123,6 +123,53 @@ def test_ignored_certain_directories(self, testdir):
123123
assert "test_notfound" not in s
124124
assert "test_found" in s
125125

126+
@pytest.mark.parametrize('fname',
127+
("activate", "activate.csh", "activate.fish",
128+
"Activate", "Activate.bat", "Activate.ps1"))
129+
def test_ignored_virtualenvs(self, testdir, fname):
130+
bindir = "Scripts" if py.std.sys.platform.startswith("win") else "bin"
131+
testdir.tmpdir.ensure("virtual", bindir, fname)
132+
testfile = testdir.tmpdir.ensure("virtual", "test_invenv.py")
133+
testfile.write("def test_hello(): pass")
134+
135+
# by default, ignore tests inside a virtualenv
136+
result = testdir.runpytest()
137+
assert "test_invenv" not in result.stdout.str()
138+
# allow test collection if user insists
139+
result = testdir.runpytest("--collect-in-virtualenv")
140+
assert "test_invenv" in result.stdout.str()
141+
# allow test collection if user directly passes in the directory
142+
result = testdir.runpytest("virtual")
143+
assert "test_invenv" in result.stdout.str()
144+
145+
@pytest.mark.parametrize('fname',
146+
("activate", "activate.csh", "activate.fish",
147+
"Activate", "Activate.bat", "Activate.ps1"))
148+
def test_ignored_virtualenvs_norecursedirs_precedence(self, testdir, fname):
149+
bindir = "Scripts" if py.std.sys.platform.startswith("win") else "bin"
150+
# norecursedirs takes priority
151+
testdir.tmpdir.ensure(".virtual", bindir, fname)
152+
testfile = testdir.tmpdir.ensure(".virtual", "test_invenv.py")
153+
testfile.write("def test_hello(): pass")
154+
result = testdir.runpytest("--collect-in-virtualenv")
155+
assert "test_invenv" not in result.stdout.str()
156+
# ...unless the virtualenv is explicitly given on the CLI
157+
result = testdir.runpytest("--collect-in-virtualenv", ".virtual")
158+
assert "test_invenv" in result.stdout.str()
159+
160+
@pytest.mark.parametrize('fname',
161+
("activate", "activate.csh", "activate.fish",
162+
"Activate", "Activate.bat", "Activate.ps1"))
163+
def test__in_venv(self, testdir, fname):
164+
"""Directly test the virtual env detection function"""
165+
bindir = "Scripts" if py.std.sys.platform.startswith("win") else "bin"
166+
# no bin/activate, not a virtualenv
167+
base_path = testdir.tmpdir.mkdir('venv')
168+
assert _in_venv(base_path) is False
169+
# with bin/activate, totally a virtualenv
170+
base_path.ensure(bindir, fname)
171+
assert _in_venv(base_path) is True
172+
126173
def test_custom_norecursedirs(self, testdir):
127174
testdir.makeini("""
128175
[pytest]

testing/test_runner.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,8 @@ def test_store_except_info_on_eror():
701701
"""
702702
# Simulate item that raises a specific exception
703703
class ItemThatRaises(object):
704+
nodeid = 'item_that_raises'
705+
704706
def runtest(self):
705707
raise IndexError('TEST')
706708
try:
@@ -713,6 +715,31 @@ def runtest(self):
713715
assert sys.last_traceback
714716

715717

718+
def test_current_test_env_var(testdir, monkeypatch):
719+
pytest_current_test_vars = []
720+
monkeypatch.setattr(sys, 'pytest_current_test_vars', pytest_current_test_vars, raising=False)
721+
testdir.makepyfile('''
722+
import pytest
723+
import sys
724+
import os
725+
726+
@pytest.fixture
727+
def fix():
728+
sys.pytest_current_test_vars.append(('setup', os.environ['PYTEST_CURRENT_TEST']))
729+
yield
730+
sys.pytest_current_test_vars.append(('teardown', os.environ['PYTEST_CURRENT_TEST']))
731+
732+
def test(fix):
733+
sys.pytest_current_test_vars.append(('call', os.environ['PYTEST_CURRENT_TEST']))
734+
''')
735+
result = testdir.runpytest_inprocess()
736+
assert result.ret == 0
737+
test_id = 'test_current_test_env_var.py::test'
738+
assert pytest_current_test_vars == [
739+
('setup', test_id + ' (setup)'), ('call', test_id + ' (call)'), ('teardown', test_id + ' (teardown)')]
740+
assert 'PYTEST_CURRENT_TEST' not in os.environ
741+
742+
716743
class TestReportContents(object):
717744
"""
718745
Test user-level API of ``TestReport`` objects.

0 commit comments

Comments
 (0)