Merge remote-tracking branch 'origin/features' into short-summary-message
Conflicts: src/_pytest/skipping.py
This commit is contained in:
commit
df1d1105b0
|
@ -1,16 +1,16 @@
|
|||
exclude: doc/en/example/py2py3/test_py2.py
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 18.9b0
|
||||
rev: 19.3b0
|
||||
hooks:
|
||||
- id: black
|
||||
args: [--safe, --quiet]
|
||||
language_version: python3
|
||||
- repo: https://github.com/asottile/blacken-docs
|
||||
rev: v0.3.0
|
||||
rev: v0.5.0
|
||||
hooks:
|
||||
- id: blacken-docs
|
||||
additional_dependencies: [black==18.9b0]
|
||||
additional_dependencies: [black==19.3b0]
|
||||
language_version: python3
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v2.1.0
|
||||
|
@ -22,22 +22,22 @@ repos:
|
|||
exclude: _pytest/debugging.py
|
||||
language_version: python3
|
||||
- repo: https://gitlab.com/pycqa/flake8
|
||||
rev: 3.7.0
|
||||
rev: 3.7.7
|
||||
hooks:
|
||||
- id: flake8
|
||||
language_version: python3
|
||||
- repo: https://github.com/asottile/reorder_python_imports
|
||||
rev: v1.3.5
|
||||
rev: v1.4.0
|
||||
hooks:
|
||||
- id: reorder-python-imports
|
||||
args: ['--application-directories=.:src']
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v1.11.1
|
||||
rev: v1.15.0
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--keep-percent-format]
|
||||
- repo: https://github.com/pre-commit/pygrep-hooks
|
||||
rev: v1.2.0
|
||||
rev: v1.3.0
|
||||
hooks:
|
||||
- id: rst-backticks
|
||||
- repo: local
|
||||
|
|
1
AUTHORS
1
AUTHORS
|
@ -208,6 +208,7 @@ Ross Lawley
|
|||
Russel Winder
|
||||
Ryan Wooden
|
||||
Samuel Dion-Girardeau
|
||||
Samuel Searles-Bryant
|
||||
Samuele Pedroni
|
||||
Sankt Petersbug
|
||||
Segev Finer
|
||||
|
|
|
@ -18,6 +18,24 @@ with advance notice in the **Deprecations** section of releases.
|
|||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 4.4.1 (2019-04-15)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#5031 <https://github.com/pytest-dev/pytest/issues/5031>`_: Environment variables are properly restored when using pytester's ``testdir`` fixture.
|
||||
|
||||
|
||||
- `#5039 <https://github.com/pytest-dev/pytest/issues/5039>`_: Fix regression with ``--pdbcls``, which stopped working with local modules in 4.0.0.
|
||||
|
||||
|
||||
- `#5092 <https://github.com/pytest-dev/pytest/issues/5092>`_: Produce a warning when unknown keywords are passed to ``pytest.param(...)``.
|
||||
|
||||
|
||||
- `#5098 <https://github.com/pytest-dev/pytest/issues/5098>`_: Invalidate import caches with ``monkeypatch.syspath_prepend``, which is required with namespace packages being used.
|
||||
|
||||
|
||||
pytest 4.4.0 (2019-03-29)
|
||||
=========================
|
||||
|
||||
|
|
|
@ -16,4 +16,4 @@ run = 'fc("/d")'
|
|||
|
||||
if __name__ == "__main__":
|
||||
print(timeit.timeit(run, setup=setup % imports[0], number=count))
|
||||
print((timeit.timeit(run, setup=setup % imports[1], number=count)))
|
||||
print(timeit.timeit(run, setup=setup % imports[1], number=count))
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Show XFail reason as part of JUnitXML message field.
|
|
@ -0,0 +1 @@
|
|||
Assertion failure messages for sequences and dicts contain the number of different items now.
|
|
@ -1 +0,0 @@
|
|||
Environment variables are properly restored when using pytester's ``testdir`` fixture.
|
|
@ -0,0 +1 @@
|
|||
The ``--cache-show`` option/action accepts an optional glob to show only matching cache entries.
|
|
@ -0,0 +1 @@
|
|||
Standard input (stdin) can be given to pytester's ``Testdir.run()`` and ``Testdir.popen()``.
|
|
@ -0,0 +1 @@
|
|||
pytester's ``Testdir.popen()`` uses ``stdout`` and ``stderr`` via keyword arguments with defaults now (``subprocess.PIPE``).
|
|
@ -0,0 +1 @@
|
|||
The ``-r`` option learnt about ``A`` to display all reports (including passed ones) in the short test summary.
|
|
@ -0,0 +1 @@
|
|||
The code for the short test summary in the terminal was moved to the terminal plugin.
|
|
@ -0,0 +1 @@
|
|||
Improved validation of kwargs for various methods in the pytester plugin.
|
|
@ -0,0 +1 @@
|
|||
The short test summary is displayed after passes with output (``-rP``).
|
|
@ -6,6 +6,7 @@ Release announcements
|
|||
:maxdepth: 2
|
||||
|
||||
|
||||
release-4.4.1
|
||||
release-4.4.0
|
||||
release-4.3.1
|
||||
release-4.3.0
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
pytest-4.4.1
|
||||
=======================================
|
||||
|
||||
pytest 4.4.1 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at https://docs.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Daniel Hahler
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
|
@ -12,12 +12,15 @@ Asserting with the ``assert`` statement
|
|||
|
||||
``pytest`` allows you to use the standard python ``assert`` for verifying
|
||||
expectations and values in Python tests. For example, you can write the
|
||||
following::
|
||||
following:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_assert1.py
|
||||
def f():
|
||||
return 3
|
||||
|
||||
|
||||
def test_function():
|
||||
assert f() == 4
|
||||
|
||||
|
@ -30,7 +33,7 @@ you will see the return value of the function call:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_assert1.py F [100%]
|
||||
|
@ -43,7 +46,7 @@ you will see the return value of the function call:
|
|||
E assert 3 == 4
|
||||
E + where 3 = f()
|
||||
|
||||
test_assert1.py:5: AssertionError
|
||||
test_assert1.py:6: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
|
||||
``pytest`` has support for showing the values of the most common subexpressions
|
||||
|
@ -52,7 +55,9 @@ operators. (See :ref:`tbreportdemo`). This allows you to use the
|
|||
idiomatic python constructs without boilerplate code while not losing
|
||||
introspection information.
|
||||
|
||||
However, if you specify a message with the assertion like this::
|
||||
However, if you specify a message with the assertion like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
assert a % 2 == 0, "value was odd, should be even"
|
||||
|
||||
|
@ -67,22 +72,29 @@ Assertions about expected exceptions
|
|||
------------------------------------------
|
||||
|
||||
In order to write assertions about raised exceptions, you can use
|
||||
``pytest.raises`` as a context manager like this::
|
||||
``pytest.raises`` as a context manager like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_zero_division():
|
||||
with pytest.raises(ZeroDivisionError):
|
||||
1 / 0
|
||||
|
||||
and if you need to have access to the actual exception info you may use::
|
||||
and if you need to have access to the actual exception info you may use:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_recursion_depth():
|
||||
with pytest.raises(RuntimeError) as excinfo:
|
||||
|
||||
def f():
|
||||
f()
|
||||
|
||||
f()
|
||||
assert 'maximum recursion' in str(excinfo.value)
|
||||
assert "maximum recursion" in str(excinfo.value)
|
||||
|
||||
``excinfo`` is a ``ExceptionInfo`` instance, which is a wrapper around
|
||||
the actual exception raised. The main attributes of interest are
|
||||
|
@ -90,15 +102,19 @@ the actual exception raised. The main attributes of interest are
|
|||
|
||||
You can pass a ``match`` keyword parameter to the context-manager to test
|
||||
that a regular expression matches on the string representation of an exception
|
||||
(similar to the ``TestCase.assertRaisesRegexp`` method from ``unittest``)::
|
||||
(similar to the ``TestCase.assertRaisesRegexp`` method from ``unittest``):
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def myfunc():
|
||||
raise ValueError("Exception 123 raised")
|
||||
|
||||
|
||||
def test_match():
|
||||
with pytest.raises(ValueError, match=r'.* 123 .*'):
|
||||
with pytest.raises(ValueError, match=r".* 123 .*"):
|
||||
myfunc()
|
||||
|
||||
The regexp parameter of the ``match`` method is matched with the ``re.search``
|
||||
|
@ -107,7 +123,9 @@ well.
|
|||
|
||||
There's an alternate form of the ``pytest.raises`` function where you pass
|
||||
a function that will be executed with the given ``*args`` and ``**kwargs`` and
|
||||
assert that the given exception is raised::
|
||||
assert that the given exception is raised:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytest.raises(ExpectedException, func, *args, **kwargs)
|
||||
|
||||
|
@ -116,7 +134,9 @@ exception* or *wrong exception*.
|
|||
|
||||
Note that it is also possible to specify a "raises" argument to
|
||||
``pytest.mark.xfail``, which checks that the test is failing in a more
|
||||
specific way than just having any exception raised::
|
||||
specific way than just having any exception raised:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(raises=IndexError)
|
||||
def test_f():
|
||||
|
@ -148,10 +168,13 @@ Making use of context-sensitive comparisons
|
|||
.. versionadded:: 2.0
|
||||
|
||||
``pytest`` has rich support for providing context-sensitive information
|
||||
when it encounters comparisons. For example::
|
||||
when it encounters comparisons. For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_assert2.py
|
||||
|
||||
|
||||
def test_set_comparison():
|
||||
set1 = set("1308")
|
||||
set2 = set("8035")
|
||||
|
@ -165,7 +188,7 @@ if you run this module:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_assert2.py F [100%]
|
||||
|
@ -184,7 +207,7 @@ if you run this module:
|
|||
E '5'
|
||||
E Use -v to get the full diff
|
||||
|
||||
test_assert2.py:5: AssertionError
|
||||
test_assert2.py:6: AssertionError
|
||||
========================= 1 failed in 0.12 seconds =========================
|
||||
|
||||
Special comparisons are done for a number of cases:
|
||||
|
@ -205,16 +228,21 @@ the ``pytest_assertrepr_compare`` hook.
|
|||
:noindex:
|
||||
|
||||
As an example consider adding the following hook in a :ref:`conftest.py <conftest.py>`
|
||||
file which provides an alternative explanation for ``Foo`` objects::
|
||||
file which provides an alternative explanation for ``Foo`` objects:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
from test_foocompare import Foo
|
||||
|
||||
|
||||
def pytest_assertrepr_compare(op, left, right):
|
||||
if isinstance(left, Foo) and isinstance(right, Foo) and op == "==":
|
||||
return ['Comparing Foo instances:',
|
||||
' vals: %s != %s' % (left.val, right.val)]
|
||||
return ["Comparing Foo instances:", " vals: %s != %s" % (left.val, right.val)]
|
||||
|
||||
now, given this test module::
|
||||
now, given this test module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_foocompare.py
|
||||
class Foo(object):
|
||||
|
@ -224,6 +252,7 @@ now, given this test module::
|
|||
def __eq__(self, other):
|
||||
return self.val == other.val
|
||||
|
||||
|
||||
def test_compare():
|
||||
f1 = Foo(1)
|
||||
f2 = Foo(2)
|
||||
|
@ -246,7 +275,7 @@ the conftest file:
|
|||
E assert Comparing Foo instances:
|
||||
E vals: 1 != 2
|
||||
|
||||
test_foocompare.py:11: AssertionError
|
||||
test_foocompare.py:12: AssertionError
|
||||
1 failed in 0.12 seconds
|
||||
|
||||
.. _assert-details:
|
||||
|
|
|
@ -82,7 +82,7 @@ If you then run it with ``--lf``:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 50 items / 48 deselected / 2 selected
|
||||
run-last-failure: rerun previous 2 failures
|
||||
|
||||
|
@ -126,7 +126,7 @@ of ``FF`` and dots):
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 50 items
|
||||
run-last-failure: rerun previous 2 failures first
|
||||
|
||||
|
@ -247,7 +247,7 @@ See the :ref:`cache-api` for more details.
|
|||
|
||||
|
||||
Inspecting Cache content
|
||||
-------------------------------
|
||||
------------------------
|
||||
|
||||
You can always peek at the content of the cache using the
|
||||
``--cache-show`` command line option:
|
||||
|
@ -258,9 +258,9 @@ You can always peek at the content of the cache using the
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
------------------------------- cache values -------------------------------
|
||||
--------------------------- cache values for '*' ---------------------------
|
||||
cache/lastfailed contains:
|
||||
{'test_50.py::test_num[17]': True,
|
||||
'test_50.py::test_num[25]': True,
|
||||
|
@ -277,8 +277,25 @@ You can always peek at the content of the cache using the
|
|||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
``--cache-show`` takes an optional argument to specify a glob pattern for
|
||||
filtering:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
$ pytest --cache-show example/*
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
----------------------- cache values for 'example/*' -----------------------
|
||||
example/value contains:
|
||||
42
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
Clearing Cache content
|
||||
-------------------------------
|
||||
----------------------
|
||||
|
||||
You can instruct pytest to clear all cache files and values
|
||||
by adding the ``--cache-clear`` option like this:
|
||||
|
|
|
@ -71,7 +71,7 @@ of the failing function and hide the other one:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_module.py .F [100%]
|
||||
|
|
|
@ -72,7 +72,7 @@ then you can just invoke ``pytest`` without command line options:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project, inifile: pytest.ini
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 1 item
|
||||
|
||||
mymodule.py . [100%]
|
||||
|
|
|
@ -9,18 +9,28 @@ Here are some example using the :ref:`mark` mechanism.
|
|||
Marking test functions and selecting them for a run
|
||||
----------------------------------------------------
|
||||
|
||||
You can "mark" a test function with custom metadata like this::
|
||||
You can "mark" a test function with custom metadata like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_server.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.webtest
|
||||
def test_send_http():
|
||||
pass # perform some webtest test for your app
|
||||
pass # perform some webtest test for your app
|
||||
|
||||
|
||||
def test_something_quick():
|
||||
pass
|
||||
|
||||
|
||||
def test_another():
|
||||
pass
|
||||
|
||||
|
||||
class TestClass(object):
|
||||
def test_method(self):
|
||||
pass
|
||||
|
@ -35,7 +45,7 @@ You can then restrict a test run to only run tests marked with ``webtest``:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 4 items / 3 deselected / 1 selected
|
||||
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
|
@ -50,7 +60,7 @@ Or the inverse, running all tests except the webtest ones:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 4 items / 1 deselected / 3 selected
|
||||
|
||||
test_server.py::test_something_quick PASSED [ 33%]
|
||||
|
@ -72,7 +82,7 @@ tests based on their module, class, method, or function name:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 1 item
|
||||
|
||||
test_server.py::TestClass::test_method PASSED [100%]
|
||||
|
@ -87,7 +97,7 @@ You can also select on the class:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 1 item
|
||||
|
||||
test_server.py::TestClass::test_method PASSED [100%]
|
||||
|
@ -102,7 +112,7 @@ Or select multiple nodes:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_server.py::TestClass::test_method PASSED [ 50%]
|
||||
|
@ -142,7 +152,7 @@ select tests based on their names:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 4 items / 3 deselected / 1 selected
|
||||
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
|
@ -157,7 +167,7 @@ And you can also run all tests except the ones that match the keyword:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 4 items / 1 deselected / 3 selected
|
||||
|
||||
test_server.py::test_something_quick PASSED [ 33%]
|
||||
|
@ -174,7 +184,7 @@ Or to select "http" and "quick" tests:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 4 items / 2 deselected / 2 selected
|
||||
|
||||
test_server.py::test_send_http PASSED [ 50%]
|
||||
|
@ -257,14 +267,19 @@ Marking whole classes or modules
|
|||
----------------------------------------------------
|
||||
|
||||
You may use ``pytest.mark`` decorators with classes to apply markers to all of
|
||||
its test methods::
|
||||
its test methods:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_mark_classlevel.py
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.webtest
|
||||
class TestClass(object):
|
||||
def test_startup(self):
|
||||
pass
|
||||
|
||||
def test_startup_and_more(self):
|
||||
pass
|
||||
|
||||
|
@ -272,17 +287,23 @@ This is equivalent to directly applying the decorator to the
|
|||
two test functions.
|
||||
|
||||
To remain backward-compatible with Python 2.4 you can also set a
|
||||
``pytestmark`` attribute on a TestClass like this::
|
||||
``pytestmark`` attribute on a TestClass like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestClass(object):
|
||||
pytestmark = pytest.mark.webtest
|
||||
|
||||
or if you need to use multiple markers you can use a list::
|
||||
or if you need to use multiple markers you can use a list:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class TestClass(object):
|
||||
pytestmark = [pytest.mark.webtest, pytest.mark.slowtest]
|
||||
|
||||
|
@ -305,18 +326,19 @@ Marking individual tests when using parametrize
|
|||
|
||||
When using parametrize, applying a mark will make it apply
|
||||
to each individual test. However it is also possible to
|
||||
apply a marker to an individual test instance::
|
||||
apply a marker to an individual test instance:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.foo
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(1, 2),
|
||||
pytest.param((1, 3), marks=pytest.mark.bar),
|
||||
(2, 3),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
("n", "expected"), [(1, 2), pytest.param((1, 3), marks=pytest.mark.bar), (2, 3)]
|
||||
)
|
||||
def test_increment(n, expected):
|
||||
assert n + 1 == expected
|
||||
assert n + 1 == expected
|
||||
|
||||
In this example the mark "foo" will apply to each of the three
|
||||
tests, whereas the "bar" mark is only applied to the second test.
|
||||
|
@ -332,31 +354,46 @@ Custom marker and command line option to control test runs
|
|||
Plugins can provide custom markers and implement specific behaviour
|
||||
based on it. This is a self-contained example which adds a command
|
||||
line option and a parametrized test function marker to run tests
|
||||
specifies via named environments::
|
||||
specifies via named environments:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("-E", action="store", metavar="NAME",
|
||||
help="only run tests matching the environment NAME.")
|
||||
parser.addoption(
|
||||
"-E",
|
||||
action="store",
|
||||
metavar="NAME",
|
||||
help="only run tests matching the environment NAME.",
|
||||
)
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
# register an additional marker
|
||||
config.addinivalue_line("markers",
|
||||
"env(name): mark test to run only on named environment")
|
||||
config.addinivalue_line(
|
||||
"markers", "env(name): mark test to run only on named environment"
|
||||
)
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
envnames = [mark.args[0] for mark in item.iter_markers(name='env')]
|
||||
envnames = [mark.args[0] for mark in item.iter_markers(name="env")]
|
||||
if envnames:
|
||||
if item.config.getoption("-E") not in envnames:
|
||||
pytest.skip("test requires env in %r" % envnames)
|
||||
|
||||
A test file using this local plugin::
|
||||
A test file using this local plugin:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_someenv.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.env("stage1")
|
||||
def test_basic_db_operation():
|
||||
pass
|
||||
|
@ -370,7 +407,7 @@ the test needs:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_someenv.py s [100%]
|
||||
|
@ -385,7 +422,7 @@ and here is one that specifies exactly the environment needed:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_someenv.py . [100%]
|
||||
|
@ -423,25 +460,32 @@ Passing a callable to custom markers
|
|||
|
||||
.. regendoc:wipe
|
||||
|
||||
Below is the config file that will be used in the next examples::
|
||||
Below is the config file that will be used in the next examples:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
import sys
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
for marker in item.iter_markers(name='my_marker'):
|
||||
for marker in item.iter_markers(name="my_marker"):
|
||||
print(marker)
|
||||
sys.stdout.flush()
|
||||
|
||||
A custom marker can have its argument set, i.e. ``args`` and ``kwargs`` properties, defined by either invoking it as a callable or using ``pytest.mark.MARKER_NAME.with_args``. These two methods achieve the same effect most of the time.
|
||||
|
||||
However, if there is a callable as the single positional argument with no keyword arguments, using the ``pytest.mark.MARKER_NAME(c)`` will not pass ``c`` as a positional argument but decorate ``c`` with the custom marker (see :ref:`MarkDecorator <mark>`). Fortunately, ``pytest.mark.MARKER_NAME.with_args`` comes to the rescue::
|
||||
However, if there is a callable as the single positional argument with no keyword arguments, using the ``pytest.mark.MARKER_NAME(c)`` will not pass ``c`` as a positional argument but decorate ``c`` with the custom marker (see :ref:`MarkDecorator <mark>`). Fortunately, ``pytest.mark.MARKER_NAME.with_args`` comes to the rescue:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_custom_marker.py
|
||||
import pytest
|
||||
|
||||
|
||||
def hello_world(*args, **kwargs):
|
||||
return 'Hello World'
|
||||
return "Hello World"
|
||||
|
||||
|
||||
@pytest.mark.my_marker.with_args(hello_world)
|
||||
def test_with_args():
|
||||
|
@ -467,12 +511,16 @@ Reading markers which were set from multiple places
|
|||
.. regendoc:wipe
|
||||
|
||||
If you are heavily using markers in your test suite you may encounter the case where a marker is applied several times to a test function. From plugin
|
||||
code you can read over all such settings. Example::
|
||||
code you can read over all such settings. Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_mark_three_times.py
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.glob("module", x=1)
|
||||
|
||||
|
||||
@pytest.mark.glob("class", x=2)
|
||||
class TestClass(object):
|
||||
@pytest.mark.glob("function", x=3)
|
||||
|
@ -480,13 +528,16 @@ code you can read over all such settings. Example::
|
|||
pass
|
||||
|
||||
Here we have the marker "glob" applied three times to the same
|
||||
test function. From a conftest file we can read it like this::
|
||||
test function. From a conftest file we can read it like this:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
import sys
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
for mark in item.iter_markers(name='glob'):
|
||||
for mark in item.iter_markers(name="glob"):
|
||||
print("glob args=%s kwargs=%s" % (mark.args, mark.kwargs))
|
||||
sys.stdout.flush()
|
||||
|
||||
|
@ -510,7 +561,9 @@ Consider you have a test suite which marks tests for particular platforms,
|
|||
namely ``pytest.mark.darwin``, ``pytest.mark.win32`` etc. and you
|
||||
also have tests that run on all platforms and have no specific
|
||||
marker. If you now want to have a way to only run the tests
|
||||
for your particular platform, you could use the following plugin::
|
||||
for your particular platform, you could use the following plugin:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
#
|
||||
|
@ -519,6 +572,7 @@ for your particular platform, you could use the following plugin::
|
|||
|
||||
ALL = set("darwin linux win32".split())
|
||||
|
||||
|
||||
def pytest_runtest_setup(item):
|
||||
supported_platforms = ALL.intersection(mark.name for mark in item.iter_markers())
|
||||
plat = sys.platform
|
||||
|
@ -526,24 +580,30 @@ for your particular platform, you could use the following plugin::
|
|||
pytest.skip("cannot run on platform %s" % (plat))
|
||||
|
||||
then tests will be skipped if they were specified for a different platform.
|
||||
Let's do a little test file to show how this looks like::
|
||||
Let's do a little test file to show how this looks like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_plat.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.darwin
|
||||
def test_if_apple_is_evil():
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.linux
|
||||
def test_if_linux_works():
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.win32
|
||||
def test_if_win32_crashes():
|
||||
pass
|
||||
|
||||
|
||||
def test_runs_everywhere():
|
||||
pass
|
||||
|
||||
|
@ -555,12 +615,12 @@ then you will see two tests skipped and two executed tests as expected:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items
|
||||
|
||||
test_plat.py s.s. [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [2] /home/sweet/project/conftest.py:12: cannot run on platform linux
|
||||
SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:13: cannot run on platform linux
|
||||
|
||||
=================== 2 passed, 2 skipped in 0.12 seconds ====================
|
||||
|
||||
|
@ -572,7 +632,7 @@ Note that if you specify a platform via the marker-command line option like this
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items / 3 deselected / 1 selected
|
||||
|
||||
test_plat.py . [100%]
|
||||
|
@ -589,28 +649,38 @@ Automatically adding markers based on test names
|
|||
If you a test suite where test function names indicate a certain
|
||||
type of test, you can implement a hook that automatically defines
|
||||
markers so that you can use the ``-m`` option with it. Let's look
|
||||
at this test module::
|
||||
at this test module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_module.py
|
||||
|
||||
|
||||
def test_interface_simple():
|
||||
assert 0
|
||||
|
||||
|
||||
def test_interface_complex():
|
||||
assert 0
|
||||
|
||||
|
||||
def test_event_simple():
|
||||
assert 0
|
||||
|
||||
|
||||
def test_something_else():
|
||||
assert 0
|
||||
|
||||
We want to dynamically define two markers and can do it in a
|
||||
``conftest.py`` plugin::
|
||||
``conftest.py`` plugin:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(items):
|
||||
for item in items:
|
||||
if "interface" in item.nodeid:
|
||||
|
@ -626,18 +696,18 @@ We can now use the ``-m option`` to select one set:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items / 2 deselected / 2 selected
|
||||
|
||||
test_module.py FF [100%]
|
||||
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_interface_simple ___________________________
|
||||
test_module.py:3: in test_interface_simple
|
||||
test_module.py:4: in test_interface_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
__________________________ test_interface_complex __________________________
|
||||
test_module.py:6: in test_interface_complex
|
||||
test_module.py:8: in test_interface_complex
|
||||
assert 0
|
||||
E assert 0
|
||||
================== 2 failed, 2 deselected in 0.12 seconds ==================
|
||||
|
@ -650,22 +720,22 @@ or to select both "event" and "interface" tests:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items / 1 deselected / 3 selected
|
||||
|
||||
test_module.py FFF [100%]
|
||||
|
||||
================================= FAILURES =================================
|
||||
__________________________ test_interface_simple ___________________________
|
||||
test_module.py:3: in test_interface_simple
|
||||
test_module.py:4: in test_interface_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
__________________________ test_interface_complex __________________________
|
||||
test_module.py:6: in test_interface_complex
|
||||
test_module.py:8: in test_interface_complex
|
||||
assert 0
|
||||
E assert 0
|
||||
____________________________ test_event_simple _____________________________
|
||||
test_module.py:9: in test_event_simple
|
||||
test_module.py:12: in test_event_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
================== 3 failed, 1 deselected in 0.12 seconds ==================
|
||||
|
|
|
@ -31,7 +31,7 @@ now execute the test specification:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project/nonpython
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython
|
||||
collected 2 items
|
||||
|
||||
test_simple.yml F. [100%]
|
||||
|
@ -66,7 +66,7 @@ consulted when reporting in ``verbose`` mode:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project/nonpython
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_simple.yml::hello FAILED [ 50%]
|
||||
|
@ -90,9 +90,9 @@ interesting to just look at the collection tree:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project/nonpython
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython
|
||||
collected 2 items
|
||||
<Package /home/sweet/project/nonpython>
|
||||
<Package $REGENDOC_TMPDIR/nonpython>
|
||||
<YamlFile test_simple.yml>
|
||||
<YamlItem hello>
|
||||
<YamlItem ok>
|
||||
|
|
|
@ -146,7 +146,7 @@ objects, they are still using the default pytest representation:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 8 items
|
||||
<Module test_time.py>
|
||||
<Function test_timedistance_v0[a0-b0-expected0]>
|
||||
|
@ -205,7 +205,7 @@ this is a fully self-contained example which you can run with:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items
|
||||
|
||||
test_scenarios.py .... [100%]
|
||||
|
@ -220,7 +220,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items
|
||||
<Module test_scenarios.py>
|
||||
<Class TestSampleWithScenarios>
|
||||
|
@ -287,7 +287,7 @@ Let's first see how it looks like at collection time:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
<Module test_backends.py>
|
||||
<Function test_db_initialized[d1]>
|
||||
|
@ -353,7 +353,7 @@ The result of this test will be successful:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
<Module test_indirect_list.py>
|
||||
<Function test_indirect[a-b]>
|
||||
|
@ -434,9 +434,9 @@ Running it results in some skips if we don't have all the python interpreters in
|
|||
.. code-block:: pytest
|
||||
|
||||
. $ pytest -rs -q multipython.py
|
||||
......sss......ssssssssssss [100%]
|
||||
...sss...sssssssss...sss... [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [15] /home/sweet/project/CWD/multipython.py:30: 'python3.5' not found
|
||||
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.4' not found
|
||||
12 passed, 15 skipped in 0.12 seconds
|
||||
|
||||
Indirect parametrization of optional implementations/imports
|
||||
|
@ -488,12 +488,12 @@ If you run this with reporting for skips enabled:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_module.py .s [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [1] /home/sweet/project/conftest.py:11: could not import 'opt2'
|
||||
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2'
|
||||
|
||||
=================== 1 passed, 1 skipped in 0.12 seconds ====================
|
||||
|
||||
|
@ -515,21 +515,25 @@ Set marks or test ID for individual parametrized test
|
|||
--------------------------------------------------------------------
|
||||
|
||||
Use ``pytest.param`` to apply marks or set test ID to individual parametrized test.
|
||||
For example::
|
||||
For example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_pytest_param_example.py
|
||||
import pytest
|
||||
@pytest.mark.parametrize('test_input,expected', [
|
||||
('3+5', 8),
|
||||
pytest.param('1+7', 8,
|
||||
marks=pytest.mark.basic),
|
||||
pytest.param('2+4', 6,
|
||||
marks=pytest.mark.basic,
|
||||
id='basic_2+4'),
|
||||
pytest.param('6*9', 42,
|
||||
marks=[pytest.mark.basic, pytest.mark.xfail],
|
||||
id='basic_6*9'),
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_input,expected",
|
||||
[
|
||||
("3+5", 8),
|
||||
pytest.param("1+7", 8, marks=pytest.mark.basic),
|
||||
pytest.param("2+4", 6, marks=pytest.mark.basic, id="basic_2+4"),
|
||||
pytest.param(
|
||||
"6*9", 42, marks=[pytest.mark.basic, pytest.mark.xfail], id="basic_6*9"
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_eval(test_input, expected):
|
||||
assert eval(test_input) == expected
|
||||
|
||||
|
@ -546,7 +550,7 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 17 items / 14 deselected / 3 selected
|
||||
|
||||
test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%]
|
||||
|
|
|
@ -148,7 +148,7 @@ The test collection would look like this:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project, inifile: pytest.ini
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 2 items
|
||||
<Module check_myapp.py>
|
||||
<Class CheckMyApp>
|
||||
|
@ -210,7 +210,7 @@ You can always peek at the collection tree without running tests like this:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project, inifile: pytest.ini
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 3 items
|
||||
<Module CWD/pythoncollection.py>
|
||||
<Function test_function>
|
||||
|
@ -285,7 +285,7 @@ file will be left out:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project, inifile: pytest.ini
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
|
||||
.. _`tbreportdemo`:
|
||||
|
||||
Demo of Python failure reports with pytest
|
||||
==================================================
|
||||
==========================================
|
||||
|
||||
Here is a nice run of several tens of failures
|
||||
and how ``pytest`` presents things (unfortunately
|
||||
not showing the nice colors here in the HTML that you
|
||||
get on the terminal - we are working on that):
|
||||
Here is a nice run of several failures and how ``pytest`` presents things:
|
||||
|
||||
.. code-block:: pytest
|
||||
|
||||
|
@ -15,7 +11,7 @@ get on the terminal - we are working on that):
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project/assertion
|
||||
rootdir: $REGENDOC_TMPDIR/assertion
|
||||
collected 44 items
|
||||
|
||||
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [100%]
|
||||
|
@ -475,7 +471,7 @@ get on the terminal - we are working on that):
|
|||
> assert 1 == 0
|
||||
E AssertionError
|
||||
|
||||
<0-codegen 'abc-123' /home/sweet/project/assertion/failure_demo.py:201>:2: AssertionError
|
||||
<0-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:201>:2: AssertionError
|
||||
____________________ TestMoreErrors.test_complex_error _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
|
|
|
@ -129,7 +129,7 @@ directory with the above conftest.py:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
@ -190,7 +190,7 @@ and when running it will see a skipped "slow" test:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_module.py .s [100%]
|
||||
|
@ -207,7 +207,7 @@ Or run it including the ``slow`` marked test:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_module.py .. [100%]
|
||||
|
@ -351,7 +351,7 @@ which will add the string to the test header accordingly:
|
|||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
project deps: mylib-1.1
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
@ -381,7 +381,7 @@ which will add info only when run with "--v":
|
|||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
info1: did you know that ...
|
||||
did you?
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
@ -394,7 +394,7 @@ and nothing when run plainly:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 0 items
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
@ -434,7 +434,7 @@ Now we can profile which test functions execute the slowest:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 3 items
|
||||
|
||||
test_some_are_slow.py ... [100%]
|
||||
|
@ -509,7 +509,7 @@ If we run this:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 4 items
|
||||
|
||||
test_step.py .Fx. [100%]
|
||||
|
@ -593,7 +593,7 @@ We can run this:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 7 items
|
||||
|
||||
test_step.py .Fx. [ 57%]
|
||||
|
@ -603,13 +603,13 @@ We can run this:
|
|||
|
||||
================================== ERRORS ==================================
|
||||
_______________________ ERROR at setup of test_root ________________________
|
||||
file /home/sweet/project/b/test_error.py, line 1
|
||||
file $REGENDOC_TMPDIR/b/test_error.py, line 1
|
||||
def test_root(db): # no db here, will error out
|
||||
E fixture 'db' not found
|
||||
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
|
||||
> use 'pytest --fixtures [testpath]' for help on them.
|
||||
|
||||
/home/sweet/project/b/test_error.py:1
|
||||
$REGENDOC_TMPDIR/b/test_error.py:1
|
||||
================================= FAILURES =================================
|
||||
____________________ TestUserHandling.test_modification ____________________
|
||||
|
||||
|
@ -707,7 +707,7 @@ and run them:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_module.py FF [100%]
|
||||
|
@ -811,7 +811,7 @@ and run it:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 3 items
|
||||
|
||||
test_module.py Esetting up a test failed! test_module.py::test_setup_fails
|
||||
|
|
|
@ -74,7 +74,7 @@ marked ``smtp_connection`` fixture function. Running the test looks like this:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_smtpsimple.py F [100%]
|
||||
|
@ -217,7 +217,7 @@ inspect what is going on and can now run the tests:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_module.py FF [100%]
|
||||
|
@ -710,7 +710,7 @@ Running the above tests results in the following test IDs being used:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 10 items
|
||||
<Module test_anothersmtp.py>
|
||||
<Function test_showhelo[smtp.gmail.com]>
|
||||
|
@ -755,7 +755,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 3 items
|
||||
|
||||
test_fixture_marks.py::test_data[0] PASSED [ 33%]
|
||||
|
@ -800,7 +800,7 @@ Here we declare an ``app`` fixture which receives the previously defined
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_appsetup.py::test_smtp_connection_exists[smtp.gmail.com] PASSED [ 50%]
|
||||
|
@ -871,7 +871,7 @@ Let's run the tests in verbose mode and with looking at the print-output:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 8 items
|
||||
|
||||
test_module.py::test_0[1] SETUP otherarg 1
|
||||
|
|
|
@ -52,7 +52,7 @@ That’s it. You can now execute the test function:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_sample.py F [100%]
|
||||
|
|
|
@ -57,14 +57,16 @@ Applying marks to ``@pytest.mark.parametrize`` parameters
|
|||
.. versionchanged:: 3.1
|
||||
|
||||
Prior to version 3.1 the supported mechanism for marking values
|
||||
used the syntax::
|
||||
used the syntax:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
@pytest.mark.parametrize("test_input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
pytest.mark.xfail(("6*9", 42),),
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_input,expected", [("3+5", 8), ("2+4", 6), pytest.mark.xfail(("6*9", 42))]
|
||||
)
|
||||
def test_eval(test_input, expected):
|
||||
assert eval(test_input) == expected
|
||||
|
||||
|
@ -105,9 +107,13 @@ Conditions as strings instead of booleans
|
|||
.. versionchanged:: 2.4
|
||||
|
||||
Prior to pytest-2.4 the only way to specify skipif/xfail conditions was
|
||||
to use strings::
|
||||
to use strings:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
|
||||
|
||||
@pytest.mark.skipif("sys.version_info >= (3,3)")
|
||||
def test_function():
|
||||
...
|
||||
|
@ -139,17 +145,20 @@ dictionary which is constructed as follows:
|
|||
expression is applied.
|
||||
|
||||
The pytest ``config`` object allows you to skip based on a test
|
||||
configuration value which you might have added::
|
||||
configuration value which you might have added:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.skipif("not config.getvalue('db')")
|
||||
def test_function(...):
|
||||
def test_function():
|
||||
...
|
||||
|
||||
The equivalent with "boolean conditions" is::
|
||||
The equivalent with "boolean conditions" is:
|
||||
|
||||
@pytest.mark.skipif(not pytest.config.getvalue("db"),
|
||||
reason="--db was not specified")
|
||||
def test_function(...):
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.skipif(not pytest.config.getvalue("db"), reason="--db was not specified")
|
||||
def test_function():
|
||||
pass
|
||||
|
||||
.. note::
|
||||
|
@ -164,12 +173,16 @@ The equivalent with "boolean conditions" is::
|
|||
|
||||
.. versionchanged:: 2.4
|
||||
|
||||
Previous to version 2.4 to set a break point in code one needed to use ``pytest.set_trace()``::
|
||||
Previous to version 2.4 to set a break point in code one needed to use ``pytest.set_trace()``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_function():
|
||||
...
|
||||
pytest.set_trace() # invoke PDB debugger and tracing
|
||||
pytest.set_trace() # invoke PDB debugger and tracing
|
||||
|
||||
|
||||
This is no longer needed and one can use the native ``import pdb;pdb.set_trace()`` call directly.
|
||||
|
|
|
@ -30,7 +30,7 @@ To execute it:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_sample.py F [100%]
|
||||
|
|
|
@ -36,15 +36,15 @@ pytest enables test parametrization at several levels:
|
|||
The builtin :ref:`pytest.mark.parametrize ref` decorator enables
|
||||
parametrization of arguments for a test function. Here is a typical example
|
||||
of a test function that implements checking that a certain input leads
|
||||
to an expected output::
|
||||
to an expected output:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_expectation.py
|
||||
import pytest
|
||||
@pytest.mark.parametrize("test_input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
("6*9", 42),
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
|
||||
def test_eval(test_input, expected):
|
||||
assert eval(test_input) == expected
|
||||
|
||||
|
@ -58,7 +58,7 @@ them in turn:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 3 items
|
||||
|
||||
test_expectation.py ..F [100%]
|
||||
|
@ -68,17 +68,13 @@ them in turn:
|
|||
|
||||
test_input = '6*9', expected = 42
|
||||
|
||||
@pytest.mark.parametrize("test_input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
("6*9", 42),
|
||||
])
|
||||
@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])
|
||||
def test_eval(test_input, expected):
|
||||
> assert eval(test_input) == expected
|
||||
E AssertionError: assert 54 == 42
|
||||
E + where 54 = eval('6*9')
|
||||
|
||||
test_expectation.py:8: AssertionError
|
||||
test_expectation.py:6: AssertionError
|
||||
==================== 1 failed, 2 passed in 0.12 seconds ====================
|
||||
|
||||
.. note::
|
||||
|
@ -104,16 +100,18 @@ Note that you could also use the parametrize marker on a class or a module
|
|||
(see :ref:`mark`) which would invoke several functions with the argument sets.
|
||||
|
||||
It is also possible to mark individual test instances within parametrize,
|
||||
for example with the builtin ``mark.xfail``::
|
||||
for example with the builtin ``mark.xfail``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_expectation.py
|
||||
import pytest
|
||||
@pytest.mark.parametrize("test_input,expected", [
|
||||
("3+5", 8),
|
||||
("2+4", 6),
|
||||
pytest.param("6*9", 42,
|
||||
marks=pytest.mark.xfail),
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"test_input,expected",
|
||||
[("3+5", 8), ("2+4", 6), pytest.param("6*9", 42, marks=pytest.mark.xfail)],
|
||||
)
|
||||
def test_eval(test_input, expected):
|
||||
assert eval(test_input) == expected
|
||||
|
||||
|
@ -125,7 +123,7 @@ Let's run this:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 3 items
|
||||
|
||||
test_expectation.py ..x [100%]
|
||||
|
@ -140,9 +138,13 @@ example, if they're dynamically generated by some function - the behaviour of
|
|||
pytest is defined by the :confval:`empty_parameter_set_mark` option.
|
||||
|
||||
To get all combinations of multiple parametrized arguments you can stack
|
||||
``parametrize`` decorators::
|
||||
``parametrize`` decorators:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize("x", [0, 1])
|
||||
@pytest.mark.parametrize("y", [2, 3])
|
||||
def test_foo(x, y):
|
||||
|
@ -166,26 +168,36 @@ parametrization.
|
|||
|
||||
For example, let's say we want to run a test taking string inputs which
|
||||
we want to set via a new ``pytest`` command line option. Let's first write
|
||||
a simple test accepting a ``stringinput`` fixture function argument::
|
||||
a simple test accepting a ``stringinput`` fixture function argument:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_strings.py
|
||||
|
||||
|
||||
def test_valid_string(stringinput):
|
||||
assert stringinput.isalpha()
|
||||
|
||||
Now we add a ``conftest.py`` file containing the addition of a
|
||||
command line option and the parametrization of our test function::
|
||||
command line option and the parametrization of our test function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of conftest.py
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--stringinput", action="append", default=[],
|
||||
help="list of stringinputs to pass to test functions")
|
||||
parser.addoption(
|
||||
"--stringinput",
|
||||
action="append",
|
||||
default=[],
|
||||
help="list of stringinputs to pass to test functions",
|
||||
)
|
||||
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
if 'stringinput' in metafunc.fixturenames:
|
||||
metafunc.parametrize("stringinput",
|
||||
metafunc.config.getoption('stringinput'))
|
||||
if "stringinput" in metafunc.fixturenames:
|
||||
metafunc.parametrize("stringinput", metafunc.config.getoption("stringinput"))
|
||||
|
||||
If we now pass two stringinput values, our test will run twice:
|
||||
|
||||
|
@ -212,7 +224,7 @@ Let's also run with a stringinput that will lead to a failing test:
|
|||
E + where False = <built-in method isalpha of str object at 0xdeadbeef>()
|
||||
E + where <built-in method isalpha of str object at 0xdeadbeef> = '!'.isalpha
|
||||
|
||||
test_strings.py:3: AssertionError
|
||||
test_strings.py:4: AssertionError
|
||||
1 failed in 0.12 seconds
|
||||
|
||||
As expected our test function fails.
|
||||
|
@ -226,7 +238,7 @@ list:
|
|||
$ pytest -q -rs test_strings.py
|
||||
s [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at /home/sweet/project/test_strings.py:1
|
||||
SKIPPED [1] test_strings.py: got empty parameter set ['stringinput'], function test_valid_string at $REGENDOC_TMPDIR/test_strings.py:2
|
||||
1 skipped in 0.12 seconds
|
||||
|
||||
Note that when calling ``metafunc.parametrize`` multiple times with different parameter sets, all parameter names across
|
||||
|
|
|
@ -84,32 +84,44 @@ It is also possible to skip the whole module using
|
|||
|
||||
If you wish to skip something conditionally then you can use ``skipif`` instead.
|
||||
Here is an example of marking a test function to be skipped
|
||||
when run on an interpreter earlier than Python3.6::
|
||||
when run on an interpreter earlier than Python3.6:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import sys
|
||||
@pytest.mark.skipif(sys.version_info < (3,6),
|
||||
reason="requires python3.6 or higher")
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 6), reason="requires python3.6 or higher")
|
||||
def test_function():
|
||||
...
|
||||
|
||||
If the condition evaluates to ``True`` during collection, the test function will be skipped,
|
||||
with the specified reason appearing in the summary when using ``-rs``.
|
||||
|
||||
You can share ``skipif`` markers between modules. Consider this test module::
|
||||
You can share ``skipif`` markers between modules. Consider this test module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_mymodule.py
|
||||
import mymodule
|
||||
minversion = pytest.mark.skipif(mymodule.__versioninfo__ < (1,1),
|
||||
reason="at least mymodule-1.1 required")
|
||||
|
||||
minversion = pytest.mark.skipif(
|
||||
mymodule.__versioninfo__ < (1, 1), reason="at least mymodule-1.1 required"
|
||||
)
|
||||
|
||||
|
||||
@minversion
|
||||
def test_function():
|
||||
...
|
||||
|
||||
You can import the marker and reuse it in another test module::
|
||||
You can import the marker and reuse it in another test module:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# test_myothermodule.py
|
||||
from test_mymodule import minversion
|
||||
|
||||
|
||||
@minversion
|
||||
def test_anotherfunction():
|
||||
...
|
||||
|
@ -128,12 +140,12 @@ so they are supported mainly for backward compatibility reasons.
|
|||
Skip all test functions of a class or module
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can use the ``skipif`` marker (as any other marker) on classes::
|
||||
You can use the ``skipif`` marker (as any other marker) on classes:
|
||||
|
||||
@pytest.mark.skipif(sys.platform == 'win32',
|
||||
reason="does not run on windows")
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows")
|
||||
class TestPosixCalls(object):
|
||||
|
||||
def test_function(self):
|
||||
"will not be setup or run under 'win32' platform"
|
||||
|
||||
|
@ -269,10 +281,11 @@ You can change the default value of the ``strict`` parameter using the
|
|||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
As with skipif_ you can also mark your expectation of a failure
|
||||
on a particular platform::
|
||||
on a particular platform:
|
||||
|
||||
@pytest.mark.xfail(sys.version_info >= (3,6),
|
||||
reason="python3.6 api changes")
|
||||
.. code-block:: python
|
||||
|
||||
@pytest.mark.xfail(sys.version_info >= (3, 6), reason="python3.6 api changes")
|
||||
def test_function():
|
||||
...
|
||||
|
||||
|
@ -335,7 +348,7 @@ Running it with the report-on-xfail option gives this output:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project/example
|
||||
rootdir: $REGENDOC_TMPDIR/example
|
||||
collected 7 items
|
||||
|
||||
xfail_demo.py xxxxxxx [100%]
|
||||
|
|
|
@ -43,7 +43,7 @@ Running this would result in a passed test except for the last
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_tmp_path.py F [100%]
|
||||
|
@ -110,7 +110,7 @@ Running this would result in a passed test except for the last
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_tmpdir.py F [100%]
|
||||
|
|
|
@ -130,7 +130,7 @@ the ``self.db`` values in the traceback:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 2 items
|
||||
|
||||
test_unittest_db.py FF [100%]
|
||||
|
|
|
@ -204,7 +204,7 @@ Example:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 6 items
|
||||
|
||||
test_example.py .FEsxX [100%]
|
||||
|
@ -227,15 +227,16 @@ Example:
|
|||
|
||||
test_example.py:14: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [1] /home/sweet/project/test_example.py:23: skipping this test
|
||||
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
|
||||
XFAIL test_example.py::test_xfail
|
||||
reason: xfailing this test
|
||||
XPASS test_example.py::test_xpass always xfail
|
||||
ERROR test_example.py::test_error
|
||||
FAILED test_example.py::test_fail
|
||||
= 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds =
|
||||
1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds
|
||||
|
||||
The ``-r`` options accepts a number of characters after it, with ``a`` used above meaning "all except passes".
|
||||
The ``-r`` options accepts a number of characters after it, with ``a`` used
|
||||
above meaning "all except passes".
|
||||
|
||||
Here is the full list of available characters that can be used:
|
||||
|
||||
|
@ -247,6 +248,7 @@ Here is the full list of available characters that can be used:
|
|||
- ``p`` - passed
|
||||
- ``P`` - passed with output
|
||||
- ``a`` - all except ``pP``
|
||||
- ``A`` - all
|
||||
|
||||
More than one character can be used, so for example to only see failed and skipped tests, you can execute:
|
||||
|
||||
|
@ -256,7 +258,7 @@ More than one character can be used, so for example to only see failed and skipp
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 6 items
|
||||
|
||||
test_example.py .FEsxX [100%]
|
||||
|
@ -280,8 +282,8 @@ More than one character can be used, so for example to only see failed and skipp
|
|||
test_example.py:14: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
FAILED test_example.py::test_fail
|
||||
SKIPPED [1] /home/sweet/project/test_example.py:23: skipping this test
|
||||
= 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds =
|
||||
SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
|
||||
1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds
|
||||
|
||||
Using ``p`` lists the passing tests, whilst ``P`` adds an extra section "PASSES" with those tests that passed but had
|
||||
captured output:
|
||||
|
@ -292,7 +294,7 @@ captured output:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 6 items
|
||||
|
||||
test_example.py .FEsxX [100%]
|
||||
|
@ -320,7 +322,7 @@ captured output:
|
|||
_________________________________ test_ok __________________________________
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
ok
|
||||
= 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds =
|
||||
1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds
|
||||
|
||||
.. _pdb-option:
|
||||
|
||||
|
|
|
@ -6,15 +6,19 @@ Warnings Capture
|
|||
.. versionadded:: 3.1
|
||||
|
||||
Starting from version ``3.1``, pytest now automatically catches warnings during test execution
|
||||
and displays them at the end of the session::
|
||||
and displays them at the end of the session:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# content of test_show_warnings.py
|
||||
import warnings
|
||||
|
||||
|
||||
def api_v1():
|
||||
warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||
return 1
|
||||
|
||||
|
||||
def test_one():
|
||||
assert api_v1() == 1
|
||||
|
||||
|
@ -26,14 +30,14 @@ Running pytest now produces this output:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project
|
||||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 1 item
|
||||
|
||||
test_show_warnings.py . [100%]
|
||||
|
||||
============================= warnings summary =============================
|
||||
test_show_warnings.py::test_one
|
||||
/home/sweet/project/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2
|
||||
$REGENDOC_TMPDIR/test_show_warnings.py:5: UserWarning: api v1, should use functions from v2
|
||||
warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
|
@ -52,14 +56,14 @@ them into errors:
|
|||
def test_one():
|
||||
> assert api_v1() == 1
|
||||
|
||||
test_show_warnings.py:8:
|
||||
test_show_warnings.py:10:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
def api_v1():
|
||||
> warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||
E UserWarning: api v1, should use functions from v2
|
||||
|
||||
test_show_warnings.py:4: UserWarning
|
||||
test_show_warnings.py:5: UserWarning
|
||||
1 failed in 0.12 seconds
|
||||
|
||||
The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option.
|
||||
|
@ -195,28 +199,36 @@ Ensuring code triggers a deprecation warning
|
|||
|
||||
You can also call a global helper for checking
|
||||
that a certain function call triggers a ``DeprecationWarning`` or
|
||||
``PendingDeprecationWarning``::
|
||||
``PendingDeprecationWarning``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_global():
|
||||
pytest.deprecated_call(myfunction, 17)
|
||||
|
||||
By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be
|
||||
caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide
|
||||
them. If you wish to record them in your own code, use the
|
||||
command ``warnings.simplefilter('always')``::
|
||||
command ``warnings.simplefilter('always')``:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
|
||||
def test_deprecation(recwarn):
|
||||
warnings.simplefilter('always')
|
||||
warnings.simplefilter("always")
|
||||
warnings.warn("deprecated", DeprecationWarning)
|
||||
assert len(recwarn) == 1
|
||||
assert recwarn.pop(DeprecationWarning)
|
||||
|
||||
You can also use it as a contextmanager::
|
||||
You can also use it as a contextmanager:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_global():
|
||||
with pytest.deprecated_call():
|
||||
|
@ -238,11 +250,14 @@ Asserting warnings with the warns function
|
|||
.. versionadded:: 2.8
|
||||
|
||||
You can check that code raises a particular warning using ``pytest.warns``,
|
||||
which works in a similar manner to :ref:`raises <assertraises>`::
|
||||
which works in a similar manner to :ref:`raises <assertraises>`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
|
||||
def test_warning():
|
||||
with pytest.warns(UserWarning):
|
||||
warnings.warn("my warning", UserWarning)
|
||||
|
@ -269,7 +284,9 @@ You can also call ``pytest.warns`` on a function or code string::
|
|||
|
||||
The function also returns a list of all raised warnings (as
|
||||
``warnings.WarningMessage`` objects), which you can query for
|
||||
additional information::
|
||||
additional information:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with pytest.warns(RuntimeWarning) as record:
|
||||
warnings.warn("another warning", RuntimeWarning)
|
||||
|
@ -297,7 +314,9 @@ You can record raised warnings either using ``pytest.warns`` or with
|
|||
the ``recwarn`` fixture.
|
||||
|
||||
To record with ``pytest.warns`` without asserting anything about the warnings,
|
||||
pass ``None`` as the expected warning type::
|
||||
pass ``None`` as the expected warning type:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
with pytest.warns(None) as record:
|
||||
warnings.warn("user", UserWarning)
|
||||
|
@ -307,10 +326,13 @@ pass ``None`` as the expected warning type::
|
|||
assert str(record[0].message) == "user"
|
||||
assert str(record[1].message) == "runtime"
|
||||
|
||||
The ``recwarn`` fixture will record warnings for the whole function::
|
||||
The ``recwarn`` fixture will record warnings for the whole function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import warnings
|
||||
|
||||
|
||||
def test_hello(recwarn):
|
||||
warnings.warn("hello", UserWarning)
|
||||
assert len(recwarn) == 1
|
||||
|
@ -378,7 +400,7 @@ defines an ``__init__`` constructor, as this prevents the class from being insta
|
|||
|
||||
============================= warnings summary =============================
|
||||
test_pytest_warnings.py:1
|
||||
/home/sweet/project/test_pytest_warnings.py:1: PytestWarning: cannot collect test class 'Test' because it has a __init__ constructor
|
||||
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestWarning: cannot collect test class 'Test' because it has a __init__ constructor
|
||||
class Test:
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
|
|
|
@ -433,14 +433,14 @@ additionally it is possible to copy examples for an example folder before runnin
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
cachedir: $PYTHON_PREFIX/.pytest_cache
|
||||
rootdir: /home/sweet/project, inifile: pytest.ini
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 2 items
|
||||
|
||||
test_example.py .. [100%]
|
||||
|
||||
============================= warnings summary =============================
|
||||
test_example.py::test_plugin
|
||||
/home/sweet/project/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
|
||||
$REGENDOC_TMPDIR/test_example.py:4: PytestExperimentalApiWarning: testdir.copy_example is an experimental api that may change over time
|
||||
testdir.copy_example("test_example.py")
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
|
@ -528,10 +528,13 @@ a :py:class:`Result <pluggy._Result>` instance which encapsulates a result or
|
|||
exception info. The yield point itself will thus typically not raise
|
||||
exceptions (unless there are bugs).
|
||||
|
||||
Here is an example definition of a hook wrapper::
|
||||
Here is an example definition of a hook wrapper:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_pyfunc_call(pyfuncitem):
|
||||
do_something_before_next_hook_executes()
|
||||
|
@ -636,10 +639,13 @@ if you depend on a plugin that is not installed, validation will fail and
|
|||
the error message will not make much sense to your users.
|
||||
|
||||
One approach is to defer the hook implementation to a new plugin instead of
|
||||
declaring the hook functions directly in your plugin module, for example::
|
||||
declaring the hook functions directly in your plugin module, for example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# contents of myplugin.py
|
||||
|
||||
|
||||
class DeferPlugin(object):
|
||||
"""Simple plugin to defer pytest-xdist hook functions."""
|
||||
|
||||
|
@ -647,8 +653,9 @@ declaring the hook functions directly in your plugin module, for example::
|
|||
"""standard xdist hook function.
|
||||
"""
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
if config.pluginmanager.hasplugin('xdist'):
|
||||
if config.pluginmanager.hasplugin("xdist"):
|
||||
config.pluginmanager.register(DeferPlugin())
|
||||
|
||||
This has the added benefit of allowing you to conditionally install hooks
|
||||
|
|
|
@ -285,20 +285,30 @@ def _compare_eq_iterable(left, right, verbose=0):
|
|||
|
||||
def _compare_eq_sequence(left, right, verbose=0):
|
||||
explanation = []
|
||||
for i in range(min(len(left), len(right))):
|
||||
len_left = len(left)
|
||||
len_right = len(right)
|
||||
for i in range(min(len_left, len_right)):
|
||||
if left[i] != right[i]:
|
||||
explanation += [u"At index %s diff: %r != %r" % (i, left[i], right[i])]
|
||||
break
|
||||
if len(left) > len(right):
|
||||
explanation += [
|
||||
u"Left contains more items, first extra item: %s"
|
||||
% saferepr(left[len(right)])
|
||||
]
|
||||
elif len(left) < len(right):
|
||||
explanation += [
|
||||
u"Right contains more items, first extra item: %s"
|
||||
% saferepr(right[len(left)])
|
||||
]
|
||||
len_diff = len_left - len_right
|
||||
|
||||
if len_diff:
|
||||
if len_diff > 0:
|
||||
dir_with_more = "Left"
|
||||
extra = saferepr(left[len_right])
|
||||
else:
|
||||
len_diff = 0 - len_diff
|
||||
dir_with_more = "Right"
|
||||
extra = saferepr(right[len_left])
|
||||
|
||||
if len_diff == 1:
|
||||
explanation += [u"%s contains one more item: %s" % (dir_with_more, extra)]
|
||||
else:
|
||||
explanation += [
|
||||
u"%s contains %d more items, first extra item: %s"
|
||||
% (dir_with_more, len_diff, extra)
|
||||
]
|
||||
return explanation
|
||||
|
||||
|
||||
|
@ -319,7 +329,9 @@ def _compare_eq_set(left, right, verbose=0):
|
|||
|
||||
def _compare_eq_dict(left, right, verbose=0):
|
||||
explanation = []
|
||||
common = set(left).intersection(set(right))
|
||||
set_left = set(left)
|
||||
set_right = set(right)
|
||||
common = set_left.intersection(set_right)
|
||||
same = {k: left[k] for k in common if left[k] == right[k]}
|
||||
if same and verbose < 2:
|
||||
explanation += [u"Omitting %s identical items, use -vv to show" % len(same)]
|
||||
|
@ -331,15 +343,23 @@ def _compare_eq_dict(left, right, verbose=0):
|
|||
explanation += [u"Differing items:"]
|
||||
for k in diff:
|
||||
explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})]
|
||||
extra_left = set(left) - set(right)
|
||||
if extra_left:
|
||||
explanation.append(u"Left contains more items:")
|
||||
extra_left = set_left - set_right
|
||||
len_extra_left = len(extra_left)
|
||||
if len_extra_left:
|
||||
explanation.append(
|
||||
u"Left contains %d more item%s:"
|
||||
% (len_extra_left, "" if len_extra_left == 1 else "s")
|
||||
)
|
||||
explanation.extend(
|
||||
pprint.pformat({k: left[k] for k in extra_left}).splitlines()
|
||||
)
|
||||
extra_right = set(right) - set(left)
|
||||
if extra_right:
|
||||
explanation.append(u"Right contains more items:")
|
||||
extra_right = set_right - set_left
|
||||
len_extra_right = len(extra_right)
|
||||
if len_extra_right:
|
||||
explanation.append(
|
||||
u"Right contains %d more item%s:"
|
||||
% (len_extra_right, "" if len_extra_right == 1 else "s")
|
||||
)
|
||||
explanation.extend(
|
||||
pprint.pformat({k: right[k] for k in extra_right}).splitlines()
|
||||
)
|
||||
|
|
|
@ -179,45 +179,45 @@ class LFPlugin(object):
|
|||
self.lastfailed[report.nodeid] = True
|
||||
|
||||
def pytest_collection_modifyitems(self, session, config, items):
|
||||
if self.active:
|
||||
if self.lastfailed:
|
||||
previously_failed = []
|
||||
previously_passed = []
|
||||
for item in items:
|
||||
if item.nodeid in self.lastfailed:
|
||||
previously_failed.append(item)
|
||||
else:
|
||||
previously_passed.append(item)
|
||||
self._previously_failed_count = len(previously_failed)
|
||||
if not self.active:
|
||||
return
|
||||
|
||||
if not previously_failed:
|
||||
# Running a subset of all tests with recorded failures
|
||||
# only outside of it.
|
||||
self._report_status = "%d known failures not in selected tests" % (
|
||||
len(self.lastfailed),
|
||||
)
|
||||
if self.lastfailed:
|
||||
previously_failed = []
|
||||
previously_passed = []
|
||||
for item in items:
|
||||
if item.nodeid in self.lastfailed:
|
||||
previously_failed.append(item)
|
||||
else:
|
||||
if self.config.getoption("lf"):
|
||||
items[:] = previously_failed
|
||||
config.hook.pytest_deselected(items=previously_passed)
|
||||
else: # --failedfirst
|
||||
items[:] = previously_failed + previously_passed
|
||||
previously_passed.append(item)
|
||||
self._previously_failed_count = len(previously_failed)
|
||||
|
||||
noun = (
|
||||
"failure" if self._previously_failed_count == 1 else "failures"
|
||||
)
|
||||
suffix = " first" if self.config.getoption("failedfirst") else ""
|
||||
self._report_status = "rerun previous {count} {noun}{suffix}".format(
|
||||
count=self._previously_failed_count, suffix=suffix, noun=noun
|
||||
)
|
||||
if not previously_failed:
|
||||
# Running a subset of all tests with recorded failures
|
||||
# only outside of it.
|
||||
self._report_status = "%d known failures not in selected tests" % (
|
||||
len(self.lastfailed),
|
||||
)
|
||||
else:
|
||||
self._report_status = "no previously failed tests, "
|
||||
if self.config.getoption("last_failed_no_failures") == "none":
|
||||
self._report_status += "deselecting all items."
|
||||
config.hook.pytest_deselected(items=items)
|
||||
items[:] = []
|
||||
else:
|
||||
self._report_status += "not deselecting items."
|
||||
if self.config.getoption("lf"):
|
||||
items[:] = previously_failed
|
||||
config.hook.pytest_deselected(items=previously_passed)
|
||||
else: # --failedfirst
|
||||
items[:] = previously_failed + previously_passed
|
||||
|
||||
noun = "failure" if self._previously_failed_count == 1 else "failures"
|
||||
suffix = " first" if self.config.getoption("failedfirst") else ""
|
||||
self._report_status = "rerun previous {count} {noun}{suffix}".format(
|
||||
count=self._previously_failed_count, suffix=suffix, noun=noun
|
||||
)
|
||||
else:
|
||||
self._report_status = "no previously failed tests, "
|
||||
if self.config.getoption("last_failed_no_failures") == "none":
|
||||
self._report_status += "deselecting all items."
|
||||
config.hook.pytest_deselected(items=items)
|
||||
items[:] = []
|
||||
else:
|
||||
self._report_status += "not deselecting items."
|
||||
|
||||
def pytest_sessionfinish(self, session):
|
||||
config = self.config
|
||||
|
@ -292,9 +292,13 @@ def pytest_addoption(parser):
|
|||
)
|
||||
group.addoption(
|
||||
"--cache-show",
|
||||
action="store_true",
|
||||
action="append",
|
||||
nargs="?",
|
||||
dest="cacheshow",
|
||||
help="show cache contents, don't perform collection or tests",
|
||||
help=(
|
||||
"show cache contents, don't perform collection or tests. "
|
||||
"Optional argument: glob (default: '*')."
|
||||
),
|
||||
)
|
||||
group.addoption(
|
||||
"--cache-clear",
|
||||
|
@ -369,11 +373,16 @@ def cacheshow(config, session):
|
|||
if not config.cache._cachedir.is_dir():
|
||||
tw.line("cache is empty")
|
||||
return 0
|
||||
|
||||
glob = config.option.cacheshow[0]
|
||||
if glob is None:
|
||||
glob = "*"
|
||||
|
||||
dummy = object()
|
||||
basedir = config.cache._cachedir
|
||||
vdir = basedir / "v"
|
||||
tw.sep("-", "cache values")
|
||||
for valpath in sorted(x for x in vdir.rglob("*") if x.is_file()):
|
||||
tw.sep("-", "cache values for %r" % glob)
|
||||
for valpath in sorted(x for x in vdir.rglob(glob) if x.is_file()):
|
||||
key = valpath.relative_to(vdir)
|
||||
val = config.cache.get(key, dummy)
|
||||
if val is dummy:
|
||||
|
@ -385,8 +394,8 @@ def cacheshow(config, session):
|
|||
|
||||
ddir = basedir / "d"
|
||||
if ddir.is_dir():
|
||||
contents = sorted(ddir.rglob("*"))
|
||||
tw.sep("-", "cache directories")
|
||||
contents = sorted(ddir.rglob(glob))
|
||||
tw.sep("-", "cache directories for %r" % glob)
|
||||
for p in contents:
|
||||
# if p.check(dir=1):
|
||||
# print("%s/" % p.relto(basedir))
|
||||
|
|
|
@ -282,7 +282,6 @@ class PytestPluginManager(PluginManager):
|
|||
known_marks = {m.name for m in getattr(method, "pytestmark", [])}
|
||||
|
||||
for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"):
|
||||
|
||||
opts.setdefault(name, hasattr(method, name) or name in known_marks)
|
||||
return opts
|
||||
|
||||
|
|
|
@ -10,31 +10,18 @@ from doctest import UnexpectedException
|
|||
|
||||
from _pytest import outcomes
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.config.exceptions import UsageError
|
||||
|
||||
|
||||
def _validate_usepdb_cls(value):
|
||||
"""Validate syntax of --pdbcls option."""
|
||||
try:
|
||||
modname, classname = value.split(":")
|
||||
except ValueError:
|
||||
raise argparse.ArgumentTypeError(
|
||||
"{!r} is not in the format 'modname:classname'".format(value)
|
||||
)
|
||||
|
||||
try:
|
||||
__import__(modname)
|
||||
mod = sys.modules[modname]
|
||||
|
||||
# Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp).
|
||||
parts = classname.split(".")
|
||||
pdb_cls = getattr(mod, parts[0])
|
||||
for part in parts[1:]:
|
||||
pdb_cls = getattr(pdb_cls, part)
|
||||
|
||||
return pdb_cls
|
||||
except Exception as exc:
|
||||
raise argparse.ArgumentTypeError(
|
||||
"could not get pdb class for {!r}: {}".format(value, exc)
|
||||
)
|
||||
return (modname, classname)
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
|
@ -68,9 +55,28 @@ def pytest_addoption(parser):
|
|||
)
|
||||
|
||||
|
||||
def _import_pdbcls(modname, classname):
|
||||
try:
|
||||
__import__(modname)
|
||||
mod = sys.modules[modname]
|
||||
|
||||
# Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp).
|
||||
parts = classname.split(".")
|
||||
pdb_cls = getattr(mod, parts[0])
|
||||
for part in parts[1:]:
|
||||
pdb_cls = getattr(pdb_cls, part)
|
||||
|
||||
return pdb_cls
|
||||
except Exception as exc:
|
||||
value = ":".join((modname, classname))
|
||||
raise UsageError("--pdbcls: could not import {!r}: {}".format(value, exc))
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
pdb_cls = config.getvalue("usepdb_cls")
|
||||
if not pdb_cls:
|
||||
if pdb_cls:
|
||||
pdb_cls = _import_pdbcls(*pdb_cls)
|
||||
else:
|
||||
pdb_cls = pdb.Pdb
|
||||
|
||||
if config.getvalue("trace"):
|
||||
|
@ -250,7 +256,7 @@ def _test_pytest_function(pyfuncitem):
|
|||
_pdb = pytestPDB._init_pdb()
|
||||
testfunction = pyfuncitem.obj
|
||||
pyfuncitem.obj = _pdb.runcall
|
||||
if "func" in pyfuncitem._fixtureinfo.argnames: # noqa
|
||||
if "func" in pyfuncitem._fixtureinfo.argnames: # pragma: no branch
|
||||
raise ValueError("--trace can't be used with a fixture named func!")
|
||||
pyfuncitem.funcargs["func"] = testfunction
|
||||
new_list = list(pyfuncitem._fixtureinfo.argnames)
|
||||
|
|
|
@ -93,3 +93,9 @@ PYTEST_WARNS_UNKNOWN_KWARGS = UnformattedWarning(
|
|||
"pytest.warns() got unexpected keyword arguments: {args!r}.\n"
|
||||
"This will be an error in future versions.",
|
||||
)
|
||||
|
||||
PYTEST_PARAM_UNKNOWN_KWARGS = UnformattedWarning(
|
||||
PytestDeprecationWarning,
|
||||
"pytest.param() got unexpected keyword arguments: {args!r}.\n"
|
||||
"This will be an error in future versions.",
|
||||
)
|
||||
|
|
|
@ -853,7 +853,9 @@ class FixtureDef(object):
|
|||
exceptions.append(sys.exc_info())
|
||||
if exceptions:
|
||||
e = exceptions[0]
|
||||
del exceptions # ensure we don't keep all frames alive because of the traceback
|
||||
del (
|
||||
exceptions
|
||||
) # ensure we don't keep all frames alive because of the traceback
|
||||
six.reraise(*e)
|
||||
|
||||
finally:
|
||||
|
|
|
@ -151,13 +151,14 @@ def showhelp(config):
|
|||
)
|
||||
tw.line()
|
||||
|
||||
columns = tw.fullwidth # costly call
|
||||
for name in config._parser._ininames:
|
||||
help, type, default = config._parser._inidict[name]
|
||||
if type is None:
|
||||
type = "string"
|
||||
spec = "%s (%s)" % (name, type)
|
||||
line = " %-24s %s" % (spec, help)
|
||||
tw.line(line[: tw.fullwidth])
|
||||
tw.line(line[:columns])
|
||||
|
||||
tw.line()
|
||||
tw.line("environment variables:")
|
||||
|
|
|
@ -227,7 +227,7 @@ def pytest_collectreport(report):
|
|||
|
||||
|
||||
def pytest_deselected(items):
|
||||
""" called for test items deselected by keyword. """
|
||||
""" called for test items deselected, e.g. by keyword. """
|
||||
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
|
|
|
@ -252,7 +252,14 @@ class _NodeReporter(object):
|
|||
|
||||
def append_skipped(self, report):
|
||||
if hasattr(report, "wasxfail"):
|
||||
self._add_simple(Junit.skipped, "expected test failure", report.wasxfail)
|
||||
xfailreason = report.wasxfail
|
||||
if xfailreason.startswith("reason: "):
|
||||
xfailreason = xfailreason[8:]
|
||||
self.append(
|
||||
Junit.skipped(
|
||||
"", type="pytest.xfail", message=bin_xml_escape(xfailreason)
|
||||
)
|
||||
)
|
||||
else:
|
||||
filename, lineno, skipreason = report.longrepr
|
||||
if skipreason.startswith("Skipped: "):
|
||||
|
|
|
@ -10,6 +10,7 @@ from ..compat import ascii_escaped
|
|||
from ..compat import getfslineno
|
||||
from ..compat import MappingMixin
|
||||
from ..compat import NOTSET
|
||||
from _pytest.deprecated import PYTEST_PARAM_UNKNOWN_KWARGS
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.warning_types import UnknownMarkWarning
|
||||
|
||||
|
@ -61,20 +62,25 @@ def get_empty_parameterset_mark(config, argnames, func):
|
|||
|
||||
class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
|
||||
@classmethod
|
||||
def param(cls, *values, **kw):
|
||||
marks = kw.pop("marks", ())
|
||||
def param(cls, *values, **kwargs):
|
||||
marks = kwargs.pop("marks", ())
|
||||
if isinstance(marks, MarkDecorator):
|
||||
marks = (marks,)
|
||||
else:
|
||||
assert isinstance(marks, (tuple, list, set))
|
||||
|
||||
id_ = kw.pop("id", None)
|
||||
id_ = kwargs.pop("id", None)
|
||||
if id_ is not None:
|
||||
if not isinstance(id_, six.string_types):
|
||||
raise TypeError(
|
||||
"Expected id to be a string, got {}: {!r}".format(type(id_), id_)
|
||||
)
|
||||
id_ = ascii_escaped(id_)
|
||||
|
||||
if kwargs:
|
||||
warnings.warn(
|
||||
PYTEST_PARAM_UNKNOWN_KWARGS.format(args=sorted(kwargs)), stacklevel=3
|
||||
)
|
||||
return cls(values, marks, id_)
|
||||
|
||||
@classmethod
|
||||
|
@ -298,7 +304,7 @@ class MarkGenerator(object):
|
|||
for line in self._config.getini("markers"):
|
||||
# example lines: "skipif(condition): skip the given test if..."
|
||||
# or "hypothesis: tests which use Hypothesis", so to get the
|
||||
# marker name we we split on both `:` and `(`.
|
||||
# marker name we split on both `:` and `(`.
|
||||
marker = line.split(":")[0].split("(")[0].strip()
|
||||
self._markers.add(marker)
|
||||
|
||||
|
@ -306,7 +312,7 @@ class MarkGenerator(object):
|
|||
# then it really is time to issue a warning or an error.
|
||||
if name not in self._markers:
|
||||
if self._config.option.strict:
|
||||
fail("{!r} not a registered marker".format(name), pytrace=False)
|
||||
fail("{!r} is not a registered marker".format(name), pytrace=False)
|
||||
else:
|
||||
warnings.warn(
|
||||
"Unknown pytest.mark.%s - is this a typo? You can register "
|
||||
|
|
|
@ -271,6 +271,18 @@ class MonkeyPatch(object):
|
|||
# https://github.com/pypa/setuptools/blob/d8b901bc/docs/pkg_resources.txt#L162-L171
|
||||
fixup_namespace_packages(str(path))
|
||||
|
||||
# A call to syspathinsert() usually means that the caller wants to
|
||||
# import some dynamically created files, thus with python3 we
|
||||
# invalidate its import caches.
|
||||
# This is especially important when any namespace package is in used,
|
||||
# since then the mtime based FileFinder cache (that gets created in
|
||||
# this case already) gets not invalidated when writing the new files
|
||||
# quickly afterwards.
|
||||
if sys.version_info >= (3, 3):
|
||||
from importlib import invalidate_caches
|
||||
|
||||
invalidate_caches()
|
||||
|
||||
def chdir(self, path):
|
||||
""" Change the current working directory to the specified path.
|
||||
Path can be a string or a py.path.local object.
|
||||
|
|
|
@ -97,8 +97,7 @@ def skip(msg="", **kwargs):
|
|||
__tracebackhide__ = True
|
||||
allow_module_level = kwargs.pop("allow_module_level", False)
|
||||
if kwargs:
|
||||
keys = [k for k in kwargs.keys()]
|
||||
raise TypeError("unexpected keyword arguments: {}".format(keys))
|
||||
raise TypeError("unexpected keyword arguments: {}".format(sorted(kwargs)))
|
||||
raise Skipped(msg=msg, allow_module_level=allow_module_level)
|
||||
|
||||
|
||||
|
|
|
@ -76,8 +76,11 @@ def pytest_configure(config):
|
|||
|
||||
|
||||
def raise_on_kwargs(kwargs):
|
||||
if kwargs:
|
||||
raise TypeError("Unexpected arguments: {}".format(", ".join(sorted(kwargs))))
|
||||
__tracebackhide__ = True
|
||||
if kwargs: # pragma: no branch
|
||||
raise TypeError(
|
||||
"Unexpected keyword arguments: {}".format(", ".join(sorted(kwargs)))
|
||||
)
|
||||
|
||||
|
||||
class LsofFdLeakChecker(object):
|
||||
|
@ -309,7 +312,8 @@ class HookRecorder(object):
|
|||
passed.append(rep)
|
||||
elif rep.skipped:
|
||||
skipped.append(rep)
|
||||
elif rep.failed:
|
||||
else:
|
||||
assert rep.failed, "Unexpected outcome: {!r}".format(rep)
|
||||
failed.append(rep)
|
||||
return passed, skipped, failed
|
||||
|
||||
|
@ -341,6 +345,15 @@ def testdir(request, tmpdir_factory):
|
|||
return Testdir(request, tmpdir_factory)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _sys_snapshot():
|
||||
snappaths = SysPathsSnapshot()
|
||||
snapmods = SysModulesSnapshot()
|
||||
yield
|
||||
snapmods.restore()
|
||||
snappaths.restore()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def _config_for_test():
|
||||
from _pytest.config import get_config
|
||||
|
@ -473,6 +486,8 @@ class Testdir(object):
|
|||
|
||||
"""
|
||||
|
||||
CLOSE_STDIN = object
|
||||
|
||||
class TimeoutExpired(Exception):
|
||||
pass
|
||||
|
||||
|
@ -613,27 +628,10 @@ class Testdir(object):
|
|||
This is undone automatically when this object dies at the end of each
|
||||
test.
|
||||
"""
|
||||
from pkg_resources import fixup_namespace_packages
|
||||
|
||||
if path is None:
|
||||
path = self.tmpdir
|
||||
|
||||
dirname = str(path)
|
||||
sys.path.insert(0, dirname)
|
||||
fixup_namespace_packages(dirname)
|
||||
|
||||
# a call to syspathinsert() usually means that the caller wants to
|
||||
# import some dynamically created files, thus with python3 we
|
||||
# invalidate its import caches
|
||||
self._possibly_invalidate_import_caches()
|
||||
|
||||
def _possibly_invalidate_import_caches(self):
|
||||
# invalidate caches if we can (py33 and above)
|
||||
try:
|
||||
from importlib import invalidate_caches
|
||||
except ImportError:
|
||||
return
|
||||
invalidate_caches()
|
||||
self.monkeypatch.syspath_prepend(str(path))
|
||||
|
||||
def mkdir(self, name):
|
||||
"""Create a new (sub)directory."""
|
||||
|
@ -801,12 +799,15 @@ class Testdir(object):
|
|||
|
||||
:param args: command line arguments to pass to :py:func:`pytest.main`
|
||||
|
||||
:param plugin: (keyword-only) extra plugin instances the
|
||||
:param plugins: (keyword-only) extra plugin instances the
|
||||
``pytest.main()`` instance should use
|
||||
|
||||
:return: a :py:class:`HookRecorder` instance
|
||||
|
||||
"""
|
||||
plugins = kwargs.pop("plugins", [])
|
||||
no_reraise_ctrlc = kwargs.pop("no_reraise_ctrlc", None)
|
||||
raise_on_kwargs(kwargs)
|
||||
|
||||
finalizers = []
|
||||
try:
|
||||
# Do not load user config (during runs only).
|
||||
|
@ -846,7 +847,6 @@ class Testdir(object):
|
|||
def pytest_configure(x, config):
|
||||
rec.append(self.make_hook_recorder(config.pluginmanager))
|
||||
|
||||
plugins = kwargs.get("plugins") or []
|
||||
plugins.append(Collect())
|
||||
ret = pytest.main(list(args), plugins=plugins)
|
||||
if len(rec) == 1:
|
||||
|
@ -860,7 +860,7 @@ class Testdir(object):
|
|||
|
||||
# typically we reraise keyboard interrupts from the child run
|
||||
# because it's our user requesting interruption of the testing
|
||||
if ret == EXIT_INTERRUPTED and not kwargs.get("no_reraise_ctrlc"):
|
||||
if ret == EXIT_INTERRUPTED and not no_reraise_ctrlc:
|
||||
calls = reprec.getcalls("pytest_keyboard_interrupt")
|
||||
if calls and calls[-1].excinfo.type == KeyboardInterrupt:
|
||||
raise KeyboardInterrupt()
|
||||
|
@ -872,9 +872,10 @@ class Testdir(object):
|
|||
def runpytest_inprocess(self, *args, **kwargs):
|
||||
"""Return result of running pytest in-process, providing a similar
|
||||
interface to what self.runpytest() provides.
|
||||
|
||||
"""
|
||||
if kwargs.get("syspathinsert"):
|
||||
syspathinsert = kwargs.pop("syspathinsert", False)
|
||||
|
||||
if syspathinsert:
|
||||
self.syspathinsert()
|
||||
now = time.time()
|
||||
capture = MultiCapture(Capture=SysCapture)
|
||||
|
@ -1032,7 +1033,14 @@ class Testdir(object):
|
|||
if colitem.name == name:
|
||||
return colitem
|
||||
|
||||
def popen(self, cmdargs, stdout, stderr, **kw):
|
||||
def popen(
|
||||
self,
|
||||
cmdargs,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
stdin=CLOSE_STDIN,
|
||||
**kw
|
||||
):
|
||||
"""Invoke subprocess.Popen.
|
||||
|
||||
This calls subprocess.Popen making sure the current working directory
|
||||
|
@ -1050,10 +1058,18 @@ class Testdir(object):
|
|||
env["USERPROFILE"] = env["HOME"]
|
||||
kw["env"] = env
|
||||
|
||||
popen = subprocess.Popen(
|
||||
cmdargs, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr, **kw
|
||||
)
|
||||
popen.stdin.close()
|
||||
if stdin is Testdir.CLOSE_STDIN:
|
||||
kw["stdin"] = subprocess.PIPE
|
||||
elif isinstance(stdin, bytes):
|
||||
kw["stdin"] = subprocess.PIPE
|
||||
else:
|
||||
kw["stdin"] = stdin
|
||||
|
||||
popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
|
||||
if stdin is Testdir.CLOSE_STDIN:
|
||||
popen.stdin.close()
|
||||
elif isinstance(stdin, bytes):
|
||||
popen.stdin.write(stdin)
|
||||
|
||||
return popen
|
||||
|
||||
|
@ -1065,6 +1081,10 @@ class Testdir(object):
|
|||
:param args: the sequence of arguments to pass to `subprocess.Popen()`
|
||||
:param timeout: the period in seconds after which to timeout and raise
|
||||
:py:class:`Testdir.TimeoutExpired`
|
||||
:param stdin: optional standard input. Bytes are being send, closing
|
||||
the pipe, otherwise it is passed through to ``popen``.
|
||||
Defaults to ``CLOSE_STDIN``, which translates to using a pipe
|
||||
(``subprocess.PIPE``) that gets closed.
|
||||
|
||||
Returns a :py:class:`RunResult`.
|
||||
|
||||
|
@ -1072,6 +1092,7 @@ class Testdir(object):
|
|||
__tracebackhide__ = True
|
||||
|
||||
timeout = kwargs.pop("timeout", None)
|
||||
stdin = kwargs.pop("stdin", Testdir.CLOSE_STDIN)
|
||||
raise_on_kwargs(kwargs)
|
||||
|
||||
cmdargs = [
|
||||
|
@ -1086,8 +1107,14 @@ class Testdir(object):
|
|||
try:
|
||||
now = time.time()
|
||||
popen = self.popen(
|
||||
cmdargs, stdout=f1, stderr=f2, close_fds=(sys.platform != "win32")
|
||||
cmdargs,
|
||||
stdin=stdin,
|
||||
stdout=f1,
|
||||
stderr=f2,
|
||||
close_fds=(sys.platform != "win32"),
|
||||
)
|
||||
if isinstance(stdin, bytes):
|
||||
popen.stdin.close()
|
||||
|
||||
def handle_timeout():
|
||||
__tracebackhide__ = True
|
||||
|
@ -1173,9 +1200,10 @@ class Testdir(object):
|
|||
:py:class:`Testdir.TimeoutExpired`
|
||||
|
||||
Returns a :py:class:`RunResult`.
|
||||
|
||||
"""
|
||||
__tracebackhide__ = True
|
||||
timeout = kwargs.pop("timeout", None)
|
||||
raise_on_kwargs(kwargs)
|
||||
|
||||
p = py.path.local.make_numbered_dir(
|
||||
prefix="runpytest-", keep=None, rootdir=self.tmpdir
|
||||
|
@ -1185,7 +1213,7 @@ class Testdir(object):
|
|||
if plugins:
|
||||
args = ("-p", plugins[0]) + args
|
||||
args = self._getpytestargs() + args
|
||||
return self.run(*args, timeout=kwargs.get("timeout"))
|
||||
return self.run(*args, timeout=timeout)
|
||||
|
||||
def spawn_pytest(self, string, expect_timeout=10.0):
|
||||
"""Run pytest using pexpect.
|
||||
|
@ -1317,7 +1345,7 @@ class LineMatcher(object):
|
|||
raise ValueError("line %r not found in output" % fnline)
|
||||
|
||||
def _log(self, *args):
|
||||
self._log_output.append(" ".join((str(x) for x in args)))
|
||||
self._log_output.append(" ".join(str(x) for x in args))
|
||||
|
||||
@property
|
||||
def _log_text(self):
|
||||
|
|
|
@ -682,7 +682,7 @@ def raises(expected_exception, *args, **kwargs):
|
|||
match_expr = kwargs.pop("match")
|
||||
if kwargs:
|
||||
msg = "Unexpected keyword arguments passed to pytest.raises: "
|
||||
msg += ", ".join(kwargs.keys())
|
||||
msg += ", ".join(sorted(kwargs))
|
||||
raise TypeError(msg)
|
||||
return RaisesContext(expected_exception, message, match_expr)
|
||||
elif isinstance(args[0], str):
|
||||
|
|
|
@ -148,6 +148,12 @@ class BaseReport(object):
|
|||
fspath, lineno, domain = self.location
|
||||
return domain
|
||||
|
||||
def _get_verbose_word(self, config):
|
||||
_category, _short, verbose = config.hook.pytest_report_teststatus(
|
||||
report=self, config=config
|
||||
)
|
||||
return verbose
|
||||
|
||||
def _to_json(self):
|
||||
"""
|
||||
This was originally the serialize_report() function from xdist (ca03269).
|
||||
|
@ -328,7 +334,8 @@ class TestReport(BaseReport):
|
|||
self.__dict__.update(extra)
|
||||
|
||||
def __repr__(self):
|
||||
return "<TestReport %r when=%r outcome=%r>" % (
|
||||
return "<%s %r when=%r outcome=%r>" % (
|
||||
self.__class__.__name__,
|
||||
self.nodeid,
|
||||
self.when,
|
||||
self.outcome,
|
||||
|
|
|
@ -4,8 +4,6 @@ from __future__ import absolute_import
|
|||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import six
|
||||
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.mark.evaluate import MarkEvaluator
|
||||
from _pytest.outcomes import fail
|
||||
|
@ -186,174 +184,3 @@ def pytest_report_teststatus(report):
|
|||
return "xfailed", "x", "XFAIL"
|
||||
elif report.passed:
|
||||
return "xpassed", "X", "XPASS"
|
||||
|
||||
|
||||
# called by the terminalreporter instance/plugin
|
||||
|
||||
|
||||
def pytest_terminal_summary(terminalreporter):
|
||||
tr = terminalreporter
|
||||
if not tr.reportchars:
|
||||
return
|
||||
|
||||
lines = []
|
||||
for char in tr.reportchars:
|
||||
action = REPORTCHAR_ACTIONS.get(char, lambda tr, lines: None)
|
||||
action(terminalreporter, lines)
|
||||
|
||||
if lines:
|
||||
tr._tw.sep("=", "short test summary info")
|
||||
for line in lines:
|
||||
tr._tw.line(line)
|
||||
|
||||
|
||||
def _get_line_with_reprcrash_message(config, rep, termwidth):
|
||||
"""Get summary line for a report, trying to add reprcrash message."""
|
||||
from wcwidth import wcswidth
|
||||
|
||||
verbose_word = _get_report_str(config, rep)
|
||||
pos = _get_pos(config, rep)
|
||||
|
||||
line = "%s %s" % (verbose_word, pos)
|
||||
len_line = wcswidth(line)
|
||||
ellipsis, len_ellipsis = "...", 3
|
||||
if len_line > termwidth - len_ellipsis:
|
||||
# No space for an additional message.
|
||||
return line
|
||||
|
||||
try:
|
||||
msg = rep.longrepr.reprcrash.message
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
# Only use the first line.
|
||||
i = msg.find("\n")
|
||||
if i != -1:
|
||||
msg = msg[:i]
|
||||
len_msg = wcswidth(msg)
|
||||
|
||||
sep, len_sep = " - ", 3
|
||||
max_len_msg = termwidth - len_line - len_sep
|
||||
if max_len_msg >= len_ellipsis:
|
||||
if len_msg > max_len_msg:
|
||||
max_len_msg -= len_ellipsis
|
||||
msg = msg[:max_len_msg]
|
||||
while wcswidth(msg) > max_len_msg:
|
||||
msg = msg[:-1]
|
||||
if six.PY2:
|
||||
# on python 2 systems with narrow unicode compilation, trying to
|
||||
# get a single character out of a multi-byte unicode character such as
|
||||
# u'😄' will result in a High Surrogate (U+D83D) character, which is
|
||||
# rendered as u'<27>'; in this case we just strip that character out as it
|
||||
# serves no purpose being rendered
|
||||
while msg.endswith(u"\uD83D"):
|
||||
msg = msg[:-1]
|
||||
msg += ellipsis
|
||||
line += sep + msg
|
||||
return line
|
||||
|
||||
|
||||
def show_simple(terminalreporter, lines, stat):
|
||||
failed = terminalreporter.stats.get(stat)
|
||||
if failed:
|
||||
config = terminalreporter.config
|
||||
termwidth = terminalreporter.writer.fullwidth
|
||||
for rep in failed:
|
||||
line = _get_line_with_reprcrash_message(config, rep, termwidth)
|
||||
lines.append(line)
|
||||
|
||||
|
||||
def show_xfailed(terminalreporter, lines):
|
||||
xfailed = terminalreporter.stats.get("xfailed")
|
||||
if xfailed:
|
||||
config = terminalreporter.config
|
||||
for rep in xfailed:
|
||||
verbose_word = _get_report_str(config, rep)
|
||||
pos = _get_pos(config, rep)
|
||||
lines.append("%s %s" % (verbose_word, pos))
|
||||
reason = rep.wasxfail
|
||||
if reason:
|
||||
lines.append(" " + str(reason))
|
||||
|
||||
|
||||
def show_xpassed(terminalreporter, lines):
|
||||
xpassed = terminalreporter.stats.get("xpassed")
|
||||
if xpassed:
|
||||
config = terminalreporter.config
|
||||
for rep in xpassed:
|
||||
verbose_word = _get_report_str(config, rep)
|
||||
pos = _get_pos(config, rep)
|
||||
reason = rep.wasxfail
|
||||
lines.append("%s %s %s" % (verbose_word, pos, reason))
|
||||
|
||||
|
||||
def folded_skips(skipped):
|
||||
d = {}
|
||||
for event in skipped:
|
||||
key = event.longrepr
|
||||
assert len(key) == 3, (event, key)
|
||||
keywords = getattr(event, "keywords", {})
|
||||
# folding reports with global pytestmark variable
|
||||
# this is workaround, because for now we cannot identify the scope of a skip marker
|
||||
# TODO: revisit after marks scope would be fixed
|
||||
if (
|
||||
event.when == "setup"
|
||||
and "skip" in keywords
|
||||
and "pytestmark" not in keywords
|
||||
):
|
||||
key = (key[0], None, key[2])
|
||||
d.setdefault(key, []).append(event)
|
||||
values = []
|
||||
for key, events in d.items():
|
||||
values.append((len(events),) + key)
|
||||
return values
|
||||
|
||||
|
||||
def show_skipped(terminalreporter, lines):
|
||||
tr = terminalreporter
|
||||
skipped = tr.stats.get("skipped", [])
|
||||
if skipped:
|
||||
fskips = folded_skips(skipped)
|
||||
if fskips:
|
||||
verbose_word = _get_report_str(terminalreporter.config, report=skipped[0])
|
||||
for num, fspath, lineno, reason in fskips:
|
||||
if reason.startswith("Skipped: "):
|
||||
reason = reason[9:]
|
||||
if lineno is not None:
|
||||
lines.append(
|
||||
"%s [%d] %s:%d: %s"
|
||||
% (verbose_word, num, fspath, lineno + 1, reason)
|
||||
)
|
||||
else:
|
||||
lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason))
|
||||
|
||||
|
||||
def shower(stat):
|
||||
def show_(terminalreporter, lines):
|
||||
return show_simple(terminalreporter, lines, stat)
|
||||
|
||||
return show_
|
||||
|
||||
|
||||
def _get_report_str(config, report):
|
||||
_category, _short, verbose = config.hook.pytest_report_teststatus(
|
||||
report=report, config=config
|
||||
)
|
||||
return verbose
|
||||
|
||||
|
||||
def _get_pos(config, rep):
|
||||
nodeid = config.cwd_relative_nodeid(rep.nodeid)
|
||||
return nodeid
|
||||
|
||||
|
||||
REPORTCHAR_ACTIONS = {
|
||||
"x": show_xfailed,
|
||||
"X": show_xpassed,
|
||||
"f": shower("failed"),
|
||||
"F": shower("failed"),
|
||||
"s": show_skipped,
|
||||
"S": show_skipped,
|
||||
"p": shower("passed"),
|
||||
"E": shower("error"),
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import collections
|
|||
import platform
|
||||
import sys
|
||||
import time
|
||||
from functools import partial
|
||||
|
||||
import attr
|
||||
import pluggy
|
||||
|
@ -81,11 +82,11 @@ def pytest_addoption(parser):
|
|||
dest="reportchars",
|
||||
default="",
|
||||
metavar="chars",
|
||||
help="show extra test summary info as specified by chars (f)ailed, "
|
||||
"(E)error, (s)skipped, (x)failed, (X)passed, "
|
||||
"(p)passed, (P)passed with output, (a)all except pP. "
|
||||
help="show extra test summary info as specified by chars: (f)ailed, "
|
||||
"(E)rror, (s)kipped, (x)failed, (X)passed, "
|
||||
"(p)assed, (P)assed with output, (a)ll except passed (p/P), or (A)ll. "
|
||||
"Warnings are displayed at all times except when "
|
||||
"--disable-warnings is set",
|
||||
"--disable-warnings is set.",
|
||||
)
|
||||
group._addoption(
|
||||
"--disable-warnings",
|
||||
|
@ -140,7 +141,7 @@ def pytest_addoption(parser):
|
|||
|
||||
parser.addini(
|
||||
"console_output_style",
|
||||
help="console output: classic or with additional progress information (classic|progress).",
|
||||
help='console output: "classic", or with additional progress information ("progress" (percentage) | "count").',
|
||||
default="progress",
|
||||
)
|
||||
|
||||
|
@ -164,12 +165,14 @@ def getreportopt(config):
|
|||
reportchars += "w"
|
||||
elif config.option.disable_warnings and "w" in reportchars:
|
||||
reportchars = reportchars.replace("w", "")
|
||||
if reportchars:
|
||||
for char in reportchars:
|
||||
if char not in reportopts and char != "a":
|
||||
reportopts += char
|
||||
elif char == "a":
|
||||
reportopts = "sxXwEf"
|
||||
for char in reportchars:
|
||||
if char == "a":
|
||||
reportopts = "sxXwEf"
|
||||
elif char == "A":
|
||||
reportopts = "sxXwEfpP"
|
||||
break
|
||||
elif char not in reportopts:
|
||||
reportopts += char
|
||||
return reportopts
|
||||
|
||||
|
||||
|
@ -254,7 +257,10 @@ class TerminalReporter(object):
|
|||
# do not show progress if we are showing fixture setup/teardown
|
||||
if self.config.getoption("setupshow", False):
|
||||
return False
|
||||
return self.config.getini("console_output_style") in ("progress", "count")
|
||||
cfg = self.config.getini("console_output_style")
|
||||
if cfg in ("progress", "count"):
|
||||
return cfg
|
||||
return False
|
||||
|
||||
@property
|
||||
def verbosity(self):
|
||||
|
@ -438,18 +444,18 @@ class TerminalReporter(object):
|
|||
self.currentfspath = -2
|
||||
|
||||
def pytest_runtest_logfinish(self, nodeid):
|
||||
if self.config.getini("console_output_style") == "count":
|
||||
num_tests = self._session.testscollected
|
||||
progress_length = len(" [{}/{}]".format(str(num_tests), str(num_tests)))
|
||||
else:
|
||||
progress_length = len(" [100%]")
|
||||
|
||||
if self.verbosity <= 0 and self._show_progress_info:
|
||||
if self._show_progress_info == "count":
|
||||
num_tests = self._session.testscollected
|
||||
progress_length = len(" [{}/{}]".format(str(num_tests), str(num_tests)))
|
||||
else:
|
||||
progress_length = len(" [100%]")
|
||||
|
||||
self._progress_nodeids_reported.add(nodeid)
|
||||
last_item = (
|
||||
is_last_item = (
|
||||
len(self._progress_nodeids_reported) == self._session.testscollected
|
||||
)
|
||||
if last_item:
|
||||
if is_last_item:
|
||||
self._write_progress_information_filling_space()
|
||||
else:
|
||||
w = self._width_of_current_line
|
||||
|
@ -460,7 +466,7 @@ class TerminalReporter(object):
|
|||
|
||||
def _get_progress_information_message(self):
|
||||
collected = self._session.testscollected
|
||||
if self.config.getini("console_output_style") == "count":
|
||||
if self._show_progress_info == "count":
|
||||
if collected:
|
||||
progress = self._progress_nodeids_reported
|
||||
counter_format = "{{:{}d}}".format(len(str(collected)))
|
||||
|
@ -677,8 +683,9 @@ class TerminalReporter(object):
|
|||
self.summary_errors()
|
||||
self.summary_failures()
|
||||
self.summary_warnings()
|
||||
yield
|
||||
self.summary_passes()
|
||||
yield
|
||||
self.short_test_summary()
|
||||
# Display any extra warnings from teardown here (if any).
|
||||
self.summary_warnings()
|
||||
|
||||
|
@ -726,10 +733,10 @@ class TerminalReporter(object):
|
|||
return res + " "
|
||||
|
||||
def _getfailureheadline(self, rep):
|
||||
if rep.head_line:
|
||||
return rep.head_line
|
||||
else:
|
||||
return "test session" # XXX?
|
||||
head_line = rep.head_line
|
||||
if head_line:
|
||||
return head_line
|
||||
return "test session" # XXX?
|
||||
|
||||
def _getcrashline(self, rep):
|
||||
try:
|
||||
|
@ -820,17 +827,22 @@ class TerminalReporter(object):
|
|||
if not reports:
|
||||
return
|
||||
self.write_sep("=", "FAILURES")
|
||||
for rep in reports:
|
||||
if self.config.option.tbstyle == "line":
|
||||
if self.config.option.tbstyle == "line":
|
||||
for rep in reports:
|
||||
line = self._getcrashline(rep)
|
||||
self.write_line(line)
|
||||
else:
|
||||
else:
|
||||
teardown_sections = {}
|
||||
for report in self.getreports(""):
|
||||
if report.when == "teardown":
|
||||
teardown_sections.setdefault(report.nodeid, []).append(report)
|
||||
|
||||
for rep in reports:
|
||||
msg = self._getfailureheadline(rep)
|
||||
self.write_sep("_", msg, red=True, bold=True)
|
||||
self._outrep_summary(rep)
|
||||
for report in self.getreports(""):
|
||||
if report.nodeid == rep.nodeid and report.when == "teardown":
|
||||
self.print_teardown_sections(report)
|
||||
for report in teardown_sections.get(rep.nodeid, []):
|
||||
self.print_teardown_sections(report)
|
||||
|
||||
def summary_errors(self):
|
||||
if self.config.option.tbstyle != "no":
|
||||
|
@ -842,10 +854,8 @@ class TerminalReporter(object):
|
|||
msg = self._getfailureheadline(rep)
|
||||
if rep.when == "collect":
|
||||
msg = "ERROR collecting " + msg
|
||||
elif rep.when == "setup":
|
||||
msg = "ERROR at setup of " + msg
|
||||
elif rep.when == "teardown":
|
||||
msg = "ERROR at teardown of " + msg
|
||||
else:
|
||||
msg = "ERROR at %s of %s" % (rep.when, msg)
|
||||
self.write_sep("_", msg, red=True, bold=True)
|
||||
self._outrep_summary(rep)
|
||||
|
||||
|
@ -873,6 +883,150 @@ class TerminalReporter(object):
|
|||
if self.verbosity == -1:
|
||||
self.write_line(msg, **markup)
|
||||
|
||||
def short_test_summary(self):
|
||||
if not self.reportchars:
|
||||
return
|
||||
|
||||
def show_simple(stat, lines):
|
||||
failed = self.stats.get(stat, [])
|
||||
if not failed:
|
||||
return
|
||||
termwidth = self.writer.fullwidth
|
||||
config = self.config
|
||||
for rep in failed:
|
||||
line = _get_line_with_reprcrash_message(config, rep, termwidth)
|
||||
lines.append(line)
|
||||
|
||||
def show_xfailed(lines):
|
||||
xfailed = self.stats.get("xfailed", [])
|
||||
for rep in xfailed:
|
||||
verbose_word = rep._get_verbose_word(self.config)
|
||||
pos = _get_pos(self.config, rep)
|
||||
lines.append("%s %s" % (verbose_word, pos))
|
||||
reason = rep.wasxfail
|
||||
if reason:
|
||||
lines.append(" " + str(reason))
|
||||
|
||||
def show_xpassed(lines):
|
||||
xpassed = self.stats.get("xpassed", [])
|
||||
for rep in xpassed:
|
||||
verbose_word = rep._get_verbose_word(self.config)
|
||||
pos = _get_pos(self.config, rep)
|
||||
reason = rep.wasxfail
|
||||
lines.append("%s %s %s" % (verbose_word, pos, reason))
|
||||
|
||||
def show_skipped(lines):
|
||||
skipped = self.stats.get("skipped", [])
|
||||
fskips = _folded_skips(skipped) if skipped else []
|
||||
if not fskips:
|
||||
return
|
||||
verbose_word = skipped[0]._get_verbose_word(self.config)
|
||||
for num, fspath, lineno, reason in fskips:
|
||||
if reason.startswith("Skipped: "):
|
||||
reason = reason[9:]
|
||||
if lineno is not None:
|
||||
lines.append(
|
||||
"%s [%d] %s:%d: %s"
|
||||
% (verbose_word, num, fspath, lineno + 1, reason)
|
||||
)
|
||||
else:
|
||||
lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason))
|
||||
|
||||
REPORTCHAR_ACTIONS = {
|
||||
"x": show_xfailed,
|
||||
"X": show_xpassed,
|
||||
"f": partial(show_simple, "failed"),
|
||||
"F": partial(show_simple, "failed"),
|
||||
"s": show_skipped,
|
||||
"S": show_skipped,
|
||||
"p": partial(show_simple, "passed"),
|
||||
"E": partial(show_simple, "error"),
|
||||
}
|
||||
|
||||
lines = []
|
||||
for char in self.reportchars:
|
||||
action = REPORTCHAR_ACTIONS.get(char)
|
||||
if action: # skipping e.g. "P" (passed with output) here.
|
||||
action(lines)
|
||||
|
||||
if lines:
|
||||
self.write_sep("=", "short test summary info")
|
||||
for line in lines:
|
||||
self.write_line(line)
|
||||
|
||||
|
||||
def _get_pos(config, rep):
|
||||
nodeid = config.cwd_relative_nodeid(rep.nodeid)
|
||||
return nodeid
|
||||
|
||||
|
||||
def _get_line_with_reprcrash_message(config, rep, termwidth):
|
||||
"""Get summary line for a report, trying to add reprcrash message."""
|
||||
from wcwidth import wcswidth
|
||||
|
||||
verbose_word = rep._get_verbose_word(config)
|
||||
pos = _get_pos(config, rep)
|
||||
|
||||
line = "%s %s" % (verbose_word, pos)
|
||||
len_line = wcswidth(line)
|
||||
ellipsis, len_ellipsis = "...", 3
|
||||
if len_line > termwidth - len_ellipsis:
|
||||
# No space for an additional message.
|
||||
return line
|
||||
|
||||
try:
|
||||
msg = rep.longrepr.reprcrash.message
|
||||
except AttributeError:
|
||||
pass
|
||||
else:
|
||||
# Only use the first line.
|
||||
i = msg.find("\n")
|
||||
if i != -1:
|
||||
msg = msg[:i]
|
||||
len_msg = wcswidth(msg)
|
||||
|
||||
sep, len_sep = " - ", 3
|
||||
max_len_msg = termwidth - len_line - len_sep
|
||||
if max_len_msg >= len_ellipsis:
|
||||
if len_msg > max_len_msg:
|
||||
max_len_msg -= len_ellipsis
|
||||
msg = msg[:max_len_msg]
|
||||
while wcswidth(msg) > max_len_msg:
|
||||
msg = msg[:-1]
|
||||
if six.PY2:
|
||||
# on python 2 systems with narrow unicode compilation, trying to
|
||||
# get a single character out of a multi-byte unicode character such as
|
||||
# u'😄' will result in a High Surrogate (U+D83D) character, which is
|
||||
# rendered as u'<27>'; in this case we just strip that character out as it
|
||||
# serves no purpose being rendered
|
||||
while msg.endswith(u"\uD83D"):
|
||||
msg = msg[:-1]
|
||||
msg += ellipsis
|
||||
line += sep + msg
|
||||
return line
|
||||
|
||||
|
||||
def _folded_skips(skipped):
|
||||
d = {}
|
||||
for event in skipped:
|
||||
key = event.longrepr
|
||||
assert len(key) == 3, (event, key)
|
||||
keywords = getattr(event, "keywords", {})
|
||||
# folding reports with global pytestmark variable
|
||||
# this is workaround, because for now we cannot identify the scope of a skip marker
|
||||
# TODO: revisit after marks scope would be fixed
|
||||
if (
|
||||
event.when == "setup"
|
||||
and "skip" in keywords
|
||||
and "pytestmark" not in keywords
|
||||
):
|
||||
key = (key[0], None, key[2])
|
||||
d.setdefault(key, []).append(event)
|
||||
values = []
|
||||
for key, events in d.items():
|
||||
values.append((len(events),) + key)
|
||||
return values
|
||||
|
||||
|
||||
def build_summary_stats_line(stats):
|
||||
known_types = (
|
||||
|
|
|
@ -485,7 +485,7 @@ class TestGeneralUsage(object):
|
|||
["*source code not available*", "E*fixture 'invalid_fixture' not found"]
|
||||
)
|
||||
|
||||
def test_plugins_given_as_strings(self, tmpdir, monkeypatch):
|
||||
def test_plugins_given_as_strings(self, tmpdir, monkeypatch, _sys_snapshot):
|
||||
"""test that str values passed to main() as `plugins` arg
|
||||
are interpreted as module names to be imported and registered.
|
||||
#855.
|
||||
|
|
|
@ -441,7 +441,7 @@ def test_match_raises_error(testdir):
|
|||
|
||||
class TestFormattedExcinfo(object):
|
||||
@pytest.fixture
|
||||
def importasmod(self, request):
|
||||
def importasmod(self, request, _sys_snapshot):
|
||||
def importasmod(source):
|
||||
source = textwrap.dedent(source)
|
||||
tmpdir = request.getfixturevalue("tmpdir")
|
||||
|
|
|
@ -410,7 +410,7 @@ def test_deindent():
|
|||
assert lines == ["def f():", " def g():", " pass"]
|
||||
|
||||
|
||||
def test_source_of_class_at_eof_without_newline(tmpdir):
|
||||
def test_source_of_class_at_eof_without_newline(tmpdir, _sys_snapshot):
|
||||
# this test fails because the implicit inspect.getsource(A) below
|
||||
# does not return the "x = 1" last line.
|
||||
source = _pytest._code.Source(
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
def pytest_collection_modifyitems(config, items):
|
||||
"""Prefer faster tests."""
|
||||
fast_items = []
|
||||
slow_items = []
|
||||
neutral_items = []
|
||||
|
||||
slow_fixturenames = ("testdir",)
|
||||
|
||||
for item in items:
|
||||
try:
|
||||
fixtures = item.fixturenames
|
||||
except AttributeError:
|
||||
# doctest at least
|
||||
# (https://github.com/pytest-dev/pytest/issues/5070)
|
||||
neutral_items.append(item)
|
||||
else:
|
||||
if any(x for x in fixtures if x in slow_fixturenames):
|
||||
slow_items.append(item)
|
||||
else:
|
||||
marker = item.get_closest_marker("slow")
|
||||
if marker:
|
||||
slow_items.append(item)
|
||||
else:
|
||||
fast_items.append(item)
|
||||
|
||||
items[:] = fast_items + neutral_items + slow_items
|
|
@ -1071,9 +1071,7 @@ class TestFixtureUsages(object):
|
|||
)
|
||||
result = testdir.runpytest_inprocess()
|
||||
result.stdout.fnmatch_lines(
|
||||
(
|
||||
"*Fixture 'badscope' from test_invalid_scope.py got an unexpected scope value 'functions'"
|
||||
)
|
||||
"*Fixture 'badscope' from test_invalid_scope.py got an unexpected scope value 'functions'"
|
||||
)
|
||||
|
||||
def test_funcarg_parametrized_and_used_twice(self, testdir):
|
||||
|
|
|
@ -446,6 +446,50 @@ class TestAssert_reprcompare(object):
|
|||
assert "Omitting" not in lines[1]
|
||||
assert lines[2] == "{'b': 1}"
|
||||
|
||||
def test_dict_different_items(self):
|
||||
lines = callequal({"a": 0}, {"b": 1, "c": 2}, verbose=2)
|
||||
assert lines == [
|
||||
"{'a': 0} == {'b': 1, 'c': 2}",
|
||||
"Left contains 1 more item:",
|
||||
"{'a': 0}",
|
||||
"Right contains 2 more items:",
|
||||
"{'b': 1, 'c': 2}",
|
||||
"Full diff:",
|
||||
"- {'a': 0}",
|
||||
"+ {'b': 1, 'c': 2}",
|
||||
]
|
||||
lines = callequal({"b": 1, "c": 2}, {"a": 0}, verbose=2)
|
||||
assert lines == [
|
||||
"{'b': 1, 'c': 2} == {'a': 0}",
|
||||
"Left contains 2 more items:",
|
||||
"{'b': 1, 'c': 2}",
|
||||
"Right contains 1 more item:",
|
||||
"{'a': 0}",
|
||||
"Full diff:",
|
||||
"- {'b': 1, 'c': 2}",
|
||||
"+ {'a': 0}",
|
||||
]
|
||||
|
||||
def test_sequence_different_items(self):
|
||||
lines = callequal((1, 2), (3, 4, 5), verbose=2)
|
||||
assert lines == [
|
||||
"(1, 2) == (3, 4, 5)",
|
||||
"At index 0 diff: 1 != 3",
|
||||
"Right contains one more item: 5",
|
||||
"Full diff:",
|
||||
"- (1, 2)",
|
||||
"+ (3, 4, 5)",
|
||||
]
|
||||
lines = callequal((1, 2, 3), (4,), verbose=2)
|
||||
assert lines == [
|
||||
"(1, 2, 3) == (4,)",
|
||||
"At index 0 diff: 1 != 4",
|
||||
"Left contains 2 more items, first extra item: 2",
|
||||
"Full diff:",
|
||||
"- (1, 2, 3)",
|
||||
"+ (4,)",
|
||||
]
|
||||
|
||||
def test_set(self):
|
||||
expl = callequal({0, 1}, {0, 2})
|
||||
assert len(expl) > 1
|
||||
|
|
|
@ -196,6 +196,7 @@ def test_cache_show(testdir):
|
|||
"""
|
||||
def pytest_configure(config):
|
||||
config.cache.set("my/name", [1,2,3])
|
||||
config.cache.set("my/hello", "world")
|
||||
config.cache.set("other/some", {1:2})
|
||||
dp = config.cache.makedir("mydb")
|
||||
dp.ensure("hello")
|
||||
|
@ -204,20 +205,39 @@ def test_cache_show(testdir):
|
|||
)
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == 5 # no tests executed
|
||||
|
||||
result = testdir.runpytest("--cache-show")
|
||||
result.stdout.fnmatch_lines_random(
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*cachedir:*",
|
||||
"-*cache values*-",
|
||||
"*my/name contains:",
|
||||
"*- cache values for '[*]' -*",
|
||||
"cache/nodeids contains:",
|
||||
"my/name contains:",
|
||||
" [1, 2, 3]",
|
||||
"*other/some contains*",
|
||||
" {*1*: 2}",
|
||||
"-*cache directories*-",
|
||||
"other/some contains:",
|
||||
" {*'1': 2}",
|
||||
"*- cache directories for '[*]' -*",
|
||||
"*mydb/hello*length 0*",
|
||||
"*mydb/world*length 0*",
|
||||
]
|
||||
)
|
||||
assert result.ret == 0
|
||||
|
||||
result = testdir.runpytest("--cache-show", "*/hello")
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*cachedir:*",
|
||||
"*- cache values for '[*]/hello' -*",
|
||||
"my/hello contains:",
|
||||
" *'world'",
|
||||
"*- cache directories for '[*]/hello' -*",
|
||||
"d/mydb/hello*length 0*",
|
||||
]
|
||||
)
|
||||
stdout = result.stdout.str()
|
||||
assert "other/some" not in stdout
|
||||
assert "d/mydb/world" not in stdout
|
||||
assert result.ret == 0
|
||||
|
||||
|
||||
class TestLastFailed(object):
|
||||
|
|
|
@ -819,15 +819,15 @@ def test_error_during_readouterr(testdir):
|
|||
testdir.makepyfile(
|
||||
pytest_xyz="""
|
||||
from _pytest.capture import FDCapture
|
||||
|
||||
def bad_snap(self):
|
||||
raise Exception('boom')
|
||||
|
||||
assert FDCapture.snap
|
||||
FDCapture.snap = bad_snap
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest_subprocess(
|
||||
"-p", "pytest_xyz", "--version", syspathinsert=True
|
||||
)
|
||||
result = testdir.runpytest_subprocess("-p", "pytest_xyz", "--version")
|
||||
result.stderr.fnmatch_lines(
|
||||
["*in bad_snap", " raise Exception('boom')", "Exception: boom"]
|
||||
)
|
||||
|
|
|
@ -436,7 +436,7 @@ class TestConfigAPI(object):
|
|||
|
||||
|
||||
class TestConfigFromdictargs(object):
|
||||
def test_basic_behavior(self):
|
||||
def test_basic_behavior(self, _sys_snapshot):
|
||||
from _pytest.config import Config
|
||||
|
||||
option_dict = {"verbose": 444, "foo": "bar", "capture": "no"}
|
||||
|
@ -450,7 +450,7 @@ class TestConfigFromdictargs(object):
|
|||
assert config.option.capture == "no"
|
||||
assert config.args == args
|
||||
|
||||
def test_origargs(self):
|
||||
def test_origargs(self, _sys_snapshot):
|
||||
"""Show that fromdictargs can handle args in their "orig" format"""
|
||||
from _pytest.config import Config
|
||||
|
||||
|
@ -1057,7 +1057,7 @@ class TestOverrideIniArgs(object):
|
|||
assert rootdir == tmpdir
|
||||
assert inifile is None
|
||||
|
||||
def test_addopts_before_initini(self, monkeypatch, _config_for_test):
|
||||
def test_addopts_before_initini(self, monkeypatch, _config_for_test, _sys_snapshot):
|
||||
cache_dir = ".custom_cache"
|
||||
monkeypatch.setenv("PYTEST_ADDOPTS", "-o cache_dir=%s" % cache_dir)
|
||||
config = _config_for_test
|
||||
|
@ -1092,7 +1092,7 @@ class TestOverrideIniArgs(object):
|
|||
)
|
||||
assert result.ret == _pytest.main.EXIT_USAGEERROR
|
||||
|
||||
def test_override_ini_does_not_contain_paths(self, _config_for_test):
|
||||
def test_override_ini_does_not_contain_paths(self, _config_for_test, _sys_snapshot):
|
||||
"""Check that -o no longer swallows all options after it (#3103)"""
|
||||
config = _config_for_test
|
||||
config._preparse(["-o", "cache_dir=/cache", "/some/test/path"])
|
||||
|
|
|
@ -13,17 +13,6 @@ from _pytest.main import EXIT_OK
|
|||
from _pytest.main import EXIT_USAGEERROR
|
||||
|
||||
|
||||
@pytest.fixture(scope="module", params=["global", "inpackage"])
|
||||
def basedir(request, tmpdir_factory):
|
||||
tmpdir = tmpdir_factory.mktemp("basedir", numbered=True)
|
||||
tmpdir.ensure("adir/conftest.py").write("a=1 ; Directory = 3")
|
||||
tmpdir.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5")
|
||||
if request.param == "inpackage":
|
||||
tmpdir.ensure("adir/__init__.py")
|
||||
tmpdir.ensure("adir/b/__init__.py")
|
||||
return tmpdir
|
||||
|
||||
|
||||
def ConftestWithSetinitial(path):
|
||||
conftest = PytestPluginManager()
|
||||
conftest_setinitial(conftest, [path])
|
||||
|
@ -41,7 +30,19 @@ def conftest_setinitial(conftest, args, confcutdir=None):
|
|||
conftest._set_initial_conftests(Namespace())
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("_sys_snapshot")
|
||||
class TestConftestValueAccessGlobal(object):
|
||||
@pytest.fixture(scope="module", params=["global", "inpackage"])
|
||||
def basedir(self, request, tmpdir_factory):
|
||||
tmpdir = tmpdir_factory.mktemp("basedir", numbered=True)
|
||||
tmpdir.ensure("adir/conftest.py").write("a=1 ; Directory = 3")
|
||||
tmpdir.ensure("adir/b/conftest.py").write("b=2 ; a = 1.5")
|
||||
if request.param == "inpackage":
|
||||
tmpdir.ensure("adir/__init__.py")
|
||||
tmpdir.ensure("adir/b/__init__.py")
|
||||
|
||||
yield tmpdir
|
||||
|
||||
def test_basic_init(self, basedir):
|
||||
conftest = PytestPluginManager()
|
||||
p = basedir.join("adir")
|
||||
|
@ -49,10 +50,10 @@ class TestConftestValueAccessGlobal(object):
|
|||
|
||||
def test_immediate_initialiation_and_incremental_are_the_same(self, basedir):
|
||||
conftest = PytestPluginManager()
|
||||
len(conftest._dirpath2confmods)
|
||||
assert not len(conftest._dirpath2confmods)
|
||||
conftest._getconftestmodules(basedir)
|
||||
snap1 = len(conftest._dirpath2confmods)
|
||||
# assert len(conftest._dirpath2confmods) == snap1 + 1
|
||||
assert snap1 == 1
|
||||
conftest._getconftestmodules(basedir.join("adir"))
|
||||
assert len(conftest._dirpath2confmods) == snap1 + 1
|
||||
conftest._getconftestmodules(basedir.join("b"))
|
||||
|
@ -80,7 +81,7 @@ class TestConftestValueAccessGlobal(object):
|
|||
assert path.purebasename.startswith("conftest")
|
||||
|
||||
|
||||
def test_conftest_in_nonpkg_with_init(tmpdir):
|
||||
def test_conftest_in_nonpkg_with_init(tmpdir, _sys_snapshot):
|
||||
tmpdir.ensure("adir-1.0/conftest.py").write("a=1 ; Directory = 3")
|
||||
tmpdir.ensure("adir-1.0/b/conftest.py").write("b=2 ; a = 1.5")
|
||||
tmpdir.ensure("adir-1.0/b/__init__.py")
|
||||
|
|
|
@ -485,9 +485,27 @@ class TestPython(object):
|
|||
tnode = node.find_first_by_tag("testcase")
|
||||
tnode.assert_attr(classname="test_xfailure_function", name="test_xfail")
|
||||
fnode = tnode.find_first_by_tag("skipped")
|
||||
fnode.assert_attr(message="expected test failure")
|
||||
fnode.assert_attr(type="pytest.xfail", message="42")
|
||||
# assert "ValueError" in fnode.toxml()
|
||||
|
||||
def test_xfailure_marker(self, testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
@pytest.mark.xfail(reason="42")
|
||||
def test_xfail():
|
||||
assert False
|
||||
"""
|
||||
)
|
||||
result, dom = runandparse(testdir)
|
||||
assert not result.ret
|
||||
node = dom.find_first_by_tag("testsuite")
|
||||
node.assert_attr(skipped=1, tests=1)
|
||||
tnode = node.find_first_by_tag("testcase")
|
||||
tnode.assert_attr(classname="test_xfailure_marker", name="test_xfail")
|
||||
fnode = tnode.find_first_by_tag("skipped")
|
||||
fnode.assert_attr(type="pytest.xfail", message="42")
|
||||
|
||||
def test_xfail_captures_output_once(self, testdir):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
|
|
|
@ -13,6 +13,7 @@ from _pytest.mark import EMPTY_PARAMETERSET_OPTION
|
|||
from _pytest.mark import MarkGenerator as Mark
|
||||
from _pytest.nodes import Collector
|
||||
from _pytest.nodes import Node
|
||||
from _pytest.warning_types import PytestDeprecationWarning
|
||||
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
|
||||
|
||||
try:
|
||||
|
@ -204,7 +205,7 @@ def test_strict_prohibits_unregistered_markers(testdir):
|
|||
)
|
||||
result = testdir.runpytest("--strict")
|
||||
assert result.ret != 0
|
||||
result.stdout.fnmatch_lines(["'unregisteredmark' not a registered marker"])
|
||||
result.stdout.fnmatch_lines(["'unregisteredmark' is not a registered marker"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
@ -991,3 +992,15 @@ def test_pytest_param_id_requires_string():
|
|||
@pytest.mark.parametrize("s", (None, "hello world"))
|
||||
def test_pytest_param_id_allows_none_or_string(s):
|
||||
assert pytest.param(id=s)
|
||||
|
||||
|
||||
def test_pytest_param_warning_on_unknown_kwargs():
|
||||
with pytest.warns(PytestDeprecationWarning) as warninfo:
|
||||
# typo, should be marks=
|
||||
pytest.param(1, 2, mark=pytest.mark.xfail())
|
||||
assert warninfo[0].filename == __file__
|
||||
msg, = warninfo[0].message.args
|
||||
assert msg == (
|
||||
"pytest.param() got unexpected keyword arguments: ['mark'].\n"
|
||||
"This will be an error in future versions."
|
||||
)
|
||||
|
|
|
@ -6,6 +6,8 @@ import py
|
|||
import _pytest
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.slow
|
||||
|
||||
MODSET = [
|
||||
x
|
||||
for x in py.path.local(_pytest.__file__).dirpath().visit("*.py")
|
||||
|
|
|
@ -462,3 +462,10 @@ def test_syspath_prepend_with_namespace_packages(testdir, monkeypatch):
|
|||
import ns_pkg.world
|
||||
|
||||
assert ns_pkg.world.check() == "world"
|
||||
|
||||
# Should invalidate caches via importlib.invalidate_caches.
|
||||
tmpdir = testdir.tmpdir
|
||||
modules_tmpdir = tmpdir.mkdir("modules_tmpdir")
|
||||
monkeypatch.syspath_prepend(str(modules_tmpdir))
|
||||
modules_tmpdir.join("main_app.py").write("app = True")
|
||||
from main_app import app # noqa: F401
|
||||
|
|
|
@ -2,7 +2,6 @@ from __future__ import absolute_import
|
|||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
|
@ -804,13 +803,12 @@ class TestPDB(object):
|
|||
)
|
||||
|
||||
def test_pdb_validate_usepdb_cls(self, testdir):
|
||||
assert _validate_usepdb_cls("os.path:dirname.__name__") == "dirname"
|
||||
assert _validate_usepdb_cls("os.path:dirname.__name__") == (
|
||||
"os.path",
|
||||
"dirname.__name__",
|
||||
)
|
||||
|
||||
with pytest.raises(
|
||||
argparse.ArgumentTypeError,
|
||||
match=r"^could not get pdb class for 'pdb:DoesNotExist': .*'DoesNotExist'",
|
||||
):
|
||||
_validate_usepdb_cls("pdb:DoesNotExist")
|
||||
assert _validate_usepdb_cls("pdb:DoesNotExist") == ("pdb", "DoesNotExist")
|
||||
|
||||
def test_pdb_custom_cls_without_pdb(self, testdir, custom_pdb_calls):
|
||||
p1 = testdir.makepyfile("""xxx """)
|
||||
|
@ -1136,3 +1134,46 @@ def test_pdb_skip_option(testdir):
|
|||
result = testdir.runpytest_inprocess("--pdb-ignore-set_trace", "-s", p)
|
||||
assert result.ret == EXIT_NOTESTSCOLLECTED
|
||||
result.stdout.fnmatch_lines(["*before_set_trace*", "*after_set_trace*"])
|
||||
|
||||
|
||||
def test_pdbcls_via_local_module(testdir):
|
||||
"""It should be imported in pytest_configure or later only."""
|
||||
p1 = testdir.makepyfile(
|
||||
"""
|
||||
def test():
|
||||
print("before_settrace")
|
||||
__import__("pdb").set_trace()
|
||||
""",
|
||||
mypdb="""
|
||||
class Wrapped:
|
||||
class MyPdb:
|
||||
def set_trace(self, *args):
|
||||
print("settrace_called", args)
|
||||
|
||||
def runcall(self, *args, **kwds):
|
||||
print("runcall_called", args, kwds)
|
||||
assert "func" in kwds
|
||||
""",
|
||||
)
|
||||
result = testdir.runpytest(
|
||||
str(p1), "--pdbcls=really.invalid:Value", syspathinsert=True
|
||||
)
|
||||
result.stderr.fnmatch_lines(
|
||||
[
|
||||
"ERROR: --pdbcls: could not import 'really.invalid:Value': No module named *really*"
|
||||
]
|
||||
)
|
||||
assert result.ret == 4
|
||||
|
||||
result = testdir.runpytest(
|
||||
str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", syspathinsert=True
|
||||
)
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(["*settrace_called*", "* 1 passed in *"])
|
||||
|
||||
# Ensure that it also works with --trace.
|
||||
result = testdir.runpytest(
|
||||
str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", "--trace", syspathinsert=True
|
||||
)
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(["*runcall_called*", "* 1 passed in *"])
|
||||
|
|
|
@ -4,6 +4,7 @@ from __future__ import division
|
|||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
@ -482,3 +483,79 @@ def test_pytester_addopts(request, monkeypatch):
|
|||
testdir.finalize()
|
||||
|
||||
assert os.environ["PYTEST_ADDOPTS"] == "--orig-unused"
|
||||
|
||||
|
||||
def test_run_stdin(testdir):
|
||||
with pytest.raises(testdir.TimeoutExpired):
|
||||
testdir.run(
|
||||
sys.executable,
|
||||
"-c",
|
||||
"import sys, time; time.sleep(1); print(sys.stdin.read())",
|
||||
stdin=subprocess.PIPE,
|
||||
timeout=0.1,
|
||||
)
|
||||
|
||||
with pytest.raises(testdir.TimeoutExpired):
|
||||
result = testdir.run(
|
||||
sys.executable,
|
||||
"-c",
|
||||
"import sys, time; time.sleep(1); print(sys.stdin.read())",
|
||||
stdin=b"input\n2ndline",
|
||||
timeout=0.1,
|
||||
)
|
||||
|
||||
result = testdir.run(
|
||||
sys.executable,
|
||||
"-c",
|
||||
"import sys; print(sys.stdin.read())",
|
||||
stdin=b"input\n2ndline",
|
||||
)
|
||||
assert result.stdout.lines == ["input", "2ndline"]
|
||||
assert result.stderr.str() == ""
|
||||
assert result.ret == 0
|
||||
|
||||
|
||||
def test_popen_stdin_pipe(testdir):
|
||||
proc = testdir.popen(
|
||||
[sys.executable, "-c", "import sys; print(sys.stdin.read())"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE,
|
||||
)
|
||||
stdin = b"input\n2ndline"
|
||||
stdout, stderr = proc.communicate(input=stdin)
|
||||
assert stdout.decode("utf8").splitlines() == ["input", "2ndline"]
|
||||
assert stderr == b""
|
||||
assert proc.returncode == 0
|
||||
|
||||
|
||||
def test_popen_stdin_bytes(testdir):
|
||||
proc = testdir.popen(
|
||||
[sys.executable, "-c", "import sys; print(sys.stdin.read())"],
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
stdin=b"input\n2ndline",
|
||||
)
|
||||
stdout, stderr = proc.communicate()
|
||||
assert stdout.decode("utf8").splitlines() == ["input", "2ndline"]
|
||||
assert stderr == b""
|
||||
assert proc.returncode == 0
|
||||
|
||||
|
||||
def test_popen_default_stdin_stderr_and_stdin_None(testdir):
|
||||
# stdout, stderr default to pipes,
|
||||
# stdin can be None to not close the pipe, avoiding
|
||||
# "ValueError: flush of closed file" with `communicate()`.
|
||||
p1 = testdir.makepyfile(
|
||||
"""
|
||||
import sys
|
||||
print(sys.stdin.read()) # empty
|
||||
print('stdout')
|
||||
sys.stderr.write('stderr')
|
||||
"""
|
||||
)
|
||||
proc = testdir.popen([sys.executable, str(p1)], stdin=None)
|
||||
stdout, stderr = proc.communicate(b"ignored")
|
||||
assert stdout.splitlines() == [b"", b"stdout"]
|
||||
assert stderr.splitlines() == [b"stderr"]
|
||||
assert proc.returncode == 0
|
||||
|
|
|
@ -581,7 +581,14 @@ def test_pytest_exit_returncode(testdir):
|
|||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(["*! *Exit: some exit msg !*"])
|
||||
assert result.stderr.lines == [""]
|
||||
# Assert no output on stderr, except for unreliable ResourceWarnings.
|
||||
# (https://github.com/pytest-dev/pytest/issues/5088)
|
||||
assert [
|
||||
x
|
||||
for x in result.stderr.lines
|
||||
if not x.startswith("Exception ignored in:")
|
||||
and not x.startswith("ResourceWarning")
|
||||
] == [""]
|
||||
assert result.ret == 99
|
||||
|
||||
# It prints to stderr also in case of exit during pytest_sessionstart.
|
||||
|
|
|
@ -7,7 +7,6 @@ import sys
|
|||
|
||||
import pytest
|
||||
from _pytest.runner import runtestprotocol
|
||||
from _pytest.skipping import folded_skips
|
||||
from _pytest.skipping import MarkEvaluator
|
||||
from _pytest.skipping import pytest_runtest_setup
|
||||
|
||||
|
@ -750,40 +749,6 @@ def test_skipif_class(testdir):
|
|||
result.stdout.fnmatch_lines(["*2 skipped*"])
|
||||
|
||||
|
||||
def test_skip_reasons_folding():
|
||||
path = "xyz"
|
||||
lineno = 3
|
||||
message = "justso"
|
||||
longrepr = (path, lineno, message)
|
||||
|
||||
class X(object):
|
||||
pass
|
||||
|
||||
ev1 = X()
|
||||
ev1.when = "execute"
|
||||
ev1.skipped = True
|
||||
ev1.longrepr = longrepr
|
||||
|
||||
ev2 = X()
|
||||
ev2.when = "execute"
|
||||
ev2.longrepr = longrepr
|
||||
ev2.skipped = True
|
||||
|
||||
# ev3 might be a collection report
|
||||
ev3 = X()
|
||||
ev3.when = "collect"
|
||||
ev3.longrepr = longrepr
|
||||
ev3.skipped = True
|
||||
|
||||
values = folded_skips([ev1, ev2, ev3])
|
||||
assert len(values) == 1
|
||||
num, fspath, lineno, reason = values[0]
|
||||
assert num == 3
|
||||
assert fspath == path
|
||||
assert lineno == lineno
|
||||
assert reason == message
|
||||
|
||||
|
||||
def test_skipped_reasons_functional(testdir):
|
||||
testdir.makepyfile(
|
||||
test_one="""
|
||||
|
|
|
@ -16,6 +16,7 @@ import py
|
|||
import pytest
|
||||
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
||||
from _pytest.reports import BaseReport
|
||||
from _pytest.terminal import _folded_skips
|
||||
from _pytest.terminal import _plugin_nameversions
|
||||
from _pytest.terminal import build_summary_stats_line
|
||||
from _pytest.terminal import getreportopt
|
||||
|
@ -774,11 +775,19 @@ def test_pass_output_reporting(testdir):
|
|||
assert "test_pass_has_output" not in s
|
||||
assert "Four score and seven years ago..." not in s
|
||||
assert "test_pass_no_output" not in s
|
||||
result = testdir.runpytest("-rP")
|
||||
result = testdir.runpytest("-rPp")
|
||||
result.stdout.fnmatch_lines(
|
||||
["*test_pass_has_output*", "Four score and seven years ago..."]
|
||||
[
|
||||
"*= PASSES =*",
|
||||
"*_ test_pass_has_output _*",
|
||||
"*- Captured stdout call -*",
|
||||
"Four score and seven years ago...",
|
||||
"*= short test summary info =*",
|
||||
"PASSED test_pass_output_reporting.py::test_pass_has_output",
|
||||
"PASSED test_pass_output_reporting.py::test_pass_no_output",
|
||||
"*= 2 passed in *",
|
||||
]
|
||||
)
|
||||
assert "test_pass_no_output" not in result.stdout.str()
|
||||
|
||||
|
||||
def test_color_yes(testdir):
|
||||
|
@ -836,14 +845,23 @@ def test_getreportopt():
|
|||
config.option.reportchars = "sfxw"
|
||||
assert getreportopt(config) == "sfx"
|
||||
|
||||
config.option.reportchars = "sfx"
|
||||
# Now with --disable-warnings.
|
||||
config.option.disable_warnings = False
|
||||
config.option.reportchars = "a"
|
||||
assert getreportopt(config) == "sxXwEf" # NOTE: "w" included!
|
||||
|
||||
config.option.reportchars = "sfx"
|
||||
assert getreportopt(config) == "sfxw"
|
||||
|
||||
config.option.reportchars = "sfxw"
|
||||
config.option.disable_warnings = False
|
||||
assert getreportopt(config) == "sfxw"
|
||||
|
||||
config.option.reportchars = "a"
|
||||
assert getreportopt(config) == "sxXwEf" # NOTE: "w" included!
|
||||
|
||||
config.option.reportchars = "A"
|
||||
assert getreportopt(config) == "sxXwEfpP"
|
||||
|
||||
|
||||
def test_terminalreporter_reportopt_addopts(testdir):
|
||||
testdir.makeini("[pytest]\naddopts=-rs")
|
||||
|
@ -1530,3 +1548,37 @@ class TestProgressWithTeardown(object):
|
|||
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
||||
output = testdir.runpytest("-n2")
|
||||
output.stdout.re_match_lines([r"[\.E]{40} \s+ \[100%\]"])
|
||||
|
||||
|
||||
def test_skip_reasons_folding():
|
||||
path = "xyz"
|
||||
lineno = 3
|
||||
message = "justso"
|
||||
longrepr = (path, lineno, message)
|
||||
|
||||
class X(object):
|
||||
pass
|
||||
|
||||
ev1 = X()
|
||||
ev1.when = "execute"
|
||||
ev1.skipped = True
|
||||
ev1.longrepr = longrepr
|
||||
|
||||
ev2 = X()
|
||||
ev2.when = "execute"
|
||||
ev2.longrepr = longrepr
|
||||
ev2.skipped = True
|
||||
|
||||
# ev3 might be a collection report
|
||||
ev3 = X()
|
||||
ev3.when = "collect"
|
||||
ev3.longrepr = longrepr
|
||||
ev3.skipped = True
|
||||
|
||||
values = _folded_skips([ev1, ev2, ev3])
|
||||
assert len(values) == 1
|
||||
num, fspath, lineno, reason = values[0]
|
||||
assert num == 3
|
||||
assert fspath == path
|
||||
assert lineno == lineno
|
||||
assert reason == message
|
||||
|
|
Loading…
Reference in New Issue