Merge pull request #2590 from nicoddemus/current-test-var

Introduce new PYTEST_CURRENT_TEST environment variable
This commit is contained in:
Florian Bruhin 2017-07-19 15:56:32 +02:00 committed by GitHub
commit eb79fa7825
7 changed files with 160 additions and 21 deletions

View File

@ -2,6 +2,7 @@
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
import bdb import bdb
import os
import sys import sys
from time import time from time import time
@ -91,9 +92,11 @@ def show_test_item(item):
tw.write(' (fixtures used: {0})'.format(', '.join(used_fixtures))) tw.write(' (fixtures used: {0})'.format(', '.join(used_fixtures)))
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
_update_current_test_var(item, 'setup')
item.session._setupstate.prepare(item) item.session._setupstate.prepare(item)
def pytest_runtest_call(item): def pytest_runtest_call(item):
_update_current_test_var(item, 'call')
try: try:
item.runtest() item.runtest()
except Exception: except Exception:
@ -107,7 +110,23 @@ def pytest_runtest_call(item):
raise raise
def pytest_runtest_teardown(item, nextitem): def pytest_runtest_teardown(item, nextitem):
_update_current_test_var(item, 'teardown')
item.session._setupstate.teardown_exact(item, nextitem) 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] = '{0} ({1})'.format(item.nodeid, when)
else:
os.environ.pop(var_name)
def pytest_report_teststatus(report): def pytest_report_teststatus(report):
if report.when in ("setup", "teardown"): if report.when in ("setup", "teardown"):

2
changelog/2583.feature Normal file
View File

@ -0,0 +1,2 @@
Introduce the ``PYTEST_CURRENT_TEST`` environment variable that is set with the ``nodeid`` and stage (``setup``, ``call`` and
``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.

View File

@ -112,15 +112,27 @@ progress output, you can write it into a configuration file:
# content of pytest.ini # content of pytest.ini
# (or tox.ini or setup.cfg) # (or tox.ini or setup.cfg)
[pytest] [pytest]
addopts = -rsxX -q addopts = -ra -q
Alternatively, you can set a PYTEST_ADDOPTS environment variable to add command Alternatively, you can set a ``PYTEST_ADDOPTS`` environment variable to add command
line options while the environment is in use:: line options while the environment is in use::
export PYTEST_ADDOPTS="-rsxX -q" export PYTEST_ADDOPTS="-v"
From now on, running ``pytest`` will add the specified options. Here's how the command-line is built in the presence of ``addopts`` or the environment variable::
<pytest.ini:addopts> $PYTEST_ADDOTPS <extra command-line arguments>
So if the user executes in the command-line::
pytest -m slow
The actual command line executed is::
pytest -ra -q -v -m slow
Note that as usual for other command-line applications, in case of conflicting options the last one wins, so the example
above will show verbose output because ``-v`` overwrites ``-q``.
Builtin configuration file options Builtin configuration file options

View File

@ -761,6 +761,47 @@ and run it::
You'll see that the fixture finalizers could use the precise reporting You'll see that the fixture finalizers could use the precise reporting
information. 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 the current stage, which can be ``setup``, ``call``
and ``teardown``.
For 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.
.. note::
The contents of ``PYTEST_CURRENT_TEST`` is meant to be human readable and the actual format
can be changed between releases (even bug fixes) so it shouldn't be relied on for scripting
or automation.
Freezing pytest Freezing pytest
--------------- ---------------

View File

@ -52,23 +52,64 @@ To stop the testing process after the first (N) failures::
Specifying tests / selecting tests Specifying tests / selecting tests
--------------------------------------------------- ---------------------------------------------------
Several test run options:: Pytest supports several ways to run and select tests from the command-line.
pytest test_mod.py # run tests in module **Run tests in a module**
pytest somepath # run all tests below somepath
pytest -k stringexpr # only run tests with names that match the
# "string expression", e.g. "MyClass and not method"
# will select TestMyClass.test_something
# but not TestMyClass.test_method_simple
pytest test_mod.py::test_func # only run tests that match the "node ID",
# e.g. "test_mod.py::test_func" will select
# only test_func in test_mod.py
pytest test_mod.py::TestClass::test_method # run a single method in
# a single class
Import 'pkg' and use its filesystem location to find and run tests:: ::
pytest test_mod.py
**Run tests in a directory**
::
pytest testing/
**Run tests by keyword expressions**
::
pytest -k "MyClass and not method"
This will run tests which contain names that match the given *string expression*, which can
include Python operators that use filenames, class names and function names as variables.
The example above will run ``TestMyClass.test_something`` but not ``TestMyClass.test_method_simple``.
.. _nodeids:
**Run tests by node ids**
Each collected test is assigned a unique ``nodeid`` which consist of the module filename followed
by specifiers like class names, function names and parameters from parametrization, separated by ``::`` characters.
To run a specific test within a module::
pytest test_mod.py::test_func
Another example specifying a test method in the command line::
pytest test_mod.py::TestClass::test_method
**Run tests by marker expressions**
::
pytest -m slow
Will run all tests which are decorated with the ``@pytest.mark.slow`` decorator.
For more information see :ref:`marks <mark>`.
**Run tests from packages**
::
pytest --pyargs pkg.testing
This will import ``pkg.testing`` and use its filesystem location to find and run tests from.
pytest --pyargs pkg # run all tests found below directory of pkg
Modifying Python traceback printing Modifying Python traceback printing
---------------------------------------------- ----------------------------------------------

View File

@ -681,6 +681,8 @@ def test_store_except_info_on_eror():
""" """
# Simulate item that raises a specific exception # Simulate item that raises a specific exception
class ItemThatRaises(object): class ItemThatRaises(object):
nodeid = 'item_that_raises'
def runtest(self): def runtest(self):
raise IndexError('TEST') raise IndexError('TEST')
try: try:
@ -693,6 +695,31 @@ def test_store_except_info_on_eror():
assert sys.last_traceback 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): class TestReportContents(object):
""" """
Test user-level API of ``TestReport`` objects. Test user-level API of ``TestReport`` objects.

View File

@ -49,9 +49,6 @@ commands=
skipsdist=True skipsdist=True
usedevelop=True usedevelop=True
basepython = python2.7 basepython = python2.7
# needed to keep check-manifest working
setenv =
SETUPTOOLS_SCM_PRETEND_VERSION=2.0.1
deps = deps =
flake8 flake8
# pygments required by rst-lint # pygments required by rst-lint