Merge master into features
Conflicts: src/_pytest/reports.py (via7259c453d
, moved the type annotation; setting it to `None` was removed in3c82b1cb9
already)
This commit is contained in:
commit
450409d123
|
@ -43,8 +43,7 @@ jobs:
|
|||
python: 'pypy3'
|
||||
|
||||
- env: TOXENV=py35-xdist
|
||||
dist: trusty
|
||||
python: '3.5.0'
|
||||
python: '3.5'
|
||||
|
||||
# Coverage for:
|
||||
# - pytester's LsofFdLeakChecker
|
||||
|
|
4
AUTHORS
4
AUTHORS
|
@ -23,6 +23,7 @@ Andras Tim
|
|||
Andrea Cimatoribus
|
||||
Andreas Zeidler
|
||||
Andrey Paramonov
|
||||
Andrzej Klajnert
|
||||
Andrzej Ostrowski
|
||||
Andy Freeland
|
||||
Anthon van der Neut
|
||||
|
@ -55,6 +56,7 @@ Charnjit SiNGH (CCSJ)
|
|||
Chris Lamb
|
||||
Christian Boelsen
|
||||
Christian Fetzer
|
||||
Christian Neumüller
|
||||
Christian Theunert
|
||||
Christian Tismer
|
||||
Christopher Gilling
|
||||
|
@ -96,6 +98,7 @@ Feng Ma
|
|||
Florian Bruhin
|
||||
Floris Bruynooghe
|
||||
Gabriel Reis
|
||||
Gene Wood
|
||||
George Kussumoto
|
||||
Georgy Dyuldin
|
||||
Graham Horler
|
||||
|
@ -210,6 +213,7 @@ Raphael Castaneda
|
|||
Raphael Pierzina
|
||||
Raquel Alegre
|
||||
Ravi Chandra
|
||||
Robert Holt
|
||||
Roberto Polli
|
||||
Roland Puntaier
|
||||
Romain Dorgueil
|
||||
|
|
|
@ -18,6 +18,32 @@ with advance notice in the **Deprecations** section of releases.
|
|||
|
||||
.. towncrier release notes start
|
||||
|
||||
pytest 5.1.2 (2019-08-30)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- `#2270 <https://github.com/pytest-dev/pytest/issues/2270>`_: Fixed ``self`` reference in function-scoped fixtures defined plugin classes: previously ``self``
|
||||
would be a reference to a *test* class, not the *plugin* class.
|
||||
|
||||
|
||||
- `#570 <https://github.com/pytest-dev/pytest/issues/570>`_: Fixed long standing issue where fixture scope was not respected when indirect fixtures were used during
|
||||
parametrization.
|
||||
|
||||
|
||||
- `#5782 <https://github.com/pytest-dev/pytest/issues/5782>`_: Fix decoding error when printing an error response from ``--pastebin``.
|
||||
|
||||
|
||||
- `#5786 <https://github.com/pytest-dev/pytest/issues/5786>`_: Chained exceptions in test and collection reports are now correctly serialized, allowing plugins like
|
||||
``pytest-xdist`` to display them properly.
|
||||
|
||||
|
||||
- `#5792 <https://github.com/pytest-dev/pytest/issues/5792>`_: Windows: Fix error that occurs in certain circumstances when loading
|
||||
``conftest.py`` from a working directory that has casing other than the one stored
|
||||
in the filesystem (e.g., ``c:\test`` instead of ``C:\test``).
|
||||
|
||||
|
||||
pytest 5.1.1 (2019-08-20)
|
||||
=========================
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Fix decoding error when printing an error response from ``--pastebin``.
|
|
@ -0,0 +1 @@
|
|||
Fix pypy3.6 (nightly) on windows.
|
|
@ -0,0 +1 @@
|
|||
Handle ``--fulltrace`` correctly with ``pytest.raises``.
|
|
@ -0,0 +1,2 @@
|
|||
Windows: Fix regression with conftest whose qualified name contains uppercase
|
||||
characters (introduced by #5792).
|
|
@ -16,7 +16,7 @@ REGENDOC_ARGS := \
|
|||
--normalize "/[ \t]+\n/\n/" \
|
||||
--normalize "~\$$REGENDOC_TMPDIR~/home/sweet/project~" \
|
||||
--normalize "~/path/to/example~/home/sweet/project~" \
|
||||
--normalize "/in \d+.\d+ seconds/in 0.12 seconds/" \
|
||||
--normalize "/in \d+.\d+s ==/in 0.12s ==/" \
|
||||
--normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \
|
||||
--normalize "@pytest-(\d+)\\.[^ ,]+@pytest-\1.x.y@" \
|
||||
--normalize "@(This is pytest version )(\d+)\\.[^ ,]+@\1\2.x.y@" \
|
||||
|
|
|
@ -6,6 +6,7 @@ Release announcements
|
|||
:maxdepth: 2
|
||||
|
||||
|
||||
release-5.1.2
|
||||
release-5.1.1
|
||||
release-5.1.0
|
||||
release-5.0.1
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
pytest-5.1.2
|
||||
=======================================
|
||||
|
||||
pytest 5.1.2 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:
|
||||
|
||||
* Andrzej Klajnert
|
||||
* Anthony Sottile
|
||||
* Bruno Oliveira
|
||||
* Christian Neumüller
|
||||
* Robert Holt
|
||||
* linchiwei123
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
|
@ -47,7 +47,7 @@ you will see the return value of the function call:
|
|||
E + where 3 = f()
|
||||
|
||||
test_assert1.py:6: AssertionError
|
||||
============================ 1 failed in 0.02s =============================
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
``pytest`` has support for showing the values of the most common subexpressions
|
||||
including calls, attributes, comparisons, and binary and unary
|
||||
|
@ -208,7 +208,7 @@ if you run this module:
|
|||
E Use -v to get the full diff
|
||||
|
||||
test_assert2.py:6: AssertionError
|
||||
============================ 1 failed in 0.02s =============================
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
Special comparisons are done for a number of cases:
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ If you run this for the first time you will see two failures:
|
|||
E Failed: bad luck
|
||||
|
||||
test_50.py:7: Failed
|
||||
2 failed, 48 passed in 0.08s
|
||||
2 failed, 48 passed in 0.07s
|
||||
|
||||
If you then run it with ``--lf``:
|
||||
|
||||
|
@ -114,7 +114,7 @@ If you then run it with ``--lf``:
|
|||
E Failed: bad luck
|
||||
|
||||
test_50.py:7: Failed
|
||||
===================== 2 failed, 48 deselected in 0.02s =====================
|
||||
===================== 2 failed, 48 deselected in 0.12s =====================
|
||||
|
||||
You have run only the two failing tests from the last run, while the 48 passing
|
||||
tests have not been run ("deselected").
|
||||
|
@ -158,7 +158,7 @@ of ``FF`` and dots):
|
|||
E Failed: bad luck
|
||||
|
||||
test_50.py:7: Failed
|
||||
======================= 2 failed, 48 passed in 0.07s =======================
|
||||
======================= 2 failed, 48 passed in 0.12s =======================
|
||||
|
||||
.. _`config.cache`:
|
||||
|
||||
|
@ -283,7 +283,7 @@ You can always peek at the content of the cache using the
|
|||
example/value contains:
|
||||
42
|
||||
|
||||
========================== no tests ran in 0.00s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
``--cache-show`` takes an optional argument to specify a glob pattern for
|
||||
filtering:
|
||||
|
@ -300,7 +300,7 @@ filtering:
|
|||
example/value contains:
|
||||
42
|
||||
|
||||
========================== no tests ran in 0.00s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
Clearing Cache content
|
||||
----------------------
|
||||
|
|
|
@ -91,7 +91,7 @@ of the failing function and hide the other one:
|
|||
test_module.py:12: AssertionError
|
||||
-------------------------- Captured stdout setup ---------------------------
|
||||
setting up <function test_func2 at 0xdeadbeef>
|
||||
======================= 1 failed, 1 passed in 0.02s ========================
|
||||
======================= 1 failed, 1 passed in 0.12s ========================
|
||||
|
||||
Accessing captured output from a test function
|
||||
---------------------------------------------------
|
||||
|
|
|
@ -107,8 +107,8 @@ check for ini-files as follows:
|
|||
|
||||
# first look for pytest.ini files
|
||||
path/pytest.ini
|
||||
path/setup.cfg # must also contain [tool:pytest] section to match
|
||||
path/tox.ini # must also contain [pytest] section to match
|
||||
path/setup.cfg # must also contain [tool:pytest] section to match
|
||||
pytest.ini
|
||||
... # all the way down to the root
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ then you can just invoke ``pytest`` directly:
|
|||
|
||||
test_example.txt . [100%]
|
||||
|
||||
============================ 1 passed in 0.01s =============================
|
||||
============================ 1 passed in 0.12s =============================
|
||||
|
||||
By default, pytest will collect ``test*.txt`` files looking for doctest directives, but you
|
||||
can pass additional globs using the ``--doctest-glob`` option (multi-allowed).
|
||||
|
@ -66,7 +66,7 @@ and functions, including from test modules:
|
|||
mymodule.py . [ 50%]
|
||||
test_example.txt . [100%]
|
||||
|
||||
============================ 2 passed in 0.01s =============================
|
||||
============================ 2 passed in 0.12s =============================
|
||||
|
||||
You can make these changes permanent in your project by
|
||||
putting them into a pytest.ini file like this:
|
||||
|
|
|
@ -52,7 +52,7 @@ You can then restrict a test run to only run tests marked with ``webtest``:
|
|||
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
|
||||
===================== 1 passed, 3 deselected in 0.01s ======================
|
||||
===================== 1 passed, 3 deselected in 0.12s ======================
|
||||
|
||||
Or the inverse, running all tests except the webtest ones:
|
||||
|
||||
|
@ -69,7 +69,7 @@ Or the inverse, running all tests except the webtest ones:
|
|||
test_server.py::test_another PASSED [ 66%]
|
||||
test_server.py::TestClass::test_method PASSED [100%]
|
||||
|
||||
===================== 3 passed, 1 deselected in 0.01s ======================
|
||||
===================== 3 passed, 1 deselected in 0.12s ======================
|
||||
|
||||
Selecting tests based on their node ID
|
||||
--------------------------------------
|
||||
|
@ -89,7 +89,7 @@ tests based on their module, class, method, or function name:
|
|||
|
||||
test_server.py::TestClass::test_method PASSED [100%]
|
||||
|
||||
============================ 1 passed in 0.01s =============================
|
||||
============================ 1 passed in 0.12s =============================
|
||||
|
||||
You can also select on the class:
|
||||
|
||||
|
@ -104,7 +104,7 @@ You can also select on the class:
|
|||
|
||||
test_server.py::TestClass::test_method PASSED [100%]
|
||||
|
||||
============================ 1 passed in 0.01s =============================
|
||||
============================ 1 passed in 0.12s =============================
|
||||
|
||||
Or select multiple nodes:
|
||||
|
||||
|
@ -120,7 +120,7 @@ Or select multiple nodes:
|
|||
test_server.py::TestClass::test_method PASSED [ 50%]
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
|
||||
============================ 2 passed in 0.01s =============================
|
||||
============================ 2 passed in 0.12s =============================
|
||||
|
||||
.. _node-id:
|
||||
|
||||
|
@ -159,7 +159,7 @@ select tests based on their names:
|
|||
|
||||
test_server.py::test_send_http PASSED [100%]
|
||||
|
||||
===================== 1 passed, 3 deselected in 0.01s ======================
|
||||
===================== 1 passed, 3 deselected in 0.12s ======================
|
||||
|
||||
And you can also run all tests except the ones that match the keyword:
|
||||
|
||||
|
@ -176,7 +176,7 @@ And you can also run all tests except the ones that match the keyword:
|
|||
test_server.py::test_another PASSED [ 66%]
|
||||
test_server.py::TestClass::test_method PASSED [100%]
|
||||
|
||||
===================== 3 passed, 1 deselected in 0.01s ======================
|
||||
===================== 3 passed, 1 deselected in 0.12s ======================
|
||||
|
||||
Or to select "http" and "quick" tests:
|
||||
|
||||
|
@ -192,7 +192,7 @@ Or to select "http" and "quick" tests:
|
|||
test_server.py::test_send_http PASSED [ 50%]
|
||||
test_server.py::test_something_quick PASSED [100%]
|
||||
|
||||
===================== 2 passed, 2 deselected in 0.01s ======================
|
||||
===================== 2 passed, 2 deselected in 0.12s ======================
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -413,7 +413,7 @@ the test needs:
|
|||
|
||||
test_someenv.py s [100%]
|
||||
|
||||
============================ 1 skipped in 0.00s ============================
|
||||
============================ 1 skipped in 0.12s ============================
|
||||
|
||||
and here is one that specifies exactly the environment needed:
|
||||
|
||||
|
@ -428,7 +428,7 @@ and here is one that specifies exactly the environment needed:
|
|||
|
||||
test_someenv.py . [100%]
|
||||
|
||||
============================ 1 passed in 0.01s =============================
|
||||
============================ 1 passed in 0.12s =============================
|
||||
|
||||
The ``--markers`` option always gives you a list of available markers:
|
||||
|
||||
|
@ -499,7 +499,7 @@ The output is as follows:
|
|||
$ pytest -q -s
|
||||
Mark(name='my_marker', args=(<function hello_world at 0xdeadbeef>,), kwargs={})
|
||||
.
|
||||
1 passed in 0.00s
|
||||
1 passed in 0.01s
|
||||
|
||||
We can see that the custom marker has its argument set extended with the function ``hello_world``. This is the key difference between creating a custom marker as a callable, which invokes ``__call__`` behind the scenes, and using ``with_args``.
|
||||
|
||||
|
@ -551,7 +551,7 @@ Let's run this without capturing output and see what we get:
|
|||
glob args=('class',) kwargs={'x': 2}
|
||||
glob args=('module',) kwargs={'x': 1}
|
||||
.
|
||||
1 passed in 0.01s
|
||||
1 passed in 0.02s
|
||||
|
||||
marking platform specific tests with pytest
|
||||
--------------------------------------------------------------
|
||||
|
@ -623,7 +623,7 @@ then you will see two tests skipped and two executed tests as expected:
|
|||
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:13: cannot run on platform linux
|
||||
======================= 2 passed, 2 skipped in 0.01s =======================
|
||||
======================= 2 passed, 2 skipped in 0.12s =======================
|
||||
|
||||
Note that if you specify a platform via the marker-command line option like this:
|
||||
|
||||
|
@ -638,7 +638,7 @@ Note that if you specify a platform via the marker-command line option like this
|
|||
|
||||
test_plat.py . [100%]
|
||||
|
||||
===================== 1 passed, 3 deselected in 0.01s ======================
|
||||
===================== 1 passed, 3 deselected in 0.12s ======================
|
||||
|
||||
then the unmarked-tests will not be run. It is thus a way to restrict the run to the specific tests.
|
||||
|
||||
|
@ -711,7 +711,7 @@ We can now use the ``-m option`` to select one set:
|
|||
test_module.py:8: in test_interface_complex
|
||||
assert 0
|
||||
E assert 0
|
||||
===================== 2 failed, 2 deselected in 0.02s ======================
|
||||
===================== 2 failed, 2 deselected in 0.12s ======================
|
||||
|
||||
or to select both "event" and "interface" tests:
|
||||
|
||||
|
@ -739,4 +739,4 @@ or to select both "event" and "interface" tests:
|
|||
test_module.py:12: in test_event_simple
|
||||
assert 0
|
||||
E assert 0
|
||||
===================== 3 failed, 1 deselected in 0.03s ======================
|
||||
===================== 3 failed, 1 deselected in 0.12s ======================
|
||||
|
|
|
@ -41,7 +41,7 @@ now execute the test specification:
|
|||
usecase execution failed
|
||||
spec failed: 'some': 'other'
|
||||
no further details known at this point.
|
||||
======================= 1 failed, 1 passed in 0.02s ========================
|
||||
======================= 1 failed, 1 passed in 0.12s ========================
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
|
@ -77,7 +77,7 @@ consulted when reporting in ``verbose`` mode:
|
|||
usecase execution failed
|
||||
spec failed: 'some': 'other'
|
||||
no further details known at this point.
|
||||
======================= 1 failed, 1 passed in 0.02s ========================
|
||||
======================= 1 failed, 1 passed in 0.12s ========================
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
|
@ -97,4 +97,4 @@ interesting to just look at the collection tree:
|
|||
<YamlItem hello>
|
||||
<YamlItem ok>
|
||||
|
||||
========================== no tests ran in 0.02s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
|
|
@ -172,7 +172,7 @@ objects, they are still using the default pytest representation:
|
|||
<Function test_timedistance_v3[forward]>
|
||||
<Function test_timedistance_v3[backward]>
|
||||
|
||||
========================== no tests ran in 0.01s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
In ``test_timedistance_v3``, we used ``pytest.param`` to specify the test IDs
|
||||
together with the actual data, instead of listing them separately.
|
||||
|
@ -229,7 +229,7 @@ this is a fully self-contained example which you can run with:
|
|||
|
||||
test_scenarios.py .... [100%]
|
||||
|
||||
============================ 4 passed in 0.01s =============================
|
||||
============================ 4 passed in 0.12s =============================
|
||||
|
||||
If you just collect tests you'll also nicely see 'advanced' and 'basic' as variants for the test function:
|
||||
|
||||
|
@ -248,7 +248,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
|
|||
<Function test_demo1[advanced]>
|
||||
<Function test_demo2[advanced]>
|
||||
|
||||
========================== no tests ran in 0.01s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
Note that we told ``metafunc.parametrize()`` that your scenario values
|
||||
should be considered class-scoped. With pytest-2.3 this leads to a
|
||||
|
@ -323,7 +323,7 @@ Let's first see how it looks like at collection time:
|
|||
<Function test_db_initialized[d1]>
|
||||
<Function test_db_initialized[d2]>
|
||||
|
||||
========================== no tests ran in 0.00s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
And then when we run the test:
|
||||
|
||||
|
@ -394,7 +394,7 @@ The result of this test will be successful:
|
|||
<Module test_indirect_list.py>
|
||||
<Function test_indirect[a-b]>
|
||||
|
||||
========================== no tests ran in 0.00s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
|
@ -475,10 +475,11 @@ Running it results in some skips if we don't have all the python interpreters in
|
|||
.. code-block:: pytest
|
||||
|
||||
. $ pytest -rs -q multipython.py
|
||||
ssssssssssss......sss...... [100%]
|
||||
ssssssssssss...ssssssssssss [100%]
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.5' not found
|
||||
12 passed, 15 skipped in 0.62s
|
||||
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.5' not found
|
||||
SKIPPED [12] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.7' not found
|
||||
3 passed, 24 skipped in 0.24s
|
||||
|
||||
Indirect parametrization of optional implementations/imports
|
||||
--------------------------------------------------------------------
|
||||
|
@ -547,7 +548,7 @@ If you run this with reporting for skips enabled:
|
|||
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:13: could not import 'opt2': No module named 'opt2'
|
||||
======================= 1 passed, 1 skipped in 0.01s =======================
|
||||
======================= 1 passed, 1 skipped in 0.12s =======================
|
||||
|
||||
You'll see that we don't have an ``opt2`` module and thus the second test run
|
||||
of our ``test_func1`` was skipped. A few notes:
|
||||
|
@ -609,7 +610,7 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
|
|||
test_pytest_param_example.py::test_eval[basic_2+4] PASSED [ 66%]
|
||||
test_pytest_param_example.py::test_eval[basic_6*9] XFAIL [100%]
|
||||
|
||||
=============== 2 passed, 15 deselected, 1 xfailed in 0.08s ================
|
||||
=============== 2 passed, 15 deselected, 1 xfailed in 0.12s ================
|
||||
|
||||
As the result:
|
||||
|
||||
|
|
|
@ -158,7 +158,7 @@ The test collection would look like this:
|
|||
<Function simple_check>
|
||||
<Function complex_check>
|
||||
|
||||
========================== no tests ran in 0.01s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
You can check for multiple glob patterns by adding a space between the patterns:
|
||||
|
||||
|
@ -221,7 +221,7 @@ You can always peek at the collection tree without running tests like this:
|
|||
<Function test_method>
|
||||
<Function test_anothermethod>
|
||||
|
||||
========================== no tests ran in 0.00s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
.. _customizing-test-collection:
|
||||
|
||||
|
@ -297,7 +297,7 @@ file will be left out:
|
|||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 0 items
|
||||
|
||||
========================== no tests ran in 0.01s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
It's also possible to ignore files based on Unix shell-style wildcards by adding
|
||||
patterns to ``collect_ignore_glob``.
|
||||
|
|
|
@ -650,4 +650,4 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
|
||||
|
||||
failure_demo.py:282: AssertionError
|
||||
============================ 44 failed in 0.26s ============================
|
||||
============================ 44 failed in 0.12s ============================
|
||||
|
|
|
@ -132,7 +132,7 @@ directory with the above conftest.py:
|
|||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 0 items
|
||||
|
||||
========================== no tests ran in 0.00s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
.. _`excontrolskip`:
|
||||
|
||||
|
@ -201,7 +201,7 @@ and when running it will see a skipped "slow" test:
|
|||
|
||||
========================= short test summary info ==========================
|
||||
SKIPPED [1] test_module.py:8: need --runslow option to run
|
||||
======================= 1 passed, 1 skipped in 0.01s =======================
|
||||
======================= 1 passed, 1 skipped in 0.12s =======================
|
||||
|
||||
Or run it including the ``slow`` marked test:
|
||||
|
||||
|
@ -216,7 +216,7 @@ Or run it including the ``slow`` marked test:
|
|||
|
||||
test_module.py .. [100%]
|
||||
|
||||
============================ 2 passed in 0.01s =============================
|
||||
============================ 2 passed in 0.12s =============================
|
||||
|
||||
Writing well integrated assertion helpers
|
||||
--------------------------------------------------
|
||||
|
@ -358,7 +358,7 @@ which will add the string to the test header accordingly:
|
|||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 0 items
|
||||
|
||||
========================== no tests ran in 0.00s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
.. regendoc:wipe
|
||||
|
||||
|
@ -388,7 +388,7 @@ which will add info only when run with "--v":
|
|||
rootdir: $REGENDOC_TMPDIR
|
||||
collecting ... collected 0 items
|
||||
|
||||
========================== no tests ran in 0.00s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
and nothing when run plainly:
|
||||
|
||||
|
@ -401,7 +401,7 @@ and nothing when run plainly:
|
|||
rootdir: $REGENDOC_TMPDIR
|
||||
collected 0 items
|
||||
|
||||
========================== no tests ran in 0.00s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
profiling test duration
|
||||
--------------------------
|
||||
|
@ -447,7 +447,7 @@ Now we can profile which test functions execute the slowest:
|
|||
0.30s call test_some_are_slow.py::test_funcslow2
|
||||
0.20s call test_some_are_slow.py::test_funcslow1
|
||||
0.10s call test_some_are_slow.py::test_funcfast
|
||||
============================ 3 passed in 0.61s =============================
|
||||
============================ 3 passed in 0.12s =============================
|
||||
|
||||
incremental testing - test steps
|
||||
---------------------------------------------------
|
||||
|
@ -531,7 +531,7 @@ If we run this:
|
|||
========================= short test summary info ==========================
|
||||
XFAIL test_step.py::TestUserHandling::test_deletion
|
||||
reason: previous test failed (test_modification)
|
||||
================== 1 failed, 2 passed, 1 xfailed in 0.03s ==================
|
||||
================== 1 failed, 2 passed, 1 xfailed in 0.12s ==================
|
||||
|
||||
We'll see that ``test_deletion`` was not executed because ``test_modification``
|
||||
failed. It is reported as an "expected failure".
|
||||
|
@ -644,7 +644,7 @@ We can run this:
|
|||
E assert 0
|
||||
|
||||
a/test_db2.py:2: AssertionError
|
||||
============= 3 failed, 2 passed, 1 xfailed, 1 error in 0.05s ==============
|
||||
============= 3 failed, 2 passed, 1 xfailed, 1 error in 0.12s ==============
|
||||
|
||||
The two test modules in the ``a`` directory see the same ``db`` fixture instance
|
||||
while the one test in the sister-directory ``b`` doesn't see it. We could of course
|
||||
|
@ -733,7 +733,7 @@ and run them:
|
|||
E assert 0
|
||||
|
||||
test_module.py:6: AssertionError
|
||||
============================ 2 failed in 0.02s =============================
|
||||
============================ 2 failed in 0.12s =============================
|
||||
|
||||
you will have a "failures" file which contains the failing test ids:
|
||||
|
||||
|
@ -848,7 +848,7 @@ and run it:
|
|||
E assert 0
|
||||
|
||||
test_module.py:19: AssertionError
|
||||
======================== 2 failed, 1 error in 0.02s ========================
|
||||
======================== 2 failed, 1 error in 0.12s ========================
|
||||
|
||||
You'll see that the fixture finalizers could use the precise reporting
|
||||
information.
|
||||
|
|
|
@ -96,7 +96,7 @@ marked ``smtp_connection`` fixture function. Running the test looks like this:
|
|||
E assert 0
|
||||
|
||||
test_smtpsimple.py:14: AssertionError
|
||||
============================ 1 failed in 0.18s =============================
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
In the failure traceback we see that the test function was called with a
|
||||
``smtp_connection`` argument, the ``smtplib.SMTP()`` instance created by the fixture
|
||||
|
@ -258,7 +258,7 @@ inspect what is going on and can now run the tests:
|
|||
E assert 0
|
||||
|
||||
test_module.py:13: AssertionError
|
||||
============================ 2 failed in 0.20s =============================
|
||||
============================ 2 failed in 0.12s =============================
|
||||
|
||||
You see the two ``assert 0`` failing and more importantly you can also see
|
||||
that the same (module-scoped) ``smtp_connection`` object was passed into the
|
||||
|
@ -361,7 +361,7 @@ Let's execute it:
|
|||
$ pytest -s -q --tb=no
|
||||
FFteardown smtp
|
||||
|
||||
2 failed in 0.20s
|
||||
2 failed in 0.79s
|
||||
|
||||
We see that the ``smtp_connection`` instance is finalized after the two
|
||||
tests finished execution. Note that if we decorated our fixture
|
||||
|
@ -515,7 +515,7 @@ again, nothing much has changed:
|
|||
$ pytest -s -q --tb=no
|
||||
FFfinalizing <smtplib.SMTP object at 0xdeadbeef> (smtp.gmail.com)
|
||||
|
||||
2 failed in 0.21s
|
||||
2 failed in 0.77s
|
||||
|
||||
Let's quickly create another test module that actually sets the
|
||||
server URL in its module namespace:
|
||||
|
@ -692,7 +692,7 @@ So let's just do another run:
|
|||
test_module.py:13: AssertionError
|
||||
------------------------- Captured stdout teardown -------------------------
|
||||
finalizing <smtplib.SMTP object at 0xdeadbeef>
|
||||
4 failed in 0.89s
|
||||
4 failed in 1.69s
|
||||
|
||||
We see that our two test functions each ran twice, against the different
|
||||
``smtp_connection`` instances. Note also, that with the ``mail.python.org``
|
||||
|
@ -771,7 +771,7 @@ Running the above tests results in the following test IDs being used:
|
|||
<Function test_ehlo[mail.python.org]>
|
||||
<Function test_noop[mail.python.org]>
|
||||
|
||||
========================== no tests ran in 0.01s ===========================
|
||||
========================== no tests ran in 0.12s ===========================
|
||||
|
||||
.. _`fixture-parametrize-marks`:
|
||||
|
||||
|
@ -812,7 +812,7 @@ Running this test will *skip* the invocation of ``data_set`` with value ``2``:
|
|||
test_fixture_marks.py::test_data[1] PASSED [ 66%]
|
||||
test_fixture_marks.py::test_data[2] SKIPPED [100%]
|
||||
|
||||
======================= 2 passed, 1 skipped in 0.01s =======================
|
||||
======================= 2 passed, 1 skipped in 0.12s =======================
|
||||
|
||||
.. _`interdependent fixtures`:
|
||||
|
||||
|
@ -861,7 +861,7 @@ Here we declare an ``app`` fixture which receives the previously defined
|
|||
test_appsetup.py::test_smtp_connection_exists[smtp.gmail.com] PASSED [ 50%]
|
||||
test_appsetup.py::test_smtp_connection_exists[mail.python.org] PASSED [100%]
|
||||
|
||||
============================ 2 passed in 0.44s =============================
|
||||
============================ 2 passed in 0.12s =============================
|
||||
|
||||
Due to the parametrization of ``smtp_connection``, the test will run twice with two
|
||||
different ``App`` instances and respective smtp servers. There is no
|
||||
|
@ -971,7 +971,7 @@ Let's run the tests in verbose mode and with looking at the print-output:
|
|||
TEARDOWN modarg mod2
|
||||
|
||||
|
||||
============================ 8 passed in 0.01s =============================
|
||||
============================ 8 passed in 0.12s =============================
|
||||
|
||||
You can see that the parametrized module-scoped ``modarg`` resource caused an
|
||||
ordering of test execution that lead to the fewest possible "active" resources.
|
||||
|
|
|
@ -69,7 +69,7 @@ That’s it. You can now execute the test function:
|
|||
E + where 4 = func(3)
|
||||
|
||||
test_sample.py:6: AssertionError
|
||||
============================ 1 failed in 0.02s =============================
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
This test returns a failure report because ``func(3)`` does not return ``5``.
|
||||
|
||||
|
@ -108,7 +108,7 @@ Execute the test function with “quiet” reporting mode:
|
|||
|
||||
$ pytest -q test_sysexit.py
|
||||
. [100%]
|
||||
1 passed in 0.00s
|
||||
1 passed in 0.01s
|
||||
|
||||
Group multiple tests in a class
|
||||
--------------------------------------------------------------
|
||||
|
|
|
@ -88,7 +88,7 @@ This has the following benefits:
|
|||
|
||||
.. note::
|
||||
|
||||
See :ref:`pythonpath` for more information about the difference between calling ``pytest`` and
|
||||
See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and
|
||||
``python -m pytest``.
|
||||
|
||||
Note that using this scheme your test files must have **unique names**, because
|
||||
|
|
|
@ -44,7 +44,7 @@ To execute it:
|
|||
E + where 4 = inc(3)
|
||||
|
||||
test_sample.py:6: AssertionError
|
||||
============================ 1 failed in 0.02s =============================
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
Due to ``pytest``'s detailed assertion introspection, only plain ``assert`` statements are used.
|
||||
See :ref:`Getting Started <getstarted>` for more examples.
|
||||
|
|
|
@ -75,7 +75,7 @@ them in turn:
|
|||
E + where 54 = eval('6*9')
|
||||
|
||||
test_expectation.py:6: AssertionError
|
||||
======================= 1 failed, 2 passed in 0.02s ========================
|
||||
======================= 1 failed, 2 passed in 0.12s ========================
|
||||
|
||||
.. note::
|
||||
|
||||
|
@ -128,7 +128,7 @@ Let's run this:
|
|||
|
||||
test_expectation.py ..x [100%]
|
||||
|
||||
======================= 2 passed, 1 xfailed in 0.02s =======================
|
||||
======================= 2 passed, 1 xfailed in 0.12s =======================
|
||||
|
||||
The one parameter set which caused a failure previously now
|
||||
shows up as an "xfailed (expected to fail)" test.
|
||||
|
|
|
@ -72,6 +72,8 @@ imported in the global import namespace.
|
|||
|
||||
This is also discussed in details in :ref:`test discovery`.
|
||||
|
||||
.. _`pytest vs python -m pytest`:
|
||||
|
||||
Invoking ``pytest`` versus ``python -m pytest``
|
||||
-----------------------------------------------
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ pytest.raises
|
|||
|
||||
**Tutorial**: :ref:`assertraises`.
|
||||
|
||||
.. autofunction:: pytest.raises(expected_exception: Exception, [match], [message])
|
||||
.. autofunction:: pytest.raises(expected_exception: Exception, [match])
|
||||
:with: excinfo
|
||||
|
||||
pytest.deprecated_call
|
||||
|
|
|
@ -371,7 +371,7 @@ Running it with the report-on-xfail option gives this output:
|
|||
XFAIL xfail_demo.py::test_hello6
|
||||
reason: reason
|
||||
XFAIL xfail_demo.py::test_hello7
|
||||
============================ 7 xfailed in 0.05s ============================
|
||||
============================ 7 xfailed in 0.12s ============================
|
||||
|
||||
.. _`skip/xfail with parametrize`:
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ Running this would result in a passed test except for the last
|
|||
E assert 0
|
||||
|
||||
test_tmp_path.py:13: AssertionError
|
||||
============================ 1 failed in 0.02s =============================
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
.. _`tmp_path_factory example`:
|
||||
|
||||
|
@ -133,7 +133,7 @@ Running this would result in a passed test except for the last
|
|||
E assert 0
|
||||
|
||||
test_tmpdir.py:9: AssertionError
|
||||
============================ 1 failed in 0.02s =============================
|
||||
============================ 1 failed in 0.12s =============================
|
||||
|
||||
.. _`tmpdir factory example`:
|
||||
|
||||
|
|
|
@ -166,7 +166,7 @@ the ``self.db`` values in the traceback:
|
|||
E assert 0
|
||||
|
||||
test_unittest_db.py:13: AssertionError
|
||||
============================ 2 failed in 0.02s =============================
|
||||
============================ 2 failed in 0.12s =============================
|
||||
|
||||
This default pytest traceback shows that the two test methods
|
||||
share the same ``self.db`` instance which was our intention
|
||||
|
|
|
@ -247,7 +247,7 @@ Example:
|
|||
XPASS test_example.py::test_xpass always xfail
|
||||
ERROR test_example.py::test_error - assert 0
|
||||
FAILED test_example.py::test_fail - assert 0
|
||||
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.03s ===
|
||||
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
|
||||
|
||||
The ``-r`` options accepts a number of characters after it, with ``a`` used
|
||||
above meaning "all except passes".
|
||||
|
@ -297,7 +297,7 @@ More than one character can be used, so for example to only see failed and skipp
|
|||
========================= short test summary info ==========================
|
||||
FAILED test_example.py::test_fail - assert 0
|
||||
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.03s ===
|
||||
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
|
||||
|
||||
Using ``p`` lists the passing tests, whilst ``P`` adds an extra section "PASSES" with those tests that passed but had
|
||||
captured output:
|
||||
|
@ -336,7 +336,7 @@ captured output:
|
|||
ok
|
||||
========================= short test summary info ==========================
|
||||
PASSED test_example.py::test_ok
|
||||
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.03s ===
|
||||
== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12s ===
|
||||
|
||||
.. _pdb-option:
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ Running pytest now produces this output:
|
|||
warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||
====================== 1 passed, 1 warnings in 0.00s =======================
|
||||
====================== 1 passed, 1 warnings in 0.12s =======================
|
||||
|
||||
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
|
||||
them into errors:
|
||||
|
|
|
@ -789,7 +789,11 @@ def _py36_windowsconsoleio_workaround(stream):
|
|||
|
||||
See https://github.com/pytest-dev/py/issues/103
|
||||
"""
|
||||
if not sys.platform.startswith("win32") or sys.version_info[:2] < (3, 6):
|
||||
if (
|
||||
not sys.platform.startswith("win32")
|
||||
or sys.version_info[:2] < (3, 6)
|
||||
or hasattr(sys, "pypy_version_info")
|
||||
):
|
||||
return
|
||||
|
||||
# bail out if ``stream`` doesn't seem like a proper ``io`` stream (#2666)
|
||||
|
|
|
@ -431,10 +431,7 @@ class PytestPluginManager(PluginManager):
|
|||
continue
|
||||
conftestpath = parent.join("conftest.py")
|
||||
if conftestpath.isfile():
|
||||
# Use realpath to avoid loading the same conftest twice
|
||||
# with build systems that create build directories containing
|
||||
# symlinks to actual files.
|
||||
mod = self._importconftest(conftestpath.realpath())
|
||||
mod = self._importconftest(conftestpath)
|
||||
clist.append(mod)
|
||||
self._dirpath2confmods[directory] = clist
|
||||
return clist
|
||||
|
@ -449,8 +446,14 @@ class PytestPluginManager(PluginManager):
|
|||
raise KeyError(name)
|
||||
|
||||
def _importconftest(self, conftestpath):
|
||||
# Use a resolved Path object as key to avoid loading the same conftest twice
|
||||
# with build systems that create build directories containing
|
||||
# symlinks to actual files.
|
||||
# Using Path().resolve() is better than py.path.realpath because
|
||||
# it resolves to the correct path/drive in case-insensitive file systems (#5792)
|
||||
key = Path(str(conftestpath)).resolve()
|
||||
try:
|
||||
return self._conftestpath2mod[conftestpath]
|
||||
return self._conftestpath2mod[key]
|
||||
except KeyError:
|
||||
pkgpath = conftestpath.pypkgpath()
|
||||
if pkgpath is None:
|
||||
|
@ -467,7 +470,7 @@ class PytestPluginManager(PluginManager):
|
|||
raise ConftestImportFailure(conftestpath, sys.exc_info())
|
||||
|
||||
self._conftest_plugins.add(mod)
|
||||
self._conftestpath2mod[conftestpath] = mod
|
||||
self._conftestpath2mod[key] = mod
|
||||
dirpath = conftestpath.dirpath()
|
||||
if dirpath in self._dirpath2confmods:
|
||||
for path, mods in self._dirpath2confmods.items():
|
||||
|
|
|
@ -859,7 +859,7 @@ class FixtureDef:
|
|||
if argname != "request":
|
||||
fixturedef.addfinalizer(functools.partial(self.finish, request=request))
|
||||
|
||||
my_cache_key = request.param_index
|
||||
my_cache_key = self.cache_key(request)
|
||||
cached_result = getattr(self, "cached_result", None)
|
||||
if cached_result is not None:
|
||||
result, cache_key, err = cached_result
|
||||
|
@ -877,6 +877,9 @@ class FixtureDef:
|
|||
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
|
||||
return hook.pytest_fixture_setup(fixturedef=self, request=request)
|
||||
|
||||
def cache_key(self, request):
|
||||
return request.param_index if not hasattr(request, "param") else request.param
|
||||
|
||||
def __repr__(self):
|
||||
return "<FixtureDef argname={!r} scope={!r} baseid={!r}>".format(
|
||||
self.argname, self.scope, self.baseid
|
||||
|
@ -897,6 +900,12 @@ def resolve_fixture_function(fixturedef, request):
|
|||
# request.instance so that code working with "fixturedef" behaves
|
||||
# as expected.
|
||||
if request.instance is not None:
|
||||
# handle the case where fixture is defined not in a test class, but some other class
|
||||
# (for example a plugin class with a fixture), see #2270
|
||||
if hasattr(fixturefunc, "__self__") and not isinstance(
|
||||
request.instance, fixturefunc.__self__.__class__
|
||||
):
|
||||
return fixturefunc
|
||||
fixturefunc = getimfunc(fixturedef.func)
|
||||
if fixturefunc != fixturedef.func:
|
||||
fixturefunc = fixturefunc.__get__(request.instance)
|
||||
|
@ -913,7 +922,7 @@ def pytest_fixture_setup(fixturedef, request):
|
|||
kwargs[argname] = result
|
||||
|
||||
fixturefunc = resolve_fixture_function(fixturedef, request)
|
||||
my_cache_key = request.param_index
|
||||
my_cache_key = fixturedef.cache_key(request)
|
||||
try:
|
||||
result = call_fixture_func(fixturefunc, request, kwargs)
|
||||
except TEST_OUTCOME:
|
||||
|
|
|
@ -263,7 +263,6 @@ class Node:
|
|||
fm = self.session._fixturemanager
|
||||
if excinfo.errisinstance(fm.FixtureLookupError):
|
||||
return excinfo.value.formatrepr()
|
||||
tbfilter = True
|
||||
if self.config.getoption("fulltrace", False):
|
||||
style = "long"
|
||||
else:
|
||||
|
@ -271,7 +270,6 @@ class Node:
|
|||
self._prunetraceback(excinfo)
|
||||
if len(excinfo.traceback) == 0:
|
||||
excinfo.traceback = tb
|
||||
tbfilter = False # prunetraceback already does it
|
||||
if style == "auto":
|
||||
style = "long"
|
||||
# XXX should excinfo.getrepr record all data and toterminal() process it?
|
||||
|
@ -297,7 +295,7 @@ class Node:
|
|||
abspath=abspath,
|
||||
showlocals=self.config.getoption("showlocals", False),
|
||||
style=style,
|
||||
tbfilter=tbfilter,
|
||||
tbfilter=False, # pruned already, or in --fulltrace mode.
|
||||
truncate_locals=truncate_locals,
|
||||
)
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ from typing import Union
|
|||
|
||||
import py
|
||||
|
||||
from _pytest._code.code import ExceptionChainRepr
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest._code.code import ReprEntry
|
||||
from _pytest._code.code import ReprEntryNative
|
||||
|
@ -161,46 +162,7 @@ class BaseReport:
|
|||
|
||||
Experimental method.
|
||||
"""
|
||||
|
||||
def disassembled_report(rep):
|
||||
reprtraceback = rep.longrepr.reprtraceback.__dict__.copy()
|
||||
reprcrash = rep.longrepr.reprcrash.__dict__.copy()
|
||||
|
||||
new_entries = []
|
||||
for entry in reprtraceback["reprentries"]:
|
||||
entry_data = {
|
||||
"type": type(entry).__name__,
|
||||
"data": entry.__dict__.copy(),
|
||||
}
|
||||
for key, value in entry_data["data"].items():
|
||||
if hasattr(value, "__dict__"):
|
||||
entry_data["data"][key] = value.__dict__.copy()
|
||||
new_entries.append(entry_data)
|
||||
|
||||
reprtraceback["reprentries"] = new_entries
|
||||
|
||||
return {
|
||||
"reprcrash": reprcrash,
|
||||
"reprtraceback": reprtraceback,
|
||||
"sections": rep.longrepr.sections,
|
||||
}
|
||||
|
||||
d = self.__dict__.copy()
|
||||
if hasattr(self.longrepr, "toterminal"):
|
||||
if hasattr(self.longrepr, "reprtraceback") and hasattr(
|
||||
self.longrepr, "reprcrash"
|
||||
):
|
||||
d["longrepr"] = disassembled_report(self)
|
||||
else:
|
||||
d["longrepr"] = str(self.longrepr)
|
||||
else:
|
||||
d["longrepr"] = self.longrepr
|
||||
for name in d:
|
||||
if isinstance(d[name], (py.path.local, Path)):
|
||||
d[name] = str(d[name])
|
||||
elif name == "result":
|
||||
d[name] = None # for now
|
||||
return d
|
||||
return _report_to_json(self)
|
||||
|
||||
@classmethod
|
||||
def _from_json(cls, reportdict):
|
||||
|
@ -212,54 +174,8 @@ class BaseReport:
|
|||
|
||||
Experimental method.
|
||||
"""
|
||||
if reportdict["longrepr"]:
|
||||
if (
|
||||
"reprcrash" in reportdict["longrepr"]
|
||||
and "reprtraceback" in reportdict["longrepr"]
|
||||
):
|
||||
|
||||
reprtraceback = reportdict["longrepr"]["reprtraceback"]
|
||||
reprcrash = reportdict["longrepr"]["reprcrash"]
|
||||
|
||||
unserialized_entries = []
|
||||
for entry_data in reprtraceback["reprentries"]:
|
||||
data = entry_data["data"]
|
||||
entry_type = entry_data["type"]
|
||||
if entry_type == "ReprEntry":
|
||||
reprfuncargs = None
|
||||
reprfileloc = None
|
||||
reprlocals = None
|
||||
if data["reprfuncargs"]:
|
||||
reprfuncargs = ReprFuncArgs(**data["reprfuncargs"])
|
||||
if data["reprfileloc"]:
|
||||
reprfileloc = ReprFileLocation(**data["reprfileloc"])
|
||||
if data["reprlocals"]:
|
||||
reprlocals = ReprLocals(data["reprlocals"]["lines"])
|
||||
|
||||
reprentry = ReprEntry(
|
||||
lines=data["lines"],
|
||||
reprfuncargs=reprfuncargs,
|
||||
reprlocals=reprlocals,
|
||||
filelocrepr=reprfileloc,
|
||||
style=data["style"],
|
||||
) # type: Union[ReprEntry, ReprEntryNative]
|
||||
elif entry_type == "ReprEntryNative":
|
||||
reprentry = ReprEntryNative(data["lines"])
|
||||
else:
|
||||
_report_unserialization_failure(entry_type, cls, reportdict)
|
||||
unserialized_entries.append(reprentry)
|
||||
reprtraceback["reprentries"] = unserialized_entries
|
||||
|
||||
exception_info = ReprExceptionInfo(
|
||||
reprtraceback=ReprTraceback(**reprtraceback),
|
||||
reprcrash=ReprFileLocation(**reprcrash),
|
||||
)
|
||||
|
||||
for section in reportdict["longrepr"]["sections"]:
|
||||
exception_info.addsection(*section)
|
||||
reportdict["longrepr"] = exception_info
|
||||
|
||||
return cls(**reportdict)
|
||||
kwargs = _report_kwargs_from_json(reportdict)
|
||||
return cls(**kwargs)
|
||||
|
||||
|
||||
def _report_unserialization_failure(type_name, report_class, reportdict):
|
||||
|
@ -425,3 +341,142 @@ def pytest_report_from_serializable(data):
|
|||
assert False, "Unknown report_type unserialize data: {}".format(
|
||||
data["_report_type"]
|
||||
)
|
||||
|
||||
|
||||
def _report_to_json(report):
|
||||
"""
|
||||
This was originally the serialize_report() function from xdist (ca03269).
|
||||
|
||||
Returns the contents of this report as a dict of builtin entries, suitable for
|
||||
serialization.
|
||||
"""
|
||||
|
||||
def serialize_repr_entry(entry):
|
||||
entry_data = {"type": type(entry).__name__, "data": entry.__dict__.copy()}
|
||||
for key, value in entry_data["data"].items():
|
||||
if hasattr(value, "__dict__"):
|
||||
entry_data["data"][key] = value.__dict__.copy()
|
||||
return entry_data
|
||||
|
||||
def serialize_repr_traceback(reprtraceback):
|
||||
result = reprtraceback.__dict__.copy()
|
||||
result["reprentries"] = [
|
||||
serialize_repr_entry(x) for x in reprtraceback.reprentries
|
||||
]
|
||||
return result
|
||||
|
||||
def serialize_repr_crash(reprcrash):
|
||||
return reprcrash.__dict__.copy()
|
||||
|
||||
def serialize_longrepr(rep):
|
||||
result = {
|
||||
"reprcrash": serialize_repr_crash(rep.longrepr.reprcrash),
|
||||
"reprtraceback": serialize_repr_traceback(rep.longrepr.reprtraceback),
|
||||
"sections": rep.longrepr.sections,
|
||||
}
|
||||
if isinstance(rep.longrepr, ExceptionChainRepr):
|
||||
result["chain"] = []
|
||||
for repr_traceback, repr_crash, description in rep.longrepr.chain:
|
||||
result["chain"].append(
|
||||
(
|
||||
serialize_repr_traceback(repr_traceback),
|
||||
serialize_repr_crash(repr_crash),
|
||||
description,
|
||||
)
|
||||
)
|
||||
else:
|
||||
result["chain"] = None
|
||||
return result
|
||||
|
||||
d = report.__dict__.copy()
|
||||
if hasattr(report.longrepr, "toterminal"):
|
||||
if hasattr(report.longrepr, "reprtraceback") and hasattr(
|
||||
report.longrepr, "reprcrash"
|
||||
):
|
||||
d["longrepr"] = serialize_longrepr(report)
|
||||
else:
|
||||
d["longrepr"] = str(report.longrepr)
|
||||
else:
|
||||
d["longrepr"] = report.longrepr
|
||||
for name in d:
|
||||
if isinstance(d[name], (py.path.local, Path)):
|
||||
d[name] = str(d[name])
|
||||
elif name == "result":
|
||||
d[name] = None # for now
|
||||
return d
|
||||
|
||||
|
||||
def _report_kwargs_from_json(reportdict):
|
||||
"""
|
||||
This was originally the serialize_report() function from xdist (ca03269).
|
||||
|
||||
Returns **kwargs that can be used to construct a TestReport or CollectReport instance.
|
||||
"""
|
||||
|
||||
def deserialize_repr_entry(entry_data):
|
||||
data = entry_data["data"]
|
||||
entry_type = entry_data["type"]
|
||||
if entry_type == "ReprEntry":
|
||||
reprfuncargs = None
|
||||
reprfileloc = None
|
||||
reprlocals = None
|
||||
if data["reprfuncargs"]:
|
||||
reprfuncargs = ReprFuncArgs(**data["reprfuncargs"])
|
||||
if data["reprfileloc"]:
|
||||
reprfileloc = ReprFileLocation(**data["reprfileloc"])
|
||||
if data["reprlocals"]:
|
||||
reprlocals = ReprLocals(data["reprlocals"]["lines"])
|
||||
|
||||
reprentry = ReprEntry(
|
||||
lines=data["lines"],
|
||||
reprfuncargs=reprfuncargs,
|
||||
reprlocals=reprlocals,
|
||||
filelocrepr=reprfileloc,
|
||||
style=data["style"],
|
||||
) # type: Union[ReprEntry, ReprEntryNative]
|
||||
elif entry_type == "ReprEntryNative":
|
||||
reprentry = ReprEntryNative(data["lines"])
|
||||
else:
|
||||
_report_unserialization_failure(entry_type, TestReport, reportdict)
|
||||
return reprentry
|
||||
|
||||
def deserialize_repr_traceback(repr_traceback_dict):
|
||||
repr_traceback_dict["reprentries"] = [
|
||||
deserialize_repr_entry(x) for x in repr_traceback_dict["reprentries"]
|
||||
]
|
||||
return ReprTraceback(**repr_traceback_dict)
|
||||
|
||||
def deserialize_repr_crash(repr_crash_dict):
|
||||
return ReprFileLocation(**repr_crash_dict)
|
||||
|
||||
if (
|
||||
reportdict["longrepr"]
|
||||
and "reprcrash" in reportdict["longrepr"]
|
||||
and "reprtraceback" in reportdict["longrepr"]
|
||||
):
|
||||
|
||||
reprtraceback = deserialize_repr_traceback(
|
||||
reportdict["longrepr"]["reprtraceback"]
|
||||
)
|
||||
reprcrash = deserialize_repr_crash(reportdict["longrepr"]["reprcrash"])
|
||||
if reportdict["longrepr"]["chain"]:
|
||||
chain = []
|
||||
for repr_traceback_data, repr_crash_data, description in reportdict[
|
||||
"longrepr"
|
||||
]["chain"]:
|
||||
chain.append(
|
||||
(
|
||||
deserialize_repr_traceback(repr_traceback_data),
|
||||
deserialize_repr_crash(repr_crash_data),
|
||||
description,
|
||||
)
|
||||
)
|
||||
exception_info = ExceptionChainRepr(chain)
|
||||
else:
|
||||
exception_info = ReprExceptionInfo(reprtraceback, reprcrash)
|
||||
|
||||
for section in reportdict["longrepr"]["sections"]:
|
||||
exception_info.addsection(*section)
|
||||
reportdict["longrepr"] = exception_info
|
||||
|
||||
return reportdict
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import sys
|
||||
from unittest import mock
|
||||
|
||||
from test_excinfo import TWMock
|
||||
|
||||
import _pytest._code
|
||||
import pytest
|
||||
|
||||
|
@ -168,17 +166,15 @@ class TestTracebackEntry:
|
|||
|
||||
|
||||
class TestReprFuncArgs:
|
||||
def test_not_raise_exception_with_mixed_encoding(self):
|
||||
def test_not_raise_exception_with_mixed_encoding(self, tw_mock):
|
||||
from _pytest._code.code import ReprFuncArgs
|
||||
|
||||
tw = TWMock()
|
||||
|
||||
args = [("unicode_string", "São Paulo"), ("utf8_string", b"S\xc3\xa3o Paulo")]
|
||||
|
||||
r = ReprFuncArgs(args)
|
||||
r.toterminal(tw)
|
||||
r.toterminal(tw_mock)
|
||||
|
||||
assert (
|
||||
tw.lines[0]
|
||||
tw_mock.lines[0]
|
||||
== r"unicode_string = São Paulo, utf8_string = b'S\xc3\xa3o Paulo'"
|
||||
)
|
||||
|
|
|
@ -31,33 +31,6 @@ def limited_recursion_depth():
|
|||
sys.setrecursionlimit(before)
|
||||
|
||||
|
||||
class TWMock:
|
||||
WRITE = object()
|
||||
|
||||
def __init__(self):
|
||||
self.lines = []
|
||||
self.is_writing = False
|
||||
|
||||
def sep(self, sep, line=None):
|
||||
self.lines.append((sep, line))
|
||||
|
||||
def write(self, msg, **kw):
|
||||
self.lines.append((TWMock.WRITE, msg))
|
||||
|
||||
def line(self, line, **kw):
|
||||
self.lines.append(line)
|
||||
|
||||
def markup(self, text, **kw):
|
||||
return text
|
||||
|
||||
def get_write_msg(self, idx):
|
||||
flag, msg = self.lines[idx]
|
||||
assert flag == TWMock.WRITE
|
||||
return msg
|
||||
|
||||
fullwidth = 80
|
||||
|
||||
|
||||
def test_excinfo_simple() -> None:
|
||||
try:
|
||||
raise ValueError
|
||||
|
@ -426,6 +399,13 @@ def test_match_raises_error(testdir):
|
|||
result = testdir.runpytest()
|
||||
assert result.ret != 0
|
||||
result.stdout.fnmatch_lines(["*AssertionError*Pattern*[123]*not found*"])
|
||||
assert "__tracebackhide__ = True" not in result.stdout.str()
|
||||
|
||||
result = testdir.runpytest("--fulltrace")
|
||||
assert result.ret != 0
|
||||
result.stdout.fnmatch_lines(
|
||||
["*__tracebackhide__ = True*", "*AssertionError*Pattern*[123]*not found*"]
|
||||
)
|
||||
|
||||
|
||||
class TestFormattedExcinfo:
|
||||
|
@ -658,7 +638,7 @@ raise ValueError()
|
|||
assert loc.lineno == 3
|
||||
# assert loc.message == "ValueError: hello"
|
||||
|
||||
def test_repr_tracebackentry_lines2(self, importasmod):
|
||||
def test_repr_tracebackentry_lines2(self, importasmod, tw_mock):
|
||||
mod = importasmod(
|
||||
"""
|
||||
def func1(m, x, y, z):
|
||||
|
@ -678,13 +658,12 @@ raise ValueError()
|
|||
p = FormattedExcinfo(funcargs=True)
|
||||
repr_entry = p.repr_traceback_entry(entry)
|
||||
assert repr_entry.reprfuncargs.args == reprfuncargs.args
|
||||
tw = TWMock()
|
||||
repr_entry.toterminal(tw)
|
||||
assert tw.lines[0] == "m = " + repr("m" * 90)
|
||||
assert tw.lines[1] == "x = 5, y = 13"
|
||||
assert tw.lines[2] == "z = " + repr("z" * 120)
|
||||
repr_entry.toterminal(tw_mock)
|
||||
assert tw_mock.lines[0] == "m = " + repr("m" * 90)
|
||||
assert tw_mock.lines[1] == "x = 5, y = 13"
|
||||
assert tw_mock.lines[2] == "z = " + repr("z" * 120)
|
||||
|
||||
def test_repr_tracebackentry_lines_var_kw_args(self, importasmod):
|
||||
def test_repr_tracebackentry_lines_var_kw_args(self, importasmod, tw_mock):
|
||||
mod = importasmod(
|
||||
"""
|
||||
def func1(x, *y, **z):
|
||||
|
@ -703,9 +682,8 @@ raise ValueError()
|
|||
p = FormattedExcinfo(funcargs=True)
|
||||
repr_entry = p.repr_traceback_entry(entry)
|
||||
assert repr_entry.reprfuncargs.args == reprfuncargs.args
|
||||
tw = TWMock()
|
||||
repr_entry.toterminal(tw)
|
||||
assert tw.lines[0] == "x = 'a', y = ('b',), z = {'c': 'd'}"
|
||||
repr_entry.toterminal(tw_mock)
|
||||
assert tw_mock.lines[0] == "x = 'a', y = ('b',), z = {'c': 'd'}"
|
||||
|
||||
def test_repr_tracebackentry_short(self, importasmod):
|
||||
mod = importasmod(
|
||||
|
@ -842,7 +820,7 @@ raise ValueError()
|
|||
assert p._makepath(__file__) == __file__
|
||||
p.repr_traceback(excinfo)
|
||||
|
||||
def test_repr_excinfo_addouterr(self, importasmod):
|
||||
def test_repr_excinfo_addouterr(self, importasmod, tw_mock):
|
||||
mod = importasmod(
|
||||
"""
|
||||
def entry():
|
||||
|
@ -852,10 +830,9 @@ raise ValueError()
|
|||
excinfo = pytest.raises(ValueError, mod.entry)
|
||||
repr = excinfo.getrepr()
|
||||
repr.addsection("title", "content")
|
||||
twmock = TWMock()
|
||||
repr.toterminal(twmock)
|
||||
assert twmock.lines[-1] == "content"
|
||||
assert twmock.lines[-2] == ("-", "title")
|
||||
repr.toterminal(tw_mock)
|
||||
assert tw_mock.lines[-1] == "content"
|
||||
assert tw_mock.lines[-2] == ("-", "title")
|
||||
|
||||
def test_repr_excinfo_reprcrash(self, importasmod):
|
||||
mod = importasmod(
|
||||
|
@ -920,7 +897,7 @@ raise ValueError()
|
|||
x = str(MyRepr())
|
||||
assert x == "я"
|
||||
|
||||
def test_toterminal_long(self, importasmod):
|
||||
def test_toterminal_long(self, importasmod, tw_mock):
|
||||
mod = importasmod(
|
||||
"""
|
||||
def g(x):
|
||||
|
@ -932,27 +909,26 @@ raise ValueError()
|
|||
excinfo = pytest.raises(ValueError, mod.f)
|
||||
excinfo.traceback = excinfo.traceback.filter()
|
||||
repr = excinfo.getrepr()
|
||||
tw = TWMock()
|
||||
repr.toterminal(tw)
|
||||
assert tw.lines[0] == ""
|
||||
tw.lines.pop(0)
|
||||
assert tw.lines[0] == " def f():"
|
||||
assert tw.lines[1] == "> g(3)"
|
||||
assert tw.lines[2] == ""
|
||||
line = tw.get_write_msg(3)
|
||||
repr.toterminal(tw_mock)
|
||||
assert tw_mock.lines[0] == ""
|
||||
tw_mock.lines.pop(0)
|
||||
assert tw_mock.lines[0] == " def f():"
|
||||
assert tw_mock.lines[1] == "> g(3)"
|
||||
assert tw_mock.lines[2] == ""
|
||||
line = tw_mock.get_write_msg(3)
|
||||
assert line.endswith("mod.py")
|
||||
assert tw.lines[4] == (":5: ")
|
||||
assert tw.lines[5] == ("_ ", None)
|
||||
assert tw.lines[6] == ""
|
||||
assert tw.lines[7] == " def g(x):"
|
||||
assert tw.lines[8] == "> raise ValueError(x)"
|
||||
assert tw.lines[9] == "E ValueError: 3"
|
||||
assert tw.lines[10] == ""
|
||||
line = tw.get_write_msg(11)
|
||||
assert tw_mock.lines[4] == (":5: ")
|
||||
assert tw_mock.lines[5] == ("_ ", None)
|
||||
assert tw_mock.lines[6] == ""
|
||||
assert tw_mock.lines[7] == " def g(x):"
|
||||
assert tw_mock.lines[8] == "> raise ValueError(x)"
|
||||
assert tw_mock.lines[9] == "E ValueError: 3"
|
||||
assert tw_mock.lines[10] == ""
|
||||
line = tw_mock.get_write_msg(11)
|
||||
assert line.endswith("mod.py")
|
||||
assert tw.lines[12] == ":3: ValueError"
|
||||
assert tw_mock.lines[12] == ":3: ValueError"
|
||||
|
||||
def test_toterminal_long_missing_source(self, importasmod, tmpdir):
|
||||
def test_toterminal_long_missing_source(self, importasmod, tmpdir, tw_mock):
|
||||
mod = importasmod(
|
||||
"""
|
||||
def g(x):
|
||||
|
@ -965,25 +941,24 @@ raise ValueError()
|
|||
tmpdir.join("mod.py").remove()
|
||||
excinfo.traceback = excinfo.traceback.filter()
|
||||
repr = excinfo.getrepr()
|
||||
tw = TWMock()
|
||||
repr.toterminal(tw)
|
||||
assert tw.lines[0] == ""
|
||||
tw.lines.pop(0)
|
||||
assert tw.lines[0] == "> ???"
|
||||
assert tw.lines[1] == ""
|
||||
line = tw.get_write_msg(2)
|
||||
repr.toterminal(tw_mock)
|
||||
assert tw_mock.lines[0] == ""
|
||||
tw_mock.lines.pop(0)
|
||||
assert tw_mock.lines[0] == "> ???"
|
||||
assert tw_mock.lines[1] == ""
|
||||
line = tw_mock.get_write_msg(2)
|
||||
assert line.endswith("mod.py")
|
||||
assert tw.lines[3] == ":5: "
|
||||
assert tw.lines[4] == ("_ ", None)
|
||||
assert tw.lines[5] == ""
|
||||
assert tw.lines[6] == "> ???"
|
||||
assert tw.lines[7] == "E ValueError: 3"
|
||||
assert tw.lines[8] == ""
|
||||
line = tw.get_write_msg(9)
|
||||
assert tw_mock.lines[3] == ":5: "
|
||||
assert tw_mock.lines[4] == ("_ ", None)
|
||||
assert tw_mock.lines[5] == ""
|
||||
assert tw_mock.lines[6] == "> ???"
|
||||
assert tw_mock.lines[7] == "E ValueError: 3"
|
||||
assert tw_mock.lines[8] == ""
|
||||
line = tw_mock.get_write_msg(9)
|
||||
assert line.endswith("mod.py")
|
||||
assert tw.lines[10] == ":3: ValueError"
|
||||
assert tw_mock.lines[10] == ":3: ValueError"
|
||||
|
||||
def test_toterminal_long_incomplete_source(self, importasmod, tmpdir):
|
||||
def test_toterminal_long_incomplete_source(self, importasmod, tmpdir, tw_mock):
|
||||
mod = importasmod(
|
||||
"""
|
||||
def g(x):
|
||||
|
@ -996,25 +971,24 @@ raise ValueError()
|
|||
tmpdir.join("mod.py").write("asdf")
|
||||
excinfo.traceback = excinfo.traceback.filter()
|
||||
repr = excinfo.getrepr()
|
||||
tw = TWMock()
|
||||
repr.toterminal(tw)
|
||||
assert tw.lines[0] == ""
|
||||
tw.lines.pop(0)
|
||||
assert tw.lines[0] == "> ???"
|
||||
assert tw.lines[1] == ""
|
||||
line = tw.get_write_msg(2)
|
||||
repr.toterminal(tw_mock)
|
||||
assert tw_mock.lines[0] == ""
|
||||
tw_mock.lines.pop(0)
|
||||
assert tw_mock.lines[0] == "> ???"
|
||||
assert tw_mock.lines[1] == ""
|
||||
line = tw_mock.get_write_msg(2)
|
||||
assert line.endswith("mod.py")
|
||||
assert tw.lines[3] == ":5: "
|
||||
assert tw.lines[4] == ("_ ", None)
|
||||
assert tw.lines[5] == ""
|
||||
assert tw.lines[6] == "> ???"
|
||||
assert tw.lines[7] == "E ValueError: 3"
|
||||
assert tw.lines[8] == ""
|
||||
line = tw.get_write_msg(9)
|
||||
assert tw_mock.lines[3] == ":5: "
|
||||
assert tw_mock.lines[4] == ("_ ", None)
|
||||
assert tw_mock.lines[5] == ""
|
||||
assert tw_mock.lines[6] == "> ???"
|
||||
assert tw_mock.lines[7] == "E ValueError: 3"
|
||||
assert tw_mock.lines[8] == ""
|
||||
line = tw_mock.get_write_msg(9)
|
||||
assert line.endswith("mod.py")
|
||||
assert tw.lines[10] == ":3: ValueError"
|
||||
assert tw_mock.lines[10] == ":3: ValueError"
|
||||
|
||||
def test_toterminal_long_filenames(self, importasmod):
|
||||
def test_toterminal_long_filenames(self, importasmod, tw_mock):
|
||||
mod = importasmod(
|
||||
"""
|
||||
def f():
|
||||
|
@ -1022,23 +996,22 @@ raise ValueError()
|
|||
"""
|
||||
)
|
||||
excinfo = pytest.raises(ValueError, mod.f)
|
||||
tw = TWMock()
|
||||
path = py.path.local(mod.__file__)
|
||||
old = path.dirpath().chdir()
|
||||
try:
|
||||
repr = excinfo.getrepr(abspath=False)
|
||||
repr.toterminal(tw)
|
||||
repr.toterminal(tw_mock)
|
||||
x = py.path.local().bestrelpath(path)
|
||||
if len(x) < len(str(path)):
|
||||
msg = tw.get_write_msg(-2)
|
||||
msg = tw_mock.get_write_msg(-2)
|
||||
assert msg == "mod.py"
|
||||
assert tw.lines[-1] == ":3: ValueError"
|
||||
assert tw_mock.lines[-1] == ":3: ValueError"
|
||||
|
||||
repr = excinfo.getrepr(abspath=True)
|
||||
repr.toterminal(tw)
|
||||
msg = tw.get_write_msg(-2)
|
||||
repr.toterminal(tw_mock)
|
||||
msg = tw_mock.get_write_msg(-2)
|
||||
assert msg == path
|
||||
line = tw.lines[-1]
|
||||
line = tw_mock.lines[-1]
|
||||
assert line == ":3: ValueError"
|
||||
finally:
|
||||
old.chdir()
|
||||
|
@ -1073,7 +1046,7 @@ raise ValueError()
|
|||
repr.toterminal(tw)
|
||||
assert tw.stringio.getvalue()
|
||||
|
||||
def test_traceback_repr_style(self, importasmod):
|
||||
def test_traceback_repr_style(self, importasmod, tw_mock):
|
||||
mod = importasmod(
|
||||
"""
|
||||
def f():
|
||||
|
@ -1091,35 +1064,34 @@ raise ValueError()
|
|||
excinfo.traceback[1].set_repr_style("short")
|
||||
excinfo.traceback[2].set_repr_style("short")
|
||||
r = excinfo.getrepr(style="long")
|
||||
tw = TWMock()
|
||||
r.toterminal(tw)
|
||||
for line in tw.lines:
|
||||
r.toterminal(tw_mock)
|
||||
for line in tw_mock.lines:
|
||||
print(line)
|
||||
assert tw.lines[0] == ""
|
||||
assert tw.lines[1] == " def f():"
|
||||
assert tw.lines[2] == "> g()"
|
||||
assert tw.lines[3] == ""
|
||||
msg = tw.get_write_msg(4)
|
||||
assert tw_mock.lines[0] == ""
|
||||
assert tw_mock.lines[1] == " def f():"
|
||||
assert tw_mock.lines[2] == "> g()"
|
||||
assert tw_mock.lines[3] == ""
|
||||
msg = tw_mock.get_write_msg(4)
|
||||
assert msg.endswith("mod.py")
|
||||
assert tw.lines[5] == ":3: "
|
||||
assert tw.lines[6] == ("_ ", None)
|
||||
tw.get_write_msg(7)
|
||||
assert tw.lines[8].endswith("in g")
|
||||
assert tw.lines[9] == " h()"
|
||||
tw.get_write_msg(10)
|
||||
assert tw.lines[11].endswith("in h")
|
||||
assert tw.lines[12] == " i()"
|
||||
assert tw.lines[13] == ("_ ", None)
|
||||
assert tw.lines[14] == ""
|
||||
assert tw.lines[15] == " def i():"
|
||||
assert tw.lines[16] == "> raise ValueError()"
|
||||
assert tw.lines[17] == "E ValueError"
|
||||
assert tw.lines[18] == ""
|
||||
msg = tw.get_write_msg(19)
|
||||
assert tw_mock.lines[5] == ":3: "
|
||||
assert tw_mock.lines[6] == ("_ ", None)
|
||||
tw_mock.get_write_msg(7)
|
||||
assert tw_mock.lines[8].endswith("in g")
|
||||
assert tw_mock.lines[9] == " h()"
|
||||
tw_mock.get_write_msg(10)
|
||||
assert tw_mock.lines[11].endswith("in h")
|
||||
assert tw_mock.lines[12] == " i()"
|
||||
assert tw_mock.lines[13] == ("_ ", None)
|
||||
assert tw_mock.lines[14] == ""
|
||||
assert tw_mock.lines[15] == " def i():"
|
||||
assert tw_mock.lines[16] == "> raise ValueError()"
|
||||
assert tw_mock.lines[17] == "E ValueError"
|
||||
assert tw_mock.lines[18] == ""
|
||||
msg = tw_mock.get_write_msg(19)
|
||||
msg.endswith("mod.py")
|
||||
assert tw.lines[20] == ":9: ValueError"
|
||||
assert tw_mock.lines[20] == ":9: ValueError"
|
||||
|
||||
def test_exc_chain_repr(self, importasmod):
|
||||
def test_exc_chain_repr(self, importasmod, tw_mock):
|
||||
mod = importasmod(
|
||||
"""
|
||||
class Err(Exception):
|
||||
|
@ -1140,72 +1112,71 @@ raise ValueError()
|
|||
)
|
||||
excinfo = pytest.raises(AttributeError, mod.f)
|
||||
r = excinfo.getrepr(style="long")
|
||||
tw = TWMock()
|
||||
r.toterminal(tw)
|
||||
for line in tw.lines:
|
||||
r.toterminal(tw_mock)
|
||||
for line in tw_mock.lines:
|
||||
print(line)
|
||||
assert tw.lines[0] == ""
|
||||
assert tw.lines[1] == " def f():"
|
||||
assert tw.lines[2] == " try:"
|
||||
assert tw.lines[3] == "> g()"
|
||||
assert tw.lines[4] == ""
|
||||
line = tw.get_write_msg(5)
|
||||
assert tw_mock.lines[0] == ""
|
||||
assert tw_mock.lines[1] == " def f():"
|
||||
assert tw_mock.lines[2] == " try:"
|
||||
assert tw_mock.lines[3] == "> g()"
|
||||
assert tw_mock.lines[4] == ""
|
||||
line = tw_mock.get_write_msg(5)
|
||||
assert line.endswith("mod.py")
|
||||
assert tw.lines[6] == ":6: "
|
||||
assert tw.lines[7] == ("_ ", None)
|
||||
assert tw.lines[8] == ""
|
||||
assert tw.lines[9] == " def g():"
|
||||
assert tw.lines[10] == "> raise ValueError()"
|
||||
assert tw.lines[11] == "E ValueError"
|
||||
assert tw.lines[12] == ""
|
||||
line = tw.get_write_msg(13)
|
||||
assert tw_mock.lines[6] == ":6: "
|
||||
assert tw_mock.lines[7] == ("_ ", None)
|
||||
assert tw_mock.lines[8] == ""
|
||||
assert tw_mock.lines[9] == " def g():"
|
||||
assert tw_mock.lines[10] == "> raise ValueError()"
|
||||
assert tw_mock.lines[11] == "E ValueError"
|
||||
assert tw_mock.lines[12] == ""
|
||||
line = tw_mock.get_write_msg(13)
|
||||
assert line.endswith("mod.py")
|
||||
assert tw.lines[14] == ":12: ValueError"
|
||||
assert tw.lines[15] == ""
|
||||
assert tw_mock.lines[14] == ":12: ValueError"
|
||||
assert tw_mock.lines[15] == ""
|
||||
assert (
|
||||
tw.lines[16]
|
||||
tw_mock.lines[16]
|
||||
== "The above exception was the direct cause of the following exception:"
|
||||
)
|
||||
assert tw.lines[17] == ""
|
||||
assert tw.lines[18] == " def f():"
|
||||
assert tw.lines[19] == " try:"
|
||||
assert tw.lines[20] == " g()"
|
||||
assert tw.lines[21] == " except Exception as e:"
|
||||
assert tw.lines[22] == "> raise Err() from e"
|
||||
assert tw.lines[23] == "E test_exc_chain_repr0.mod.Err"
|
||||
assert tw.lines[24] == ""
|
||||
line = tw.get_write_msg(25)
|
||||
assert tw_mock.lines[17] == ""
|
||||
assert tw_mock.lines[18] == " def f():"
|
||||
assert tw_mock.lines[19] == " try:"
|
||||
assert tw_mock.lines[20] == " g()"
|
||||
assert tw_mock.lines[21] == " except Exception as e:"
|
||||
assert tw_mock.lines[22] == "> raise Err() from e"
|
||||
assert tw_mock.lines[23] == "E test_exc_chain_repr0.mod.Err"
|
||||
assert tw_mock.lines[24] == ""
|
||||
line = tw_mock.get_write_msg(25)
|
||||
assert line.endswith("mod.py")
|
||||
assert tw.lines[26] == ":8: Err"
|
||||
assert tw.lines[27] == ""
|
||||
assert tw_mock.lines[26] == ":8: Err"
|
||||
assert tw_mock.lines[27] == ""
|
||||
assert (
|
||||
tw.lines[28]
|
||||
tw_mock.lines[28]
|
||||
== "During handling of the above exception, another exception occurred:"
|
||||
)
|
||||
assert tw.lines[29] == ""
|
||||
assert tw.lines[30] == " def f():"
|
||||
assert tw.lines[31] == " try:"
|
||||
assert tw.lines[32] == " g()"
|
||||
assert tw.lines[33] == " except Exception as e:"
|
||||
assert tw.lines[34] == " raise Err() from e"
|
||||
assert tw.lines[35] == " finally:"
|
||||
assert tw.lines[36] == "> h()"
|
||||
assert tw.lines[37] == ""
|
||||
line = tw.get_write_msg(38)
|
||||
assert tw_mock.lines[29] == ""
|
||||
assert tw_mock.lines[30] == " def f():"
|
||||
assert tw_mock.lines[31] == " try:"
|
||||
assert tw_mock.lines[32] == " g()"
|
||||
assert tw_mock.lines[33] == " except Exception as e:"
|
||||
assert tw_mock.lines[34] == " raise Err() from e"
|
||||
assert tw_mock.lines[35] == " finally:"
|
||||
assert tw_mock.lines[36] == "> h()"
|
||||
assert tw_mock.lines[37] == ""
|
||||
line = tw_mock.get_write_msg(38)
|
||||
assert line.endswith("mod.py")
|
||||
assert tw.lines[39] == ":10: "
|
||||
assert tw.lines[40] == ("_ ", None)
|
||||
assert tw.lines[41] == ""
|
||||
assert tw.lines[42] == " def h():"
|
||||
assert tw.lines[43] == "> raise AttributeError()"
|
||||
assert tw.lines[44] == "E AttributeError"
|
||||
assert tw.lines[45] == ""
|
||||
line = tw.get_write_msg(46)
|
||||
assert tw_mock.lines[39] == ":10: "
|
||||
assert tw_mock.lines[40] == ("_ ", None)
|
||||
assert tw_mock.lines[41] == ""
|
||||
assert tw_mock.lines[42] == " def h():"
|
||||
assert tw_mock.lines[43] == "> raise AttributeError()"
|
||||
assert tw_mock.lines[44] == "E AttributeError"
|
||||
assert tw_mock.lines[45] == ""
|
||||
line = tw_mock.get_write_msg(46)
|
||||
assert line.endswith("mod.py")
|
||||
assert tw.lines[47] == ":15: AttributeError"
|
||||
assert tw_mock.lines[47] == ":15: AttributeError"
|
||||
|
||||
@pytest.mark.parametrize("mode", ["from_none", "explicit_suppress"])
|
||||
def test_exc_repr_chain_suppression(self, importasmod, mode):
|
||||
def test_exc_repr_chain_suppression(self, importasmod, mode, tw_mock):
|
||||
"""Check that exc repr does not show chained exceptions in Python 3.
|
||||
- When the exception is raised with "from None"
|
||||
- Explicitly suppressed with "chain=False" to ExceptionInfo.getrepr().
|
||||
|
@ -1226,24 +1197,23 @@ raise ValueError()
|
|||
)
|
||||
excinfo = pytest.raises(AttributeError, mod.f)
|
||||
r = excinfo.getrepr(style="long", chain=mode != "explicit_suppress")
|
||||
tw = TWMock()
|
||||
r.toterminal(tw)
|
||||
for line in tw.lines:
|
||||
r.toterminal(tw_mock)
|
||||
for line in tw_mock.lines:
|
||||
print(line)
|
||||
assert tw.lines[0] == ""
|
||||
assert tw.lines[1] == " def f():"
|
||||
assert tw.lines[2] == " try:"
|
||||
assert tw.lines[3] == " g()"
|
||||
assert tw.lines[4] == " except Exception:"
|
||||
assert tw.lines[5] == "> raise AttributeError(){}".format(
|
||||
assert tw_mock.lines[0] == ""
|
||||
assert tw_mock.lines[1] == " def f():"
|
||||
assert tw_mock.lines[2] == " try:"
|
||||
assert tw_mock.lines[3] == " g()"
|
||||
assert tw_mock.lines[4] == " except Exception:"
|
||||
assert tw_mock.lines[5] == "> raise AttributeError(){}".format(
|
||||
raise_suffix
|
||||
)
|
||||
assert tw.lines[6] == "E AttributeError"
|
||||
assert tw.lines[7] == ""
|
||||
line = tw.get_write_msg(8)
|
||||
assert tw_mock.lines[6] == "E AttributeError"
|
||||
assert tw_mock.lines[7] == ""
|
||||
line = tw_mock.get_write_msg(8)
|
||||
assert line.endswith("mod.py")
|
||||
assert tw.lines[9] == ":6: AttributeError"
|
||||
assert len(tw.lines) == 10
|
||||
assert tw_mock.lines[9] == ":6: AttributeError"
|
||||
assert len(tw_mock.lines) == 10
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"reason, description",
|
||||
|
@ -1304,7 +1274,7 @@ raise ValueError()
|
|||
]
|
||||
)
|
||||
|
||||
def test_exc_chain_repr_cycle(self, importasmod):
|
||||
def test_exc_chain_repr_cycle(self, importasmod, tw_mock):
|
||||
mod = importasmod(
|
||||
"""
|
||||
class Err(Exception):
|
||||
|
@ -1325,9 +1295,8 @@ raise ValueError()
|
|||
)
|
||||
excinfo = pytest.raises(ZeroDivisionError, mod.unreraise)
|
||||
r = excinfo.getrepr(style="short")
|
||||
tw = TWMock()
|
||||
r.toterminal(tw)
|
||||
out = "\n".join(line for line in tw.lines if isinstance(line, str))
|
||||
r.toterminal(tw_mock)
|
||||
out = "\n".join(line for line in tw_mock.lines if isinstance(line, str))
|
||||
expected_out = textwrap.dedent(
|
||||
"""\
|
||||
:13: in unreraise
|
||||
|
|
|
@ -55,3 +55,36 @@ def pytest_collection_modifyitems(config, items):
|
|||
items[:] = fast_items + neutral_items + slow_items + slowest_items
|
||||
|
||||
yield
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tw_mock():
|
||||
"""Returns a mock terminal writer"""
|
||||
|
||||
class TWMock:
|
||||
WRITE = object()
|
||||
|
||||
def __init__(self):
|
||||
self.lines = []
|
||||
self.is_writing = False
|
||||
|
||||
def sep(self, sep, line=None):
|
||||
self.lines.append((sep, line))
|
||||
|
||||
def write(self, msg, **kw):
|
||||
self.lines.append((TWMock.WRITE, msg))
|
||||
|
||||
def line(self, line, **kw):
|
||||
self.lines.append(line)
|
||||
|
||||
def markup(self, text, **kw):
|
||||
return text
|
||||
|
||||
def get_write_msg(self, idx):
|
||||
flag, msg = self.lines[idx]
|
||||
assert flag == TWMock.WRITE
|
||||
return msg
|
||||
|
||||
fullwidth = 80
|
||||
|
||||
return TWMock()
|
||||
|
|
|
@ -449,7 +449,8 @@ class TestFillFixtures:
|
|||
"*ERROR at setup of test_lookup_error*",
|
||||
" def test_lookup_error(unknown):*",
|
||||
"E fixture 'unknown' not found",
|
||||
"> available fixtures:*a_fixture,*b_fixture,*c_fixture,*d_fixture*monkeypatch,*", # sorted
|
||||
"> available fixtures:*a_fixture,*b_fixture,*c_fixture,*d_fixture*monkeypatch,*",
|
||||
# sorted
|
||||
"> use 'py*test --fixtures *' for help on them.",
|
||||
"*1 error*",
|
||||
]
|
||||
|
@ -3945,6 +3946,38 @@ class TestScopeOrdering:
|
|||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=2)
|
||||
|
||||
def test_class_fixture_self_instance(self, testdir):
|
||||
"""Check that plugin classes which implement fixtures receive the plugin instance
|
||||
as self (see #2270).
|
||||
"""
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
def pytest_configure(config):
|
||||
config.pluginmanager.register(MyPlugin())
|
||||
|
||||
class MyPlugin():
|
||||
def __init__(self):
|
||||
self.arg = 1
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def myfix(self):
|
||||
assert isinstance(self, MyPlugin)
|
||||
return self.arg
|
||||
"""
|
||||
)
|
||||
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
class TestClass(object):
|
||||
def test_1(self, myfix):
|
||||
assert myfix == 1
|
||||
"""
|
||||
)
|
||||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=1)
|
||||
|
||||
|
||||
def test_call_fixture_function_error():
|
||||
"""Check if an error is raised if a fixture function is called directly (#4545)"""
|
||||
|
@ -4009,3 +4042,55 @@ def test_fixture_named_request(testdir):
|
|||
" *test_fixture_named_request.py:5",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_indirect_fixture_does_not_break_scope(testdir):
|
||||
"""Ensure that fixture scope is respected when using indirect fixtures (#570)"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
instantiated = []
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def fixture_1(request):
|
||||
instantiated.append(("fixture_1", request.param))
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def fixture_2(request):
|
||||
instantiated.append(("fixture_2", request.param))
|
||||
|
||||
|
||||
scenarios = [
|
||||
("A", "a1"),
|
||||
("A", "a2"),
|
||||
("B", "b1"),
|
||||
("B", "b2"),
|
||||
("C", "c1"),
|
||||
("C", "c2"),
|
||||
]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"fixture_1,fixture_2", scenarios, indirect=["fixture_1", "fixture_2"]
|
||||
)
|
||||
def test_create_fixtures(fixture_1, fixture_2):
|
||||
pass
|
||||
|
||||
|
||||
def test_check_fixture_instantiations():
|
||||
assert instantiated == [
|
||||
('fixture_1', 'A'),
|
||||
('fixture_2', 'a1'),
|
||||
('fixture_2', 'a2'),
|
||||
('fixture_1', 'B'),
|
||||
('fixture_2', 'b1'),
|
||||
('fixture_2', 'b2'),
|
||||
('fixture_1', 'C'),
|
||||
('fixture_2', 'c1'),
|
||||
('fixture_2', 'c2'),
|
||||
]
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.assert_outcomes(passed=7)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import os
|
||||
import textwrap
|
||||
from pathlib import Path
|
||||
|
||||
import py
|
||||
|
||||
|
@ -163,11 +165,12 @@ def test_setinitial_conftest_subdirs(testdir, name):
|
|||
subconftest = sub.ensure("conftest.py")
|
||||
conftest = PytestPluginManager()
|
||||
conftest_setinitial(conftest, [sub.dirpath()], confcutdir=testdir.tmpdir)
|
||||
key = Path(str(subconftest)).resolve()
|
||||
if name not in ("whatever", ".dotdir"):
|
||||
assert subconftest in conftest._conftestpath2mod
|
||||
assert key in conftest._conftestpath2mod
|
||||
assert len(conftest._conftestpath2mod) == 1
|
||||
else:
|
||||
assert subconftest not in conftest._conftestpath2mod
|
||||
assert key not in conftest._conftestpath2mod
|
||||
assert len(conftest._conftestpath2mod) == 0
|
||||
|
||||
|
||||
|
@ -275,6 +278,31 @@ def test_conftest_symlink_files(testdir):
|
|||
assert result.ret == ExitCode.OK
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
os.path.normcase("x") != os.path.normcase("X"),
|
||||
reason="only relevant for case insensitive file systems",
|
||||
)
|
||||
def test_conftest_badcase(testdir):
|
||||
"""Check conftest.py loading when directory casing is wrong (#5792)."""
|
||||
testdir.tmpdir.mkdir("JenkinsRoot").mkdir("test")
|
||||
source = {"setup.py": "", "test/__init__.py": "", "test/conftest.py": ""}
|
||||
testdir.makepyfile(**{"JenkinsRoot/%s" % k: v for k, v in source.items()})
|
||||
|
||||
testdir.tmpdir.join("jenkinsroot/test").chdir()
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == ExitCode.NO_TESTS_COLLECTED
|
||||
|
||||
|
||||
def test_conftest_uppercase(testdir):
|
||||
"""Check conftest.py whose qualified name contains uppercase characters (#5819)"""
|
||||
source = {"__init__.py": "", "Foo/conftest.py": "", "Foo/__init__.py": ""}
|
||||
testdir.makepyfile(**source)
|
||||
|
||||
testdir.tmpdir.chdir()
|
||||
result = testdir.runpytest()
|
||||
assert result.ret == ExitCode.NO_TESTS_COLLECTED
|
||||
|
||||
|
||||
def test_no_conftest(testdir):
|
||||
testdir.makeconftest("assert 0")
|
||||
result = testdir.runpytest("--noconftest")
|
||||
|
|
|
@ -853,7 +853,7 @@ class TestDebuggingBreakpoints:
|
|||
Test that supports breakpoint global marks on Python 3.7+ and not on
|
||||
CPython 3.5, 2.7
|
||||
"""
|
||||
if sys.version_info.major == 3 and sys.version_info.minor >= 7:
|
||||
if sys.version_info >= (3, 7):
|
||||
assert SUPPORTS_BREAKPOINT_BUILTIN is True
|
||||
if sys.version_info.major == 3 and sys.version_info.minor == 5:
|
||||
assert SUPPORTS_BREAKPOINT_BUILTIN is False
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import pytest
|
||||
from _pytest._code.code import ExceptionChainRepr
|
||||
from _pytest.pathlib import Path
|
||||
from _pytest.reports import CollectReport
|
||||
from _pytest.reports import TestReport
|
||||
|
@ -220,8 +221,8 @@ class TestReportSerialization:
|
|||
assert data["path1"] == str(testdir.tmpdir)
|
||||
assert data["path2"] == str(testdir.tmpdir)
|
||||
|
||||
def test_unserialization_failure(self, testdir):
|
||||
"""Check handling of failure during unserialization of report types."""
|
||||
def test_deserialization_failure(self, testdir):
|
||||
"""Check handling of failure during deserialization of report types."""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_a():
|
||||
|
@ -242,6 +243,75 @@ class TestReportSerialization:
|
|||
):
|
||||
TestReport._from_json(data)
|
||||
|
||||
@pytest.mark.parametrize("report_class", [TestReport, CollectReport])
|
||||
def test_chained_exceptions(self, testdir, tw_mock, report_class):
|
||||
"""Check serialization/deserialization of report objects containing chained exceptions (#5786)"""
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def foo():
|
||||
raise ValueError('value error')
|
||||
def test_a():
|
||||
try:
|
||||
foo()
|
||||
except ValueError as e:
|
||||
raise RuntimeError('runtime error') from e
|
||||
if {error_during_import}:
|
||||
test_a()
|
||||
""".format(
|
||||
error_during_import=report_class is CollectReport
|
||||
)
|
||||
)
|
||||
|
||||
reprec = testdir.inline_run()
|
||||
if report_class is TestReport:
|
||||
reports = reprec.getreports("pytest_runtest_logreport")
|
||||
# we have 3 reports: setup/call/teardown
|
||||
assert len(reports) == 3
|
||||
# get the call report
|
||||
report = reports[1]
|
||||
else:
|
||||
assert report_class is CollectReport
|
||||
# two collection reports: session and test file
|
||||
reports = reprec.getreports("pytest_collectreport")
|
||||
assert len(reports) == 2
|
||||
report = reports[1]
|
||||
|
||||
def check_longrepr(longrepr):
|
||||
"""Check the attributes of the given longrepr object according to the test file.
|
||||
|
||||
We can get away with testing both CollectReport and TestReport with this function because
|
||||
the longrepr objects are very similar.
|
||||
"""
|
||||
assert isinstance(longrepr, ExceptionChainRepr)
|
||||
assert longrepr.sections == [("title", "contents", "=")]
|
||||
assert len(longrepr.chain) == 2
|
||||
entry1, entry2 = longrepr.chain
|
||||
tb1, fileloc1, desc1 = entry1
|
||||
tb2, fileloc2, desc2 = entry2
|
||||
|
||||
assert "ValueError('value error')" in str(tb1)
|
||||
assert "RuntimeError('runtime error')" in str(tb2)
|
||||
|
||||
assert (
|
||||
desc1
|
||||
== "The above exception was the direct cause of the following exception:"
|
||||
)
|
||||
assert desc2 is None
|
||||
|
||||
assert report.failed
|
||||
assert len(report.sections) == 0
|
||||
report.longrepr.addsection("title", "contents", "=")
|
||||
check_longrepr(report.longrepr)
|
||||
|
||||
data = report._to_json()
|
||||
loaded_report = report_class._from_json(data)
|
||||
check_longrepr(loaded_report.longrepr)
|
||||
|
||||
# make sure we don't blow up on ``toterminal`` call; we don't test the actual output because it is very
|
||||
# brittle and hard to maintain, but we can assume it is correct because ``toterminal`` is already tested
|
||||
# elsewhere and we do check the contents of the longrepr object after loading it.
|
||||
loaded_report.longrepr.toterminal(tw_mock)
|
||||
|
||||
|
||||
class TestHooks:
|
||||
"""Test that the hooks are working correctly for plugins"""
|
||||
|
|
Loading…
Reference in New Issue