Merge remote-tracking branch 'origin/features' into short-summary-message

Conflicts:
	src/_pytest/skipping.py
This commit is contained in:
Daniel Hahler 2019-04-17 15:30:19 +02:00
commit df1d1105b0
74 changed files with 1243 additions and 655 deletions

View File

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

View File

@ -208,6 +208,7 @@ Ross Lawley
Russel Winder
Ryan Wooden
Samuel Dion-Girardeau
Samuel Searles-Bryant
Samuele Pedroni
Sankt Petersbug
Segev Finer

View File

@ -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)
=========================

View File

@ -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))

View File

@ -0,0 +1 @@
Show XFail reason as part of JUnitXML message field.

View File

@ -0,0 +1 @@
Assertion failure messages for sequences and dicts contain the number of different items now.

View File

@ -1 +0,0 @@
Environment variables are properly restored when using pytester's ``testdir`` fixture.

View File

@ -0,0 +1 @@
The ``--cache-show`` option/action accepts an optional glob to show only matching cache entries.

View File

@ -0,0 +1 @@
Standard input (stdin) can be given to pytester's ``Testdir.run()`` and ``Testdir.popen()``.

View File

@ -0,0 +1 @@
pytester's ``Testdir.popen()`` uses ``stdout`` and ``stderr`` via keyword arguments with defaults now (``subprocess.PIPE``).

View File

@ -0,0 +1 @@
The ``-r`` option learnt about ``A`` to display all reports (including passed ones) in the short test summary.

View File

@ -0,0 +1 @@
The code for the short test summary in the terminal was moved to the terminal plugin.

View File

@ -0,0 +1 @@
Improved validation of kwargs for various methods in the pytester plugin.

View File

@ -0,0 +1 @@
The short test summary is displayed after passes with output (``-rP``).

View File

@ -6,6 +6,7 @@ Release announcements
:maxdepth: 2
release-4.4.1
release-4.4.0
release-4.3.1
release-4.3.0

View File

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

View File

@ -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:

View File

@ -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:

View File

@ -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%]

View File

@ -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%]

View File

@ -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 ==================

View File

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

View File

@ -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%]

View File

@ -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 =======================

View File

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

View File

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

View File

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

View File

@ -52,7 +52,7 @@ Thats 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%]

View File

@ -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.

View File

@ -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%]

View File

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

View File

@ -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%]

View File

@ -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%]

View File

@ -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%]

View File

@ -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:

View File

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

View File

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

View File

@ -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()
)

View File

@ -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))

View File

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

View File

@ -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)

View File

@ -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.",
)

View File

@ -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:

View File

@ -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:")

View File

@ -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)

View File

@ -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: "):

View File

@ -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 "

View File

@ -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.

View File

@ -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)

View File

@ -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):

View File

@ -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):

View File

@ -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,

View File

@ -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"),
}

View File

@ -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 = (

View File

@ -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.

View File

@ -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")

View File

@ -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(

26
testing/conftest.py Normal file
View File

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

View File

@ -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):

View File

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

View File

@ -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):

View File

@ -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"]
)

View File

@ -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"])

View File

@ -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")

View File

@ -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(
"""

View File

@ -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."
)

View File

@ -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")

View File

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

View File

@ -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 *"])

View File

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

View File

@ -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.

View File

@ -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="""

View File

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

View File

@ -171,6 +171,7 @@ filterwarnings =
pytester_example_dir = testing/example_scripts
markers =
issue
slow
[flake8]
max-line-length = 120