Skip to content

Commit 2d4f1f0

Browse files
committed
Introduce PYTEST_CURRENT_TEST environment variable
Fix #2583
1 parent 637e566 commit 2d4f1f0

File tree

4 files changed

+89
-0
lines changed

4 files changed

+89
-0
lines changed

_pytest/runner.py

Lines changed: 19 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

@@ -91,9 +92,11 @@ def show_test_item(item):
9192
tw.write(' (fixtures used: {0})'.format(', '.join(used_fixtures)))
9293

9394
def pytest_runtest_setup(item):
95+
_update_current_test_var(item, 'setup')
9496
item.session._setupstate.prepare(item)
9597

9698
def pytest_runtest_call(item):
99+
_update_current_test_var(item, 'call')
97100
try:
98101
item.runtest()
99102
except Exception:
@@ -107,7 +110,23 @@ def pytest_runtest_call(item):
107110
raise
108111

109112
def pytest_runtest_teardown(item, nextitem):
113+
_update_current_test_var(item, 'teardown')
110114
item.session._setupstate.teardown_exact(item, nextitem)
115+
_update_current_test_var(item, None)
116+
117+
118+
def _update_current_test_var(item, when):
119+
"""
120+
Update PYTEST_CURRENT_TEST to reflect the current item and stage.
121+
122+
If ``when`` is None, delete PYTEST_CURRENT_TEST from the environment.
123+
"""
124+
var_name = 'PYTEST_CURRENT_TEST'
125+
if when:
126+
os.environ[var_name] = '{0} ({1})'.format(item.nodeid, when)
127+
else:
128+
os.environ.pop(var_name)
129+
111130

112131
def pytest_report_teststatus(report):
113132
if report.when in ("setup", "teardown"):

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/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

testing/test_runner.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,8 @@ def test_store_except_info_on_eror():
681681
"""
682682
# Simulate item that raises a specific exception
683683
class ItemThatRaises(object):
684+
nodeid = 'item_that_raises'
685+
684686
def runtest(self):
685687
raise IndexError('TEST')
686688
try:
@@ -693,6 +695,31 @@ def runtest(self):
693695
assert sys.last_traceback
694696

695697

698+
def test_current_test_env_var(testdir, monkeypatch):
699+
pytest_current_test_vars = []
700+
monkeypatch.setattr(sys, 'pytest_current_test_vars', pytest_current_test_vars, raising=False)
701+
testdir.makepyfile('''
702+
import pytest
703+
import sys
704+
import os
705+
706+
@pytest.fixture
707+
def fix():
708+
sys.pytest_current_test_vars.append(('setup', os.environ['PYTEST_CURRENT_TEST']))
709+
yield
710+
sys.pytest_current_test_vars.append(('teardown', os.environ['PYTEST_CURRENT_TEST']))
711+
712+
def test(fix):
713+
sys.pytest_current_test_vars.append(('call', os.environ['PYTEST_CURRENT_TEST']))
714+
''')
715+
result = testdir.runpytest_inprocess()
716+
assert result.ret == 0
717+
test_id = 'test_current_test_env_var.py::test'
718+
assert pytest_current_test_vars == [
719+
('setup', test_id + ' (setup)'), ('call', test_id + ' (call)'), ('teardown', test_id + ' (teardown)')]
720+
assert 'PYTEST_CURRENT_TEST' not in os.environ
721+
722+
696723
class TestReportContents(object):
697724
"""
698725
Test user-level API of ``TestReport`` objects.

0 commit comments

Comments
 (0)