Skip to content

Commit

Permalink
Introduce PYTEST_CURRENT_TEST environment variable
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoddemus committed Jul 19, 2017
1 parent 637e566 commit ba16e3c
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 0 deletions.
19 changes: 19 additions & 0 deletions _pytest/runner.py
Expand Up @@ -2,6 +2,7 @@
from __future__ import absolute_import, division, print_function

import bdb
import os
import sys
from time import time

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

def pytest_runtest_setup(item):
_update_current_test_var(item, 'setup')
item.session._setupstate.prepare(item)

def pytest_runtest_call(item):
_update_current_test_var(item, 'call')
try:
item.runtest()
except Exception:
Expand All @@ -107,7 +110,23 @@ def pytest_runtest_call(item):
raise

def pytest_runtest_teardown(item, nextitem):
_update_current_test_var(item, 'teardown')
item.session._setupstate.teardown_exact(item, nextitem)
_update_current_test_var(item, None)


def _update_current_test_var(item, when):
"""
Update PYTEST_CURRENT_TEST to reflect the current item and stage.
If ``when`` is None, delete PYTEST_CURRENT_TEST from the environment.
"""
var_name = 'PYTEST_CURRENT_TEST'
if when:
os.environ[var_name] = item.nodeid + ',' + when
else:
os.environ.pop(var_name)


def pytest_report_teststatus(report):
if report.when in ("setup", "teardown"):
Expand Down
2 changes: 2 additions & 0 deletions changelog/2583.feature
@@ -0,0 +1,2 @@
The ``PYTEST_CURRENT_TEST`` environment variable is now set with the test's ``nodeid`` and stage (``setup``, ``call`` and
``teardown``) during test execution. See the `documentation <https://docs.pytest.org/en/latest/example/simple.html#pytest-current-test-environment-variable>`_ for more info.
35 changes: 35 additions & 0 deletions doc/en/example/simple.rst
Expand Up @@ -761,6 +761,41 @@ and run it::
You'll see that the fixture finalizers could use the precise reporting
information.

``PYTEST_CURRENT_TEST`` environment variable
--------------------------------------------

.. versionadded:: 3.2

Sometimes a test session might get stuck and there might be no easy way to figure out
which test got stuck, for example if pytest was run in quiet mode (``-q``) or you don't have access to the console
output. This is particularly a problem if the problem helps only sporadically, the famous "flaky" kind of tests.

``pytest`` sets a ``PYTEST_CURRENT_TEST`` environment variable when running tests, which can be inspected
by process monitoring utilities or libraries like `psutil <https://pypi.python.org/pypi/psutil>`_ to discover which
test got stuck if necessary:

.. code-block:: python
import psutil
for pid in psutil.pids():
environ = psutil.Process(pid).environ()
if 'PYTEST_CURRENT_TEST' in environ:
print(f'pytest process {pid} running: {environ["PYTEST_CURRENT_TEST"]}')
During the test session pytest will set ``PYTEST_CURRENT_TEST`` to the current test
:ref:`nodeid <nodeids>` and stage, separated by ``,``. Each stage can be ``setup``, ``call``
and ``teardown``.

As an example, when running a single test function named ``test_foo`` from ``foo_module.py``,
``PYTEST_CURRENT_TEST`` will be set to:

#. ``foo_module.py::test_foo,setup``
#. ``foo_module.py::test_foo,call``
#. ``foo_module.py::test_foo,teardown``

In that order.

Freezing pytest
---------------

Expand Down
27 changes: 27 additions & 0 deletions testing/test_runner.py
Expand Up @@ -681,6 +681,8 @@ def test_store_except_info_on_eror():
"""
# Simulate item that raises a specific exception
class ItemThatRaises(object):
nodeid = 'item_that_raises'

def runtest(self):
raise IndexError('TEST')
try:
Expand All @@ -693,6 +695,31 @@ def runtest(self):
assert sys.last_traceback


def test_current_test_env_var(testdir, monkeypatch):
pytest_current_test_vars = []
monkeypatch.setattr(sys, 'pytest_current_test_vars', pytest_current_test_vars, raising=False)
testdir.makepyfile('''
import pytest
import sys
import os
@pytest.fixture
def fix():
sys.pytest_current_test_vars.append(('setup', os.environ['PYTEST_CURRENT_TEST']))
yield
sys.pytest_current_test_vars.append(('teardown', os.environ['PYTEST_CURRENT_TEST']))
def test(fix):
sys.pytest_current_test_vars.append(('call', os.environ['PYTEST_CURRENT_TEST']))
''')
result = testdir.runpytest_inprocess()
assert result.ret == 0
test_id = 'test_current_test_env_var.py::test'
assert pytest_current_test_vars == [
('setup', test_id + ',setup'), ('call', test_id + ',call'), ('teardown', test_id + ',teardown')]
assert 'PYTEST_CURRENT_TEST' not in os.environ


class TestReportContents(object):
"""
Test user-level API of ``TestReport`` objects.
Expand Down

0 comments on commit ba16e3c

Please sign in to comment.