commit
917195ea8e
|
@ -112,11 +112,9 @@ matrix:
|
||||||
allow_failures:
|
allow_failures:
|
||||||
- python: '3.8-dev'
|
- python: '3.8-dev'
|
||||||
env: TOXENV=py38-xdist
|
env: TOXENV=py38-xdist
|
||||||
# temporary until pytest 4.6 is released
|
# Temporary (https://github.com/pytest-dev/pytest/pull/5334).
|
||||||
- env: TOXENV=py27-pluggymaster-xdist
|
- env: TOXENV=pypy3-xdist
|
||||||
python: '2.7'
|
python: 'pypy3'
|
||||||
- env: TOXENV=py37-pluggymaster-xdist
|
|
||||||
|
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
- |
|
- |
|
||||||
|
|
|
@ -18,6 +18,84 @@ with advance notice in the **Deprecations** section of releases.
|
||||||
|
|
||||||
.. towncrier release notes start
|
.. towncrier release notes start
|
||||||
|
|
||||||
|
pytest 4.6.0 (2019-05-31)
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Important
|
||||||
|
---------
|
||||||
|
|
||||||
|
The ``4.6.X`` series will be the last series to support **Python 2 and Python 3.4**.
|
||||||
|
|
||||||
|
For more details, see our `Python 2.7 and 3.4 support plan <https://docs.pytest.org/en/latest/py27-py34-deprecation.html>`__.
|
||||||
|
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
- `#4559 <https://github.com/pytest-dev/pytest/issues/4559>`_: Added the ``junit_log_passing_tests`` ini value which can be used to enable or disable logging of passing test output in the Junit XML file.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4956 <https://github.com/pytest-dev/pytest/issues/4956>`_: pytester's ``testdir.spawn`` uses ``tmpdir`` as HOME/USERPROFILE directory.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5062 <https://github.com/pytest-dev/pytest/issues/5062>`_: Unroll calls to ``all`` to full for-loops with assertion rewriting for better failure messages, especially when using Generator Expressions.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5063 <https://github.com/pytest-dev/pytest/issues/5063>`_: Switch from ``pkg_resources`` to ``importlib-metadata`` for entrypoint detection for improved performance and import time.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5091 <https://github.com/pytest-dev/pytest/issues/5091>`_: The output for ini options in ``--help`` has been improved.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5269 <https://github.com/pytest-dev/pytest/issues/5269>`_: ``pytest.importorskip`` includes the ``ImportError`` now in the default ``reason``.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5311 <https://github.com/pytest-dev/pytest/issues/5311>`_: Captured logs that are output for each failing test are formatted using the
|
||||||
|
ColoredLevelFormatter.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5312 <https://github.com/pytest-dev/pytest/issues/5312>`_: Improved formatting of multiline log messages in Python 3.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Bug Fixes
|
||||||
|
---------
|
||||||
|
|
||||||
|
- `#2064 <https://github.com/pytest-dev/pytest/issues/2064>`_: The debugging plugin imports the wrapped ``Pdb`` class (``--pdbcls``) on-demand now.
|
||||||
|
|
||||||
|
|
||||||
|
- `#4908 <https://github.com/pytest-dev/pytest/issues/4908>`_: The ``pytest_enter_pdb`` hook gets called with post-mortem (``--pdb``).
|
||||||
|
|
||||||
|
|
||||||
|
- `#5036 <https://github.com/pytest-dev/pytest/issues/5036>`_: Fix issue where fixtures dependent on other parametrized fixtures would be erroneously parametrized.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5256 <https://github.com/pytest-dev/pytest/issues/5256>`_: Handle internal error due to a lone surrogate unicode character not being representable in Jython.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5257 <https://github.com/pytest-dev/pytest/issues/5257>`_: Ensure that ``sys.stdout.mode`` does not include ``'b'`` as it is a text stream.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5278 <https://github.com/pytest-dev/pytest/issues/5278>`_: Pytest's internal python plugin can be disabled using ``-p no:python`` again.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5286 <https://github.com/pytest-dev/pytest/issues/5286>`_: Fix issue with ``disable_test_id_escaping_and_forfeit_all_rights_to_community_support`` option not working when using a list of test IDs in parametrized tests.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5330 <https://github.com/pytest-dev/pytest/issues/5330>`_: Show the test module being collected when emitting ``PytestCollectionWarning`` messages for
|
||||||
|
test classes with ``__init__`` and ``__new__`` methods to make it easier to pin down the problem.
|
||||||
|
|
||||||
|
|
||||||
|
- `#5333 <https://github.com/pytest-dev/pytest/issues/5333>`_: Fix regression in 4.5.0 with ``--lf`` not re-running all tests with known failures from non-selected tests.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Improved Documentation
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
- `#5250 <https://github.com/pytest-dev/pytest/issues/5250>`_: Expand docs on use of ``setenv`` and ``delenv`` with ``monkeypatch``.
|
||||||
|
|
||||||
|
|
||||||
pytest 4.5.0 (2019-05-11)
|
pytest 4.5.0 (2019-05-11)
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
The debugging plugin imports the wrapped ``Pdb`` class (``--pdbcls``) on-demand now.
|
|
|
@ -1 +0,0 @@
|
||||||
The ``pytest_enter_pdb`` hook gets called with post-mortem (``--pdb``).
|
|
|
@ -1 +0,0 @@
|
||||||
Fix issue where fixtures dependent on other parametrized fixtures would be erroneously parametrized.
|
|
|
@ -1 +0,0 @@
|
||||||
Expand docs on use of ``setenv`` and ``delenv`` with ``monkeypatch``.
|
|
|
@ -1 +0,0 @@
|
||||||
Handle internal error due to a lone surrogate unicode character not being representable in Jython.
|
|
|
@ -1 +0,0 @@
|
||||||
Ensure that ``sys.stdout.mode`` does not include ``'b'`` as it is a text stream.
|
|
|
@ -1 +0,0 @@
|
||||||
Pytest's internal python plugin can be disabled using ``-p no:python`` again.
|
|
|
@ -1 +0,0 @@
|
||||||
Fix issue with ``disable_test_id_escaping_and_forfeit_all_rights_to_community_support`` option doesn't work when using a list of test IDs in parametrized tests.
|
|
|
@ -1,2 +0,0 @@
|
||||||
Show the test module being collected when emitting ``PytestCollectionWarning`` messages for
|
|
||||||
test classes with ``__init__`` and ``__new__`` methods to make it easier to pin down the problem.
|
|
|
@ -1 +0,0 @@
|
||||||
Fix regression with ``--lf`` not re-running all tests with known failures from non-selected tests.
|
|
|
@ -6,6 +6,7 @@ Release announcements
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
|
||||||
|
release-4.6.0
|
||||||
release-4.5.0
|
release-4.5.0
|
||||||
release-4.4.2
|
release-4.4.2
|
||||||
release-4.4.1
|
release-4.4.1
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
pytest-4.6.0
|
||||||
|
=======================================
|
||||||
|
|
||||||
|
The pytest team is proud to announce the 4.6.0 release!
|
||||||
|
|
||||||
|
pytest is a mature Python testing tool with more than a 2000 tests
|
||||||
|
against itself, passing on many different interpreters and platforms.
|
||||||
|
|
||||||
|
This release contains a number of bugs fixes and improvements, so users are encouraged
|
||||||
|
to take a look at the CHANGELOG:
|
||||||
|
|
||||||
|
https://docs.pytest.org/en/latest/changelog.html
|
||||||
|
|
||||||
|
For complete documentation, please visit:
|
||||||
|
|
||||||
|
https://docs.pytest.org/en/latest/
|
||||||
|
|
||||||
|
As usual, you can upgrade from pypi via:
|
||||||
|
|
||||||
|
pip install -U pytest
|
||||||
|
|
||||||
|
Thanks to all who contributed to this release, among them:
|
||||||
|
|
||||||
|
* Akiomi Kamakura
|
||||||
|
* Anthony Sottile
|
||||||
|
* Bruno Oliveira
|
||||||
|
* Daniel Hahler
|
||||||
|
* David Röthlisberger
|
||||||
|
* Evan Kepner
|
||||||
|
* Jeffrey Rackauckas
|
||||||
|
* MyComputer
|
||||||
|
* Nikita Krokosh
|
||||||
|
* Raul Tambre
|
||||||
|
* Thomas Hisch
|
||||||
|
* Tim Hoffmann
|
||||||
|
* Tomer Keren
|
||||||
|
* Victor Maryama
|
||||||
|
* danielx123
|
||||||
|
* oleg-yegorov
|
||||||
|
|
||||||
|
|
||||||
|
Happy testing,
|
||||||
|
The Pytest Development Team
|
|
@ -436,7 +436,7 @@ Running it results in some skips if we don't have all the python interpreters in
|
||||||
. $ pytest -rs -q multipython.py
|
. $ pytest -rs -q multipython.py
|
||||||
...sss...sssssssss...sss... [100%]
|
...sss...sssssssss...sss... [100%]
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:30: 'python3.4' not found
|
SKIPPED [15] $REGENDOC_TMPDIR/CWD/multipython.py:31: 'python3.4' not found
|
||||||
12 passed, 15 skipped in 0.12 seconds
|
12 passed, 15 skipped in 0.12 seconds
|
||||||
|
|
||||||
Indirect parametrization of optional implementations/imports
|
Indirect parametrization of optional implementations/imports
|
||||||
|
@ -494,7 +494,7 @@ If you run this with reporting for skips enabled:
|
||||||
test_module.py .s [100%]
|
test_module.py .s [100%]
|
||||||
|
|
||||||
========================= short test summary info ==========================
|
========================= short test summary info ==========================
|
||||||
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2'
|
SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2': No module named 'opt2'
|
||||||
=================== 1 passed, 1 skipped in 0.12 seconds ====================
|
=================== 1 passed, 1 skipped in 0.12 seconds ====================
|
||||||
|
|
||||||
You'll see that we don't have an ``opt2`` module and thus the second test run
|
You'll see that we don't have an ``opt2`` module and thus the second test run
|
||||||
|
|
|
@ -26,7 +26,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> assert param1 * 2 < param2
|
> assert param1 * 2 < param2
|
||||||
E assert (3 * 2) < 6
|
E assert (3 * 2) < 6
|
||||||
|
|
||||||
failure_demo.py:20: AssertionError
|
failure_demo.py:21: AssertionError
|
||||||
_________________________ TestFailing.test_simple __________________________
|
_________________________ TestFailing.test_simple __________________________
|
||||||
|
|
||||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||||
|
@ -43,7 +43,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + where 42 = <function TestFailing.test_simple.<locals>.f at 0xdeadbeef>()
|
E + where 42 = <function TestFailing.test_simple.<locals>.f at 0xdeadbeef>()
|
||||||
E + and 43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>()
|
E + and 43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>()
|
||||||
|
|
||||||
failure_demo.py:31: AssertionError
|
failure_demo.py:32: AssertionError
|
||||||
____________________ TestFailing.test_simple_multiline _____________________
|
____________________ TestFailing.test_simple_multiline _____________________
|
||||||
|
|
||||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||||
|
@ -51,7 +51,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
def test_simple_multiline(self):
|
def test_simple_multiline(self):
|
||||||
> otherfunc_multi(42, 6 * 9)
|
> otherfunc_multi(42, 6 * 9)
|
||||||
|
|
||||||
failure_demo.py:34:
|
failure_demo.py:35:
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||||
|
|
||||||
a = 42, b = 54
|
a = 42, b = 54
|
||||||
|
@ -60,7 +60,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> assert a == b
|
> assert a == b
|
||||||
E assert 42 == 54
|
E assert 42 == 54
|
||||||
|
|
||||||
failure_demo.py:15: AssertionError
|
failure_demo.py:16: AssertionError
|
||||||
___________________________ TestFailing.test_not ___________________________
|
___________________________ TestFailing.test_not ___________________________
|
||||||
|
|
||||||
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
self = <failure_demo.TestFailing object at 0xdeadbeef>
|
||||||
|
@ -73,7 +73,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert not 42
|
E assert not 42
|
||||||
E + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>()
|
E + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>()
|
||||||
|
|
||||||
failure_demo.py:40: AssertionError
|
failure_demo.py:41: AssertionError
|
||||||
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -84,7 +84,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E - spam
|
E - spam
|
||||||
E + eggs
|
E + eggs
|
||||||
|
|
||||||
failure_demo.py:45: AssertionError
|
failure_demo.py:46: AssertionError
|
||||||
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
|
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -97,7 +97,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + foo 2 bar
|
E + foo 2 bar
|
||||||
E ? ^
|
E ? ^
|
||||||
|
|
||||||
failure_demo.py:48: AssertionError
|
failure_demo.py:49: AssertionError
|
||||||
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
|
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -110,7 +110,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + eggs
|
E + eggs
|
||||||
E bar
|
E bar
|
||||||
|
|
||||||
failure_demo.py:51: AssertionError
|
failure_demo.py:52: AssertionError
|
||||||
______________ TestSpecialisedExplanations.test_eq_long_text _______________
|
______________ TestSpecialisedExplanations.test_eq_long_text _______________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -127,7 +127,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + 1111111111b222222222
|
E + 1111111111b222222222
|
||||||
E ? ^
|
E ? ^
|
||||||
|
|
||||||
failure_demo.py:56: AssertionError
|
failure_demo.py:57: AssertionError
|
||||||
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
|
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -147,7 +147,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E
|
E
|
||||||
E ...Full output truncated (7 lines hidden), use '-vv' to show
|
E ...Full output truncated (7 lines hidden), use '-vv' to show
|
||||||
|
|
||||||
failure_demo.py:61: AssertionError
|
failure_demo.py:62: AssertionError
|
||||||
_________________ TestSpecialisedExplanations.test_eq_list _________________
|
_________________ TestSpecialisedExplanations.test_eq_list _________________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -158,7 +158,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E At index 2 diff: 2 != 3
|
E At index 2 diff: 2 != 3
|
||||||
E Use -v to get the full diff
|
E Use -v to get the full diff
|
||||||
|
|
||||||
failure_demo.py:64: AssertionError
|
failure_demo.py:65: AssertionError
|
||||||
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -171,7 +171,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E At index 100 diff: 1 != 2
|
E At index 100 diff: 1 != 2
|
||||||
E Use -v to get the full diff
|
E Use -v to get the full diff
|
||||||
|
|
||||||
failure_demo.py:69: AssertionError
|
failure_demo.py:70: AssertionError
|
||||||
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -189,7 +189,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E
|
E
|
||||||
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||||
|
|
||||||
failure_demo.py:72: AssertionError
|
failure_demo.py:73: AssertionError
|
||||||
_________________ TestSpecialisedExplanations.test_eq_set __________________
|
_________________ TestSpecialisedExplanations.test_eq_set __________________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -207,7 +207,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E
|
E
|
||||||
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||||
|
|
||||||
failure_demo.py:75: AssertionError
|
failure_demo.py:76: AssertionError
|
||||||
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
|
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -218,7 +218,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E Right contains one more item: 3
|
E Right contains one more item: 3
|
||||||
E Use -v to get the full diff
|
E Use -v to get the full diff
|
||||||
|
|
||||||
failure_demo.py:78: AssertionError
|
failure_demo.py:79: AssertionError
|
||||||
_________________ TestSpecialisedExplanations.test_in_list _________________
|
_________________ TestSpecialisedExplanations.test_in_list _________________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -227,7 +227,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> assert 1 in [0, 2, 3, 4, 5]
|
> assert 1 in [0, 2, 3, 4, 5]
|
||||||
E assert 1 in [0, 2, 3, 4, 5]
|
E assert 1 in [0, 2, 3, 4, 5]
|
||||||
|
|
||||||
failure_demo.py:81: AssertionError
|
failure_demo.py:82: AssertionError
|
||||||
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
|
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -246,7 +246,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E
|
E
|
||||||
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
E ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||||
|
|
||||||
failure_demo.py:85: AssertionError
|
failure_demo.py:86: AssertionError
|
||||||
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
|
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -259,7 +259,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E single foo line
|
E single foo line
|
||||||
E ? +++
|
E ? +++
|
||||||
|
|
||||||
failure_demo.py:89: AssertionError
|
failure_demo.py:90: AssertionError
|
||||||
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
|
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -272,7 +272,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
||||||
E ? +++
|
E ? +++
|
||||||
|
|
||||||
failure_demo.py:93: AssertionError
|
failure_demo.py:94: AssertionError
|
||||||
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
|
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -285,7 +285,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
|
||||||
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
failure_demo.py:97: AssertionError
|
failure_demo.py:98: AssertionError
|
||||||
______________ TestSpecialisedExplanations.test_eq_dataclass _______________
|
______________ TestSpecialisedExplanations.test_eq_dataclass _______________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -306,7 +306,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E Differing attributes:
|
E Differing attributes:
|
||||||
E b: 'b' != 'c'
|
E b: 'b' != 'c'
|
||||||
|
|
||||||
failure_demo.py:109: AssertionError
|
failure_demo.py:110: AssertionError
|
||||||
________________ TestSpecialisedExplanations.test_eq_attrs _________________
|
________________ TestSpecialisedExplanations.test_eq_attrs _________________
|
||||||
|
|
||||||
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
|
||||||
|
@ -327,7 +327,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E Differing attributes:
|
E Differing attributes:
|
||||||
E b: 'b' != 'c'
|
E b: 'b' != 'c'
|
||||||
|
|
||||||
failure_demo.py:121: AssertionError
|
failure_demo.py:122: AssertionError
|
||||||
______________________________ test_attribute ______________________________
|
______________________________ test_attribute ______________________________
|
||||||
|
|
||||||
def test_attribute():
|
def test_attribute():
|
||||||
|
@ -339,7 +339,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert 1 == 2
|
E assert 1 == 2
|
||||||
E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b
|
E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b
|
||||||
|
|
||||||
failure_demo.py:129: AssertionError
|
failure_demo.py:130: AssertionError
|
||||||
_________________________ test_attribute_instance __________________________
|
_________________________ test_attribute_instance __________________________
|
||||||
|
|
||||||
def test_attribute_instance():
|
def test_attribute_instance():
|
||||||
|
@ -351,7 +351,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef>.b
|
E + where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef>.b
|
||||||
E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
|
E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
|
||||||
|
|
||||||
failure_demo.py:136: AssertionError
|
failure_demo.py:137: AssertionError
|
||||||
__________________________ test_attribute_failure __________________________
|
__________________________ test_attribute_failure __________________________
|
||||||
|
|
||||||
def test_attribute_failure():
|
def test_attribute_failure():
|
||||||
|
@ -364,7 +364,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
i = Foo()
|
i = Foo()
|
||||||
> assert i.b == 2
|
> assert i.b == 2
|
||||||
|
|
||||||
failure_demo.py:147:
|
failure_demo.py:148:
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||||
|
|
||||||
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
|
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
|
||||||
|
@ -373,7 +373,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> raise Exception("Failed to get attrib")
|
> raise Exception("Failed to get attrib")
|
||||||
E Exception: Failed to get attrib
|
E Exception: Failed to get attrib
|
||||||
|
|
||||||
failure_demo.py:142: Exception
|
failure_demo.py:143: Exception
|
||||||
_________________________ test_attribute_multiple __________________________
|
_________________________ test_attribute_multiple __________________________
|
||||||
|
|
||||||
def test_attribute_multiple():
|
def test_attribute_multiple():
|
||||||
|
@ -390,7 +390,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b
|
E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b
|
||||||
E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
|
E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
|
||||||
|
|
||||||
failure_demo.py:157: AssertionError
|
failure_demo.py:158: AssertionError
|
||||||
__________________________ TestRaises.test_raises __________________________
|
__________________________ TestRaises.test_raises __________________________
|
||||||
|
|
||||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||||
|
@ -400,7 +400,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> raises(TypeError, int, s)
|
> raises(TypeError, int, s)
|
||||||
E ValueError: invalid literal for int() with base 10: 'qwe'
|
E ValueError: invalid literal for int() with base 10: 'qwe'
|
||||||
|
|
||||||
failure_demo.py:167: ValueError
|
failure_demo.py:168: ValueError
|
||||||
______________________ TestRaises.test_raises_doesnt _______________________
|
______________________ TestRaises.test_raises_doesnt _______________________
|
||||||
|
|
||||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||||
|
@ -409,7 +409,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> raises(IOError, int, "3")
|
> raises(IOError, int, "3")
|
||||||
E Failed: DID NOT RAISE <class 'OSError'>
|
E Failed: DID NOT RAISE <class 'OSError'>
|
||||||
|
|
||||||
failure_demo.py:170: Failed
|
failure_demo.py:171: Failed
|
||||||
__________________________ TestRaises.test_raise ___________________________
|
__________________________ TestRaises.test_raise ___________________________
|
||||||
|
|
||||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||||
|
@ -418,7 +418,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> raise ValueError("demo error")
|
> raise ValueError("demo error")
|
||||||
E ValueError: demo error
|
E ValueError: demo error
|
||||||
|
|
||||||
failure_demo.py:173: ValueError
|
failure_demo.py:174: ValueError
|
||||||
________________________ TestRaises.test_tupleerror ________________________
|
________________________ TestRaises.test_tupleerror ________________________
|
||||||
|
|
||||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||||
|
@ -427,7 +427,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> a, b = [1] # NOQA
|
> a, b = [1] # NOQA
|
||||||
E ValueError: not enough values to unpack (expected 2, got 1)
|
E ValueError: not enough values to unpack (expected 2, got 1)
|
||||||
|
|
||||||
failure_demo.py:176: ValueError
|
failure_demo.py:177: ValueError
|
||||||
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
|
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
|
||||||
|
|
||||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||||
|
@ -438,7 +438,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> a, b = items.pop()
|
> a, b = items.pop()
|
||||||
E TypeError: 'int' object is not iterable
|
E TypeError: 'int' object is not iterable
|
||||||
|
|
||||||
failure_demo.py:181: TypeError
|
failure_demo.py:182: TypeError
|
||||||
--------------------------- Captured stdout call ---------------------------
|
--------------------------- Captured stdout call ---------------------------
|
||||||
items is [1, 2, 3]
|
items is [1, 2, 3]
|
||||||
________________________ TestRaises.test_some_error ________________________
|
________________________ TestRaises.test_some_error ________________________
|
||||||
|
@ -449,7 +449,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> if namenotexi: # NOQA
|
> if namenotexi: # NOQA
|
||||||
E NameError: name 'namenotexi' is not defined
|
E NameError: name 'namenotexi' is not defined
|
||||||
|
|
||||||
failure_demo.py:184: NameError
|
failure_demo.py:185: NameError
|
||||||
____________________ test_dynamic_compile_shows_nicely _____________________
|
____________________ test_dynamic_compile_shows_nicely _____________________
|
||||||
|
|
||||||
def test_dynamic_compile_shows_nicely():
|
def test_dynamic_compile_shows_nicely():
|
||||||
|
@ -464,14 +464,14 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
sys.modules[name] = module
|
sys.modules[name] = module
|
||||||
> module.foo()
|
> module.foo()
|
||||||
|
|
||||||
failure_demo.py:202:
|
failure_demo.py:203:
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||||
|
|
||||||
def foo():
|
def foo():
|
||||||
> assert 1 == 0
|
> assert 1 == 0
|
||||||
E AssertionError
|
E AssertionError
|
||||||
|
|
||||||
<0-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:199>:2: AssertionError
|
<0-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:200>:2: AssertionError
|
||||||
____________________ TestMoreErrors.test_complex_error _____________________
|
____________________ TestMoreErrors.test_complex_error _____________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -485,9 +485,9 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
|
|
||||||
> somefunc(f(), g())
|
> somefunc(f(), g())
|
||||||
|
|
||||||
failure_demo.py:213:
|
failure_demo.py:214:
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||||
failure_demo.py:11: in somefunc
|
failure_demo.py:12: in somefunc
|
||||||
otherfunc(x, y)
|
otherfunc(x, y)
|
||||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||||
|
|
||||||
|
@ -497,7 +497,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> assert a == b
|
> assert a == b
|
||||||
E assert 44 == 43
|
E assert 44 == 43
|
||||||
|
|
||||||
failure_demo.py:7: AssertionError
|
failure_demo.py:8: AssertionError
|
||||||
___________________ TestMoreErrors.test_z1_unpack_error ____________________
|
___________________ TestMoreErrors.test_z1_unpack_error ____________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -507,7 +507,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> a, b = items
|
> a, b = items
|
||||||
E ValueError: not enough values to unpack (expected 2, got 0)
|
E ValueError: not enough values to unpack (expected 2, got 0)
|
||||||
|
|
||||||
failure_demo.py:217: ValueError
|
failure_demo.py:218: ValueError
|
||||||
____________________ TestMoreErrors.test_z2_type_error _____________________
|
____________________ TestMoreErrors.test_z2_type_error _____________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -517,7 +517,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> a, b = items
|
> a, b = items
|
||||||
E TypeError: 'int' object is not iterable
|
E TypeError: 'int' object is not iterable
|
||||||
|
|
||||||
failure_demo.py:221: TypeError
|
failure_demo.py:222: TypeError
|
||||||
______________________ TestMoreErrors.test_startswith ______________________
|
______________________ TestMoreErrors.test_startswith ______________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -530,7 +530,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + where False = <built-in method startswith of str object at 0xdeadbeef>('456')
|
E + where False = <built-in method startswith of str object at 0xdeadbeef>('456')
|
||||||
E + where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith
|
E + where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith
|
||||||
|
|
||||||
failure_demo.py:226: AssertionError
|
failure_demo.py:227: AssertionError
|
||||||
__________________ TestMoreErrors.test_startswith_nested ___________________
|
__________________ TestMoreErrors.test_startswith_nested ___________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -549,7 +549,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef>()
|
E + where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef>()
|
||||||
E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>()
|
E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>()
|
||||||
|
|
||||||
failure_demo.py:235: AssertionError
|
failure_demo.py:236: AssertionError
|
||||||
_____________________ TestMoreErrors.test_global_func ______________________
|
_____________________ TestMoreErrors.test_global_func ______________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -560,7 +560,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E + where False = isinstance(43, float)
|
E + where False = isinstance(43, float)
|
||||||
E + where 43 = globf(42)
|
E + where 43 = globf(42)
|
||||||
|
|
||||||
failure_demo.py:238: AssertionError
|
failure_demo.py:239: AssertionError
|
||||||
_______________________ TestMoreErrors.test_instance _______________________
|
_______________________ TestMoreErrors.test_instance _______________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -571,7 +571,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert 42 != 42
|
E assert 42 != 42
|
||||||
E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
|
E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
|
||||||
|
|
||||||
failure_demo.py:242: AssertionError
|
failure_demo.py:243: AssertionError
|
||||||
_______________________ TestMoreErrors.test_compare ________________________
|
_______________________ TestMoreErrors.test_compare ________________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -581,7 +581,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert 11 < 5
|
E assert 11 < 5
|
||||||
E + where 11 = globf(10)
|
E + where 11 = globf(10)
|
||||||
|
|
||||||
failure_demo.py:245: AssertionError
|
failure_demo.py:246: AssertionError
|
||||||
_____________________ TestMoreErrors.test_try_finally ______________________
|
_____________________ TestMoreErrors.test_try_finally ______________________
|
||||||
|
|
||||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||||
|
@ -592,7 +592,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
> assert x == 0
|
> assert x == 0
|
||||||
E assert 1 == 0
|
E assert 1 == 0
|
||||||
|
|
||||||
failure_demo.py:250: AssertionError
|
failure_demo.py:251: AssertionError
|
||||||
___________________ TestCustomAssertMsg.test_single_line ___________________
|
___________________ TestCustomAssertMsg.test_single_line ___________________
|
||||||
|
|
||||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||||
|
@ -607,7 +607,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert 1 == 2
|
E assert 1 == 2
|
||||||
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a
|
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a
|
||||||
|
|
||||||
failure_demo.py:261: AssertionError
|
failure_demo.py:262: AssertionError
|
||||||
____________________ TestCustomAssertMsg.test_multiline ____________________
|
____________________ TestCustomAssertMsg.test_multiline ____________________
|
||||||
|
|
||||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||||
|
@ -626,7 +626,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert 1 == 2
|
E assert 1 == 2
|
||||||
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
|
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
|
||||||
|
|
||||||
failure_demo.py:268: AssertionError
|
failure_demo.py:269: AssertionError
|
||||||
___________________ TestCustomAssertMsg.test_custom_repr ___________________
|
___________________ TestCustomAssertMsg.test_custom_repr ___________________
|
||||||
|
|
||||||
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
|
||||||
|
@ -648,5 +648,5 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
||||||
E assert 1 == 2
|
E assert 1 == 2
|
||||||
E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
|
E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
|
||||||
|
|
||||||
failure_demo.py:281: AssertionError
|
failure_demo.py:282: AssertionError
|
||||||
======================== 44 failed in 0.12 seconds =========================
|
======================== 44 failed in 0.12 seconds =========================
|
||||||
|
|
|
@ -1087,6 +1087,22 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||||
This tells pytest to ignore deprecation warnings and turn all other warnings
|
This tells pytest to ignore deprecation warnings and turn all other warnings
|
||||||
into errors. For more information please refer to :ref:`warnings`.
|
into errors. For more information please refer to :ref:`warnings`.
|
||||||
|
|
||||||
|
|
||||||
|
.. confval:: junit_duration_report
|
||||||
|
|
||||||
|
.. versionadded:: 4.1
|
||||||
|
|
||||||
|
Configures how durations are recorded into the JUnit XML report:
|
||||||
|
|
||||||
|
* ``total`` (the default): duration times reported include setup, call, and teardown times.
|
||||||
|
* ``call``: duration times reported include only call times, excluding setup and teardown.
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
junit_duration_report = call
|
||||||
|
|
||||||
|
|
||||||
.. confval:: junit_family
|
.. confval:: junit_family
|
||||||
|
|
||||||
.. versionadded:: 4.2
|
.. versionadded:: 4.2
|
||||||
|
@ -1102,10 +1118,35 @@ passed multiple times. The expected format is ``name=value``. For example::
|
||||||
[pytest]
|
[pytest]
|
||||||
junit_family = xunit2
|
junit_family = xunit2
|
||||||
|
|
||||||
|
|
||||||
|
.. confval:: junit_logging
|
||||||
|
|
||||||
|
.. versionadded:: 3.5
|
||||||
|
|
||||||
|
Configures if stdout/stderr should be written to the JUnit XML file. Valid values are
|
||||||
|
``system-out``, ``system-err``, and ``no`` (the default).
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
junit_logging = system-out
|
||||||
|
|
||||||
|
|
||||||
|
.. confval:: junit_log_passing_tests
|
||||||
|
|
||||||
|
.. versionadded:: 4.6
|
||||||
|
|
||||||
|
If ``junit_logging != "no"``, configures if the captured output should be written
|
||||||
|
to the JUnit XML file for **passing** tests. Default is ``True``.
|
||||||
|
|
||||||
|
.. code-block:: ini
|
||||||
|
|
||||||
|
[pytest]
|
||||||
|
junit_log_passing_tests = False
|
||||||
|
|
||||||
|
|
||||||
.. confval:: junit_suite_name
|
.. confval:: junit_suite_name
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
To set the name of the root test suite xml item, you can configure the ``junit_suite_name`` option in your config file:
|
To set the name of the root test suite xml item, you can configure the ``junit_suite_name`` option in your config file:
|
||||||
|
|
||||||
.. code-block:: ini
|
.. code-block:: ini
|
||||||
|
|
|
@ -400,7 +400,7 @@ defines an ``__init__`` constructor, as this prevents the class from being insta
|
||||||
|
|
||||||
============================= warnings summary =============================
|
============================= warnings summary =============================
|
||||||
test_pytest_warnings.py:1
|
test_pytest_warnings.py:1
|
||||||
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestCollectionWarning: cannot collect test class 'Test' because it has a __init__ constructor
|
$REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestCollectionWarning: cannot collect test class 'Test' because it has a __init__ constructor (from: test_pytest_warnings.py)
|
||||||
class Test:
|
class Test:
|
||||||
|
|
||||||
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
-- Docs: https://docs.pytest.org/en/latest/warnings.html
|
||||||
|
|
5
setup.py
5
setup.py
|
@ -6,7 +6,7 @@ from setuptools import setup
|
||||||
INSTALL_REQUIRES = [
|
INSTALL_REQUIRES = [
|
||||||
"py>=1.5.0",
|
"py>=1.5.0",
|
||||||
"six>=1.10.0",
|
"six>=1.10.0",
|
||||||
"setuptools",
|
"packaging",
|
||||||
"attrs>=17.4.0",
|
"attrs>=17.4.0",
|
||||||
'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"',
|
'more-itertools>=4.0.0,<6.0.0;python_version<="2.7"',
|
||||||
'more-itertools>=4.0.0;python_version>"2.7"',
|
'more-itertools>=4.0.0;python_version>"2.7"',
|
||||||
|
@ -14,7 +14,8 @@ INSTALL_REQUIRES = [
|
||||||
'funcsigs>=1.0;python_version<"3.0"',
|
'funcsigs>=1.0;python_version<"3.0"',
|
||||||
'pathlib2>=2.2.0;python_version<"3.6"',
|
'pathlib2>=2.2.0;python_version<"3.6"',
|
||||||
'colorama;sys_platform=="win32"',
|
'colorama;sys_platform=="win32"',
|
||||||
"pluggy==0.11", # temporary until 4.6 is released
|
"pluggy>=0.12,<1.0",
|
||||||
|
"importlib-metadata>=0.12",
|
||||||
"wcwidth",
|
"wcwidth",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -68,7 +68,6 @@ class AssertionRewritingHook(object):
|
||||||
self.session = None
|
self.session = None
|
||||||
self.modules = {}
|
self.modules = {}
|
||||||
self._rewritten_names = set()
|
self._rewritten_names = set()
|
||||||
self._register_with_pkg_resources()
|
|
||||||
self._must_rewrite = set()
|
self._must_rewrite = set()
|
||||||
# flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
|
# flag to guard against trying to rewrite a pyc file while we are already writing another pyc file,
|
||||||
# which might result in infinite recursion (#3506)
|
# which might result in infinite recursion (#3506)
|
||||||
|
@ -319,24 +318,6 @@ class AssertionRewritingHook(object):
|
||||||
tp = desc[2]
|
tp = desc[2]
|
||||||
return tp == imp.PKG_DIRECTORY
|
return tp == imp.PKG_DIRECTORY
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _register_with_pkg_resources(cls):
|
|
||||||
"""
|
|
||||||
Ensure package resources can be loaded from this loader. May be called
|
|
||||||
multiple times, as the operation is idempotent.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
# access an attribute in case a deferred importer is present
|
|
||||||
pkg_resources.__name__
|
|
||||||
except ImportError:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Since pytest tests are always located in the file system, the
|
|
||||||
# DefaultProvider is appropriate.
|
|
||||||
pkg_resources.register_loader_type(cls, pkg_resources.DefaultProvider)
|
|
||||||
|
|
||||||
def get_data(self, pathname):
|
def get_data(self, pathname):
|
||||||
"""Optional PEP302 get_data API.
|
"""Optional PEP302 get_data API.
|
||||||
"""
|
"""
|
||||||
|
@ -972,6 +953,8 @@ warn_explicit(
|
||||||
"""
|
"""
|
||||||
visit `ast.Call` nodes on Python3.5 and after
|
visit `ast.Call` nodes on Python3.5 and after
|
||||||
"""
|
"""
|
||||||
|
if isinstance(call.func, ast.Name) and call.func.id == "all":
|
||||||
|
return self._visit_all(call)
|
||||||
new_func, func_expl = self.visit(call.func)
|
new_func, func_expl = self.visit(call.func)
|
||||||
arg_expls = []
|
arg_expls = []
|
||||||
new_args = []
|
new_args = []
|
||||||
|
@ -995,6 +978,27 @@ warn_explicit(
|
||||||
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
|
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, expl)
|
||||||
return res, outer_expl
|
return res, outer_expl
|
||||||
|
|
||||||
|
def _visit_all(self, call):
|
||||||
|
"""Special rewrite for the builtin all function, see #5062"""
|
||||||
|
if not isinstance(call.args[0], (ast.GeneratorExp, ast.ListComp)):
|
||||||
|
return
|
||||||
|
gen_exp = call.args[0]
|
||||||
|
assertion_module = ast.Module(
|
||||||
|
body=[ast.Assert(test=gen_exp.elt, lineno=1, msg="", col_offset=1)]
|
||||||
|
)
|
||||||
|
AssertionRewriter(module_path=None, config=None).run(assertion_module)
|
||||||
|
for_loop = ast.For(
|
||||||
|
iter=gen_exp.generators[0].iter,
|
||||||
|
target=gen_exp.generators[0].target,
|
||||||
|
body=assertion_module.body,
|
||||||
|
orelse=[],
|
||||||
|
)
|
||||||
|
self.statements.append(for_loop)
|
||||||
|
return (
|
||||||
|
ast.Num(n=1),
|
||||||
|
"",
|
||||||
|
) # Return an empty expression, all the asserts are in the for_loop
|
||||||
|
|
||||||
def visit_Starred(self, starred):
|
def visit_Starred(self, starred):
|
||||||
# From Python 3.5, a Starred node can appear in a function call
|
# From Python 3.5, a Starred node can appear in a function call
|
||||||
res, expl = self.visit(starred.value)
|
res, expl = self.visit(starred.value)
|
||||||
|
@ -1005,6 +1009,8 @@ warn_explicit(
|
||||||
"""
|
"""
|
||||||
visit `ast.Call nodes on 3.4 and below`
|
visit `ast.Call nodes on 3.4 and below`
|
||||||
"""
|
"""
|
||||||
|
if isinstance(call.func, ast.Name) and call.func.id == "all":
|
||||||
|
return self._visit_all(call)
|
||||||
new_func, func_expl = self.visit(call.func)
|
new_func, func_expl = self.visit(call.func)
|
||||||
arg_expls = []
|
arg_expls = []
|
||||||
new_args = []
|
new_args = []
|
||||||
|
|
|
@ -37,7 +37,6 @@ if _PY3:
|
||||||
else:
|
else:
|
||||||
from funcsigs import signature, Parameter as Parameter
|
from funcsigs import signature, Parameter as Parameter
|
||||||
|
|
||||||
NoneType = type(None)
|
|
||||||
NOTSET = object()
|
NOTSET = object()
|
||||||
|
|
||||||
PY35 = sys.version_info[:2] >= (3, 5)
|
PY35 = sys.version_info[:2] >= (3, 5)
|
||||||
|
|
|
@ -13,8 +13,10 @@ import sys
|
||||||
import types
|
import types
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
import importlib_metadata
|
||||||
import py
|
import py
|
||||||
import six
|
import six
|
||||||
|
from packaging.version import Version
|
||||||
from pluggy import HookimplMarker
|
from pluggy import HookimplMarker
|
||||||
from pluggy import HookspecMarker
|
from pluggy import HookspecMarker
|
||||||
from pluggy import PluginManager
|
from pluggy import PluginManager
|
||||||
|
@ -788,25 +790,17 @@ class Config(object):
|
||||||
modules or packages in the distribution package for
|
modules or packages in the distribution package for
|
||||||
all pytest plugins.
|
all pytest plugins.
|
||||||
"""
|
"""
|
||||||
import pkg_resources
|
|
||||||
|
|
||||||
self.pluginmanager.rewrite_hook = hook
|
self.pluginmanager.rewrite_hook = hook
|
||||||
|
|
||||||
if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
|
if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
|
||||||
# We don't autoload from setuptools entry points, no need to continue.
|
# We don't autoload from setuptools entry points, no need to continue.
|
||||||
return
|
return
|
||||||
|
|
||||||
# 'RECORD' available for plugins installed normally (pip install)
|
|
||||||
# 'SOURCES.txt' available for plugins installed in dev mode (pip install -e)
|
|
||||||
# for installed plugins 'SOURCES.txt' returns an empty list, and vice-versa
|
|
||||||
# so it shouldn't be an issue
|
|
||||||
metadata_files = "RECORD", "SOURCES.txt"
|
|
||||||
|
|
||||||
package_files = (
|
package_files = (
|
||||||
entry.split(",")[0]
|
str(file)
|
||||||
for entrypoint in pkg_resources.iter_entry_points("pytest11")
|
for dist in importlib_metadata.distributions()
|
||||||
for metadata in metadata_files
|
if any(ep.group == "pytest11" for ep in dist.entry_points)
|
||||||
for entry in entrypoint.dist._get_metadata(metadata)
|
for file in dist.files
|
||||||
)
|
)
|
||||||
|
|
||||||
for name in _iter_rewritable_modules(package_files):
|
for name in _iter_rewritable_modules(package_files):
|
||||||
|
@ -875,11 +869,10 @@ class Config(object):
|
||||||
|
|
||||||
def _checkversion(self):
|
def _checkversion(self):
|
||||||
import pytest
|
import pytest
|
||||||
from pkg_resources import parse_version
|
|
||||||
|
|
||||||
minver = self.inicfg.get("minversion", None)
|
minver = self.inicfg.get("minversion", None)
|
||||||
if minver:
|
if minver:
|
||||||
if parse_version(minver) > parse_version(pytest.__version__):
|
if Version(minver) > Version(pytest.__version__):
|
||||||
raise pytest.UsageError(
|
raise pytest.UsageError(
|
||||||
"%s:%d: requires pytest-%s, actual pytest-%s'"
|
"%s:%d: requires pytest-%s, actual pytest-%s'"
|
||||||
% (
|
% (
|
||||||
|
|
|
@ -81,6 +81,7 @@ class pytestPDB(object):
|
||||||
_config = None
|
_config = None
|
||||||
_saved = []
|
_saved = []
|
||||||
_recursive_debug = 0
|
_recursive_debug = 0
|
||||||
|
_wrapped_pdb_cls = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _is_capturing(cls, capman):
|
def _is_capturing(cls, capman):
|
||||||
|
@ -89,43 +90,138 @@ class pytestPDB(object):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _import_pdb_cls(cls):
|
def _import_pdb_cls(cls, capman):
|
||||||
if not cls._config:
|
if not cls._config:
|
||||||
# Happens when using pytest.set_trace outside of a test.
|
# Happens when using pytest.set_trace outside of a test.
|
||||||
return pdb.Pdb
|
return pdb.Pdb
|
||||||
|
|
||||||
pdb_cls = cls._config.getvalue("usepdb_cls")
|
usepdb_cls = cls._config.getvalue("usepdb_cls")
|
||||||
if not pdb_cls:
|
|
||||||
return pdb.Pdb
|
|
||||||
|
|
||||||
modname, classname = pdb_cls
|
if cls._wrapped_pdb_cls and cls._wrapped_pdb_cls[0] == usepdb_cls:
|
||||||
|
return cls._wrapped_pdb_cls[1]
|
||||||
|
|
||||||
try:
|
if usepdb_cls:
|
||||||
__import__(modname)
|
modname, classname = usepdb_cls
|
||||||
mod = sys.modules[modname]
|
|
||||||
|
|
||||||
# Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp).
|
try:
|
||||||
parts = classname.split(".")
|
__import__(modname)
|
||||||
pdb_cls = getattr(mod, parts[0])
|
mod = sys.modules[modname]
|
||||||
for part in parts[1:]:
|
|
||||||
pdb_cls = getattr(pdb_cls, part)
|
|
||||||
|
|
||||||
return pdb_cls
|
# Handle --pdbcls=pdb:pdb.Pdb (useful e.g. with pdbpp).
|
||||||
except Exception as exc:
|
parts = classname.split(".")
|
||||||
value = ":".join((modname, classname))
|
pdb_cls = getattr(mod, parts[0])
|
||||||
raise UsageError("--pdbcls: could not import {!r}: {}".format(value, exc))
|
for part in parts[1:]:
|
||||||
|
pdb_cls = getattr(pdb_cls, part)
|
||||||
|
except Exception as exc:
|
||||||
|
value = ":".join((modname, classname))
|
||||||
|
raise UsageError(
|
||||||
|
"--pdbcls: could not import {!r}: {}".format(value, exc)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
pdb_cls = pdb.Pdb
|
||||||
|
|
||||||
|
wrapped_cls = cls._get_pdb_wrapper_class(pdb_cls, capman)
|
||||||
|
cls._wrapped_pdb_cls = (usepdb_cls, wrapped_cls)
|
||||||
|
return wrapped_cls
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _init_pdb(cls, *args, **kwargs):
|
def _get_pdb_wrapper_class(cls, pdb_cls, capman):
|
||||||
|
import _pytest.config
|
||||||
|
|
||||||
|
class PytestPdbWrapper(pdb_cls, object):
|
||||||
|
_pytest_capman = capman
|
||||||
|
_continued = False
|
||||||
|
|
||||||
|
def do_debug(self, arg):
|
||||||
|
cls._recursive_debug += 1
|
||||||
|
ret = super(PytestPdbWrapper, self).do_debug(arg)
|
||||||
|
cls._recursive_debug -= 1
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def do_continue(self, arg):
|
||||||
|
ret = super(PytestPdbWrapper, self).do_continue(arg)
|
||||||
|
if cls._recursive_debug == 0:
|
||||||
|
tw = _pytest.config.create_terminal_writer(cls._config)
|
||||||
|
tw.line()
|
||||||
|
|
||||||
|
capman = self._pytest_capman
|
||||||
|
capturing = pytestPDB._is_capturing(capman)
|
||||||
|
if capturing:
|
||||||
|
if capturing == "global":
|
||||||
|
tw.sep(">", "PDB continue (IO-capturing resumed)")
|
||||||
|
else:
|
||||||
|
tw.sep(
|
||||||
|
">",
|
||||||
|
"PDB continue (IO-capturing resumed for %s)"
|
||||||
|
% capturing,
|
||||||
|
)
|
||||||
|
capman.resume()
|
||||||
|
else:
|
||||||
|
tw.sep(">", "PDB continue")
|
||||||
|
cls._pluginmanager.hook.pytest_leave_pdb(config=cls._config, pdb=self)
|
||||||
|
self._continued = True
|
||||||
|
return ret
|
||||||
|
|
||||||
|
do_c = do_cont = do_continue
|
||||||
|
|
||||||
|
def do_quit(self, arg):
|
||||||
|
"""Raise Exit outcome when quit command is used in pdb.
|
||||||
|
|
||||||
|
This is a bit of a hack - it would be better if BdbQuit
|
||||||
|
could be handled, but this would require to wrap the
|
||||||
|
whole pytest run, and adjust the report etc.
|
||||||
|
"""
|
||||||
|
ret = super(PytestPdbWrapper, self).do_quit(arg)
|
||||||
|
|
||||||
|
if cls._recursive_debug == 0:
|
||||||
|
outcomes.exit("Quitting debugger")
|
||||||
|
|
||||||
|
return ret
|
||||||
|
|
||||||
|
do_q = do_quit
|
||||||
|
do_exit = do_quit
|
||||||
|
|
||||||
|
def setup(self, f, tb):
|
||||||
|
"""Suspend on setup().
|
||||||
|
|
||||||
|
Needed after do_continue resumed, and entering another
|
||||||
|
breakpoint again.
|
||||||
|
"""
|
||||||
|
ret = super(PytestPdbWrapper, self).setup(f, tb)
|
||||||
|
if not ret and self._continued:
|
||||||
|
# pdb.setup() returns True if the command wants to exit
|
||||||
|
# from the interaction: do not suspend capturing then.
|
||||||
|
if self._pytest_capman:
|
||||||
|
self._pytest_capman.suspend_global_capture(in_=True)
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def get_stack(self, f, t):
|
||||||
|
stack, i = super(PytestPdbWrapper, self).get_stack(f, t)
|
||||||
|
if f is None:
|
||||||
|
# Find last non-hidden frame.
|
||||||
|
i = max(0, len(stack) - 1)
|
||||||
|
while i and stack[i][0].f_locals.get("__tracebackhide__", False):
|
||||||
|
i -= 1
|
||||||
|
return stack, i
|
||||||
|
|
||||||
|
return PytestPdbWrapper
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _init_pdb(cls, method, *args, **kwargs):
|
||||||
""" Initialize PDB debugging, dropping any IO capturing. """
|
""" Initialize PDB debugging, dropping any IO capturing. """
|
||||||
import _pytest.config
|
import _pytest.config
|
||||||
|
|
||||||
if cls._pluginmanager is not None:
|
if cls._pluginmanager is not None:
|
||||||
capman = cls._pluginmanager.getplugin("capturemanager")
|
capman = cls._pluginmanager.getplugin("capturemanager")
|
||||||
if capman:
|
else:
|
||||||
capman.suspend(in_=True)
|
capman = None
|
||||||
|
if capman:
|
||||||
|
capman.suspend(in_=True)
|
||||||
|
|
||||||
|
if cls._config:
|
||||||
tw = _pytest.config.create_terminal_writer(cls._config)
|
tw = _pytest.config.create_terminal_writer(cls._config)
|
||||||
tw.line()
|
tw.line()
|
||||||
|
|
||||||
if cls._recursive_debug == 0:
|
if cls._recursive_debug == 0:
|
||||||
# Handle header similar to pdb.set_trace in py37+.
|
# Handle header similar to pdb.set_trace in py37+.
|
||||||
header = kwargs.pop("header", None)
|
header = kwargs.pop("header", None)
|
||||||
|
@ -133,112 +229,28 @@ class pytestPDB(object):
|
||||||
tw.sep(">", header)
|
tw.sep(">", header)
|
||||||
else:
|
else:
|
||||||
capturing = cls._is_capturing(capman)
|
capturing = cls._is_capturing(capman)
|
||||||
if capturing:
|
if capturing == "global":
|
||||||
if capturing == "global":
|
tw.sep(">", "PDB %s (IO-capturing turned off)" % (method,))
|
||||||
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
|
elif capturing:
|
||||||
else:
|
tw.sep(
|
||||||
tw.sep(
|
">",
|
||||||
">",
|
"PDB %s (IO-capturing turned off for %s)"
|
||||||
"PDB set_trace (IO-capturing turned off for %s)"
|
% (method, capturing),
|
||||||
% capturing,
|
)
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
tw.sep(">", "PDB set_trace")
|
tw.sep(">", "PDB %s" % (method,))
|
||||||
|
|
||||||
pdb_cls = cls._import_pdb_cls()
|
_pdb = cls._import_pdb_cls(capman)(**kwargs)
|
||||||
|
|
||||||
class PytestPdbWrapper(pdb_cls, object):
|
if cls._pluginmanager:
|
||||||
_pytest_capman = capman
|
|
||||||
_continued = False
|
|
||||||
|
|
||||||
def do_debug(self, arg):
|
|
||||||
cls._recursive_debug += 1
|
|
||||||
ret = super(PytestPdbWrapper, self).do_debug(arg)
|
|
||||||
cls._recursive_debug -= 1
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def do_continue(self, arg):
|
|
||||||
ret = super(PytestPdbWrapper, self).do_continue(arg)
|
|
||||||
if cls._recursive_debug == 0:
|
|
||||||
tw = _pytest.config.create_terminal_writer(cls._config)
|
|
||||||
tw.line()
|
|
||||||
|
|
||||||
capman = self._pytest_capman
|
|
||||||
capturing = pytestPDB._is_capturing(capman)
|
|
||||||
if capturing:
|
|
||||||
if capturing == "global":
|
|
||||||
tw.sep(">", "PDB continue (IO-capturing resumed)")
|
|
||||||
else:
|
|
||||||
tw.sep(
|
|
||||||
">",
|
|
||||||
"PDB continue (IO-capturing resumed for %s)"
|
|
||||||
% capturing,
|
|
||||||
)
|
|
||||||
capman.resume()
|
|
||||||
else:
|
|
||||||
tw.sep(">", "PDB continue")
|
|
||||||
cls._pluginmanager.hook.pytest_leave_pdb(
|
|
||||||
config=cls._config, pdb=self
|
|
||||||
)
|
|
||||||
self._continued = True
|
|
||||||
return ret
|
|
||||||
|
|
||||||
do_c = do_cont = do_continue
|
|
||||||
|
|
||||||
def do_quit(self, arg):
|
|
||||||
"""Raise Exit outcome when quit command is used in pdb.
|
|
||||||
|
|
||||||
This is a bit of a hack - it would be better if BdbQuit
|
|
||||||
could be handled, but this would require to wrap the
|
|
||||||
whole pytest run, and adjust the report etc.
|
|
||||||
"""
|
|
||||||
ret = super(PytestPdbWrapper, self).do_quit(arg)
|
|
||||||
|
|
||||||
if cls._recursive_debug == 0:
|
|
||||||
outcomes.exit("Quitting debugger")
|
|
||||||
|
|
||||||
return ret
|
|
||||||
|
|
||||||
do_q = do_quit
|
|
||||||
do_exit = do_quit
|
|
||||||
|
|
||||||
def setup(self, f, tb):
|
|
||||||
"""Suspend on setup().
|
|
||||||
|
|
||||||
Needed after do_continue resumed, and entering another
|
|
||||||
breakpoint again.
|
|
||||||
"""
|
|
||||||
ret = super(PytestPdbWrapper, self).setup(f, tb)
|
|
||||||
if not ret and self._continued:
|
|
||||||
# pdb.setup() returns True if the command wants to exit
|
|
||||||
# from the interaction: do not suspend capturing then.
|
|
||||||
if self._pytest_capman:
|
|
||||||
self._pytest_capman.suspend_global_capture(in_=True)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def get_stack(self, f, t):
|
|
||||||
stack, i = super(PytestPdbWrapper, self).get_stack(f, t)
|
|
||||||
if f is None:
|
|
||||||
# Find last non-hidden frame.
|
|
||||||
i = max(0, len(stack) - 1)
|
|
||||||
while i and stack[i][0].f_locals.get(
|
|
||||||
"__tracebackhide__", False
|
|
||||||
):
|
|
||||||
i -= 1
|
|
||||||
return stack, i
|
|
||||||
|
|
||||||
_pdb = PytestPdbWrapper(**kwargs)
|
|
||||||
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb)
|
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb)
|
||||||
else:
|
|
||||||
pdb_cls = cls._import_pdb_cls()
|
|
||||||
_pdb = pdb_cls(**kwargs)
|
|
||||||
return _pdb
|
return _pdb
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_trace(cls, *args, **kwargs):
|
def set_trace(cls, *args, **kwargs):
|
||||||
"""Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing."""
|
"""Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing."""
|
||||||
frame = sys._getframe().f_back
|
frame = sys._getframe().f_back
|
||||||
_pdb = cls._init_pdb(*args, **kwargs)
|
_pdb = cls._init_pdb("set_trace", *args, **kwargs)
|
||||||
_pdb.set_trace(frame)
|
_pdb.set_trace(frame)
|
||||||
|
|
||||||
|
|
||||||
|
@ -265,7 +277,7 @@ class PdbTrace(object):
|
||||||
|
|
||||||
|
|
||||||
def _test_pytest_function(pyfuncitem):
|
def _test_pytest_function(pyfuncitem):
|
||||||
_pdb = pytestPDB._init_pdb()
|
_pdb = pytestPDB._init_pdb("runcall")
|
||||||
testfunction = pyfuncitem.obj
|
testfunction = pyfuncitem.obj
|
||||||
pyfuncitem.obj = _pdb.runcall
|
pyfuncitem.obj = _pdb.runcall
|
||||||
if "func" in pyfuncitem._fixtureinfo.argnames: # pragma: no branch
|
if "func" in pyfuncitem._fixtureinfo.argnames: # pragma: no branch
|
||||||
|
@ -315,7 +327,7 @@ def _postmortem_traceback(excinfo):
|
||||||
|
|
||||||
|
|
||||||
def post_mortem(t):
|
def post_mortem(t):
|
||||||
p = pytestPDB._init_pdb()
|
p = pytestPDB._init_pdb("post_mortem")
|
||||||
p.reset()
|
p.reset()
|
||||||
p.interaction(None, t)
|
p.interaction(None, t)
|
||||||
if p.quitting:
|
if p.quitting:
|
||||||
|
|
|
@ -854,11 +854,9 @@ class FixtureDef(object):
|
||||||
exceptions.append(sys.exc_info())
|
exceptions.append(sys.exc_info())
|
||||||
if exceptions:
|
if exceptions:
|
||||||
e = exceptions[0]
|
e = exceptions[0]
|
||||||
del (
|
# Ensure to not keep frame references through traceback.
|
||||||
exceptions
|
del exceptions
|
||||||
) # ensure we don't keep all frames alive because of the traceback
|
|
||||||
six.reraise(*e)
|
six.reraise(*e)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
|
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
|
||||||
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
|
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
|
||||||
|
|
|
@ -142,24 +142,48 @@ def pytest_cmdline_main(config):
|
||||||
|
|
||||||
|
|
||||||
def showhelp(config):
|
def showhelp(config):
|
||||||
|
import textwrap
|
||||||
|
|
||||||
reporter = config.pluginmanager.get_plugin("terminalreporter")
|
reporter = config.pluginmanager.get_plugin("terminalreporter")
|
||||||
tw = reporter._tw
|
tw = reporter._tw
|
||||||
tw.write(config._parser.optparser.format_help())
|
tw.write(config._parser.optparser.format_help())
|
||||||
tw.line()
|
tw.line()
|
||||||
tw.line()
|
|
||||||
tw.line(
|
tw.line(
|
||||||
"[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:"
|
"[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:"
|
||||||
)
|
)
|
||||||
tw.line()
|
tw.line()
|
||||||
|
|
||||||
columns = tw.fullwidth # costly call
|
columns = tw.fullwidth # costly call
|
||||||
|
indent_len = 24 # based on argparse's max_help_position=24
|
||||||
|
indent = " " * indent_len
|
||||||
for name in config._parser._ininames:
|
for name in config._parser._ininames:
|
||||||
help, type, default = config._parser._inidict[name]
|
help, type, default = config._parser._inidict[name]
|
||||||
if type is None:
|
if type is None:
|
||||||
type = "string"
|
type = "string"
|
||||||
spec = "%s (%s)" % (name, type)
|
spec = "%s (%s):" % (name, type)
|
||||||
line = " %-24s %s" % (spec, help)
|
tw.write(" %s" % spec)
|
||||||
tw.line(line[:columns])
|
spec_len = len(spec)
|
||||||
|
if spec_len > (indent_len - 3):
|
||||||
|
# Display help starting at a new line.
|
||||||
|
tw.line()
|
||||||
|
helplines = textwrap.wrap(
|
||||||
|
help,
|
||||||
|
columns,
|
||||||
|
initial_indent=indent,
|
||||||
|
subsequent_indent=indent,
|
||||||
|
break_on_hyphens=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
for line in helplines:
|
||||||
|
tw.line(line)
|
||||||
|
else:
|
||||||
|
# Display help starting after the spec, following lines indented.
|
||||||
|
tw.write(" " * (indent_len - spec_len - 2))
|
||||||
|
wrapped = textwrap.wrap(help, columns - indent_len, break_on_hyphens=False)
|
||||||
|
|
||||||
|
tw.line(wrapped[0])
|
||||||
|
for line in wrapped[1:]:
|
||||||
|
tw.line(indent + line)
|
||||||
|
|
||||||
tw.line()
|
tw.line()
|
||||||
tw.line("environment variables:")
|
tw.line("environment variables:")
|
||||||
|
|
|
@ -167,6 +167,9 @@ class _NodeReporter(object):
|
||||||
self.append(node)
|
self.append(node)
|
||||||
|
|
||||||
def write_captured_output(self, report):
|
def write_captured_output(self, report):
|
||||||
|
if not self.xml.log_passing_tests and report.passed:
|
||||||
|
return
|
||||||
|
|
||||||
content_out = report.capstdout
|
content_out = report.capstdout
|
||||||
content_log = report.caplog
|
content_log = report.caplog
|
||||||
content_err = report.capstderr
|
content_err = report.capstderr
|
||||||
|
@ -414,6 +417,12 @@ def pytest_addoption(parser):
|
||||||
"one of no|system-out|system-err",
|
"one of no|system-out|system-err",
|
||||||
default="no",
|
default="no",
|
||||||
) # choices=['no', 'stdout', 'stderr'])
|
) # choices=['no', 'stdout', 'stderr'])
|
||||||
|
parser.addini(
|
||||||
|
"junit_log_passing_tests",
|
||||||
|
"Capture log information for passing tests to JUnit report: ",
|
||||||
|
type="bool",
|
||||||
|
default=True,
|
||||||
|
)
|
||||||
parser.addini(
|
parser.addini(
|
||||||
"junit_duration_report",
|
"junit_duration_report",
|
||||||
"Duration time to report: one of total|call",
|
"Duration time to report: one of total|call",
|
||||||
|
@ -437,6 +446,7 @@ def pytest_configure(config):
|
||||||
config.getini("junit_logging"),
|
config.getini("junit_logging"),
|
||||||
config.getini("junit_duration_report"),
|
config.getini("junit_duration_report"),
|
||||||
config.getini("junit_family"),
|
config.getini("junit_family"),
|
||||||
|
config.getini("junit_log_passing_tests"),
|
||||||
)
|
)
|
||||||
config.pluginmanager.register(config._xml)
|
config.pluginmanager.register(config._xml)
|
||||||
|
|
||||||
|
@ -472,12 +482,14 @@ class LogXML(object):
|
||||||
logging="no",
|
logging="no",
|
||||||
report_duration="total",
|
report_duration="total",
|
||||||
family="xunit1",
|
family="xunit1",
|
||||||
|
log_passing_tests=True,
|
||||||
):
|
):
|
||||||
logfile = os.path.expanduser(os.path.expandvars(logfile))
|
logfile = os.path.expanduser(os.path.expandvars(logfile))
|
||||||
self.logfile = os.path.normpath(os.path.abspath(logfile))
|
self.logfile = os.path.normpath(os.path.abspath(logfile))
|
||||||
self.prefix = prefix
|
self.prefix = prefix
|
||||||
self.suite_name = suite_name
|
self.suite_name = suite_name
|
||||||
self.logging = logging
|
self.logging = logging
|
||||||
|
self.log_passing_tests = log_passing_tests
|
||||||
self.report_duration = report_duration
|
self.report_duration = report_duration
|
||||||
self.family = family
|
self.family = family
|
||||||
self.stats = dict.fromkeys(["error", "passed", "failure", "skipped"], 0)
|
self.stats = dict.fromkeys(["error", "passed", "failure", "skipped"], 0)
|
||||||
|
|
|
@ -18,6 +18,11 @@ from _pytest.pathlib import Path
|
||||||
|
|
||||||
DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s"
|
DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s"
|
||||||
DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S"
|
DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S"
|
||||||
|
_ANSI_ESCAPE_SEQ = re.compile(r"\x1b\[[\d;]+m")
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_ansi_escape_sequences(text):
|
||||||
|
return _ANSI_ESCAPE_SEQ.sub("", text)
|
||||||
|
|
||||||
|
|
||||||
class ColoredLevelFormatter(logging.Formatter):
|
class ColoredLevelFormatter(logging.Formatter):
|
||||||
|
@ -72,6 +77,36 @@ class ColoredLevelFormatter(logging.Formatter):
|
||||||
return super(ColoredLevelFormatter, self).format(record)
|
return super(ColoredLevelFormatter, self).format(record)
|
||||||
|
|
||||||
|
|
||||||
|
if not six.PY2:
|
||||||
|
# Formatter classes don't support format styles in PY2
|
||||||
|
|
||||||
|
class PercentStyleMultiline(logging.PercentStyle):
|
||||||
|
"""A logging style with special support for multiline messages.
|
||||||
|
|
||||||
|
If the message of a record consists of multiple lines, this style
|
||||||
|
formats the message as if each line were logged separately.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _update_message(record_dict, message):
|
||||||
|
tmp = record_dict.copy()
|
||||||
|
tmp["message"] = message
|
||||||
|
return tmp
|
||||||
|
|
||||||
|
def format(self, record):
|
||||||
|
if "\n" in record.message:
|
||||||
|
lines = record.message.splitlines()
|
||||||
|
formatted = self._fmt % self._update_message(record.__dict__, lines[0])
|
||||||
|
# TODO optimize this by introducing an option that tells the
|
||||||
|
# logging framework that the indentation doesn't
|
||||||
|
# change. This allows to compute the indentation only once.
|
||||||
|
indentation = _remove_ansi_escape_sequences(formatted).find(lines[0])
|
||||||
|
lines[0] = formatted
|
||||||
|
return ("\n" + " " * indentation).join(lines)
|
||||||
|
else:
|
||||||
|
return self._fmt % record.__dict__
|
||||||
|
|
||||||
|
|
||||||
def get_option_ini(config, *names):
|
def get_option_ini(config, *names):
|
||||||
for name in names:
|
for name in names:
|
||||||
ret = config.getoption(name) # 'default' arg won't work as expected
|
ret = config.getoption(name) # 'default' arg won't work as expected
|
||||||
|
@ -257,8 +292,8 @@ class LogCaptureFixture(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def text(self):
|
def text(self):
|
||||||
"""Returns the log text."""
|
"""Returns the formatted log text."""
|
||||||
return self.handler.stream.getvalue()
|
return _remove_ansi_escape_sequences(self.handler.stream.getvalue())
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def records(self):
|
def records(self):
|
||||||
|
@ -394,7 +429,7 @@ class LoggingPlugin(object):
|
||||||
config.option.verbose = 1
|
config.option.verbose = 1
|
||||||
|
|
||||||
self.print_logs = get_option_ini(config, "log_print")
|
self.print_logs = get_option_ini(config, "log_print")
|
||||||
self.formatter = logging.Formatter(
|
self.formatter = self._create_formatter(
|
||||||
get_option_ini(config, "log_format"),
|
get_option_ini(config, "log_format"),
|
||||||
get_option_ini(config, "log_date_format"),
|
get_option_ini(config, "log_date_format"),
|
||||||
)
|
)
|
||||||
|
@ -428,6 +463,22 @@ class LoggingPlugin(object):
|
||||||
if self._log_cli_enabled():
|
if self._log_cli_enabled():
|
||||||
self._setup_cli_logging()
|
self._setup_cli_logging()
|
||||||
|
|
||||||
|
def _create_formatter(self, log_format, log_date_format):
|
||||||
|
# color option doesn't exist if terminal plugin is disabled
|
||||||
|
color = getattr(self._config.option, "color", "no")
|
||||||
|
if color != "no" and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(
|
||||||
|
log_format
|
||||||
|
):
|
||||||
|
formatter = ColoredLevelFormatter(
|
||||||
|
create_terminal_writer(self._config), log_format, log_date_format
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
formatter = logging.Formatter(log_format, log_date_format)
|
||||||
|
|
||||||
|
if not six.PY2:
|
||||||
|
formatter._style = PercentStyleMultiline(formatter._style._fmt)
|
||||||
|
return formatter
|
||||||
|
|
||||||
def _setup_cli_logging(self):
|
def _setup_cli_logging(self):
|
||||||
config = self._config
|
config = self._config
|
||||||
terminal_reporter = config.pluginmanager.get_plugin("terminalreporter")
|
terminal_reporter = config.pluginmanager.get_plugin("terminalreporter")
|
||||||
|
@ -438,23 +489,12 @@ class LoggingPlugin(object):
|
||||||
capture_manager = config.pluginmanager.get_plugin("capturemanager")
|
capture_manager = config.pluginmanager.get_plugin("capturemanager")
|
||||||
# if capturemanager plugin is disabled, live logging still works.
|
# if capturemanager plugin is disabled, live logging still works.
|
||||||
log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
|
log_cli_handler = _LiveLoggingStreamHandler(terminal_reporter, capture_manager)
|
||||||
log_cli_format = get_option_ini(config, "log_cli_format", "log_format")
|
|
||||||
log_cli_date_format = get_option_ini(
|
log_cli_formatter = self._create_formatter(
|
||||||
config, "log_cli_date_format", "log_date_format"
|
get_option_ini(config, "log_cli_format", "log_format"),
|
||||||
|
get_option_ini(config, "log_cli_date_format", "log_date_format"),
|
||||||
)
|
)
|
||||||
if (
|
|
||||||
config.option.color != "no"
|
|
||||||
and ColoredLevelFormatter.LEVELNAME_FMT_REGEX.search(log_cli_format)
|
|
||||||
):
|
|
||||||
log_cli_formatter = ColoredLevelFormatter(
|
|
||||||
create_terminal_writer(config),
|
|
||||||
log_cli_format,
|
|
||||||
datefmt=log_cli_date_format,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
log_cli_formatter = logging.Formatter(
|
|
||||||
log_cli_format, datefmt=log_cli_date_format
|
|
||||||
)
|
|
||||||
log_cli_level = get_actual_log_level(config, "log_cli_level", "log_level")
|
log_cli_level = get_actual_log_level(config, "log_cli_level", "log_level")
|
||||||
self.log_cli_handler = log_cli_handler
|
self.log_cli_handler = log_cli_handler
|
||||||
self.live_logs_context = lambda: catching_logs(
|
self.live_logs_context = lambda: catching_logs(
|
||||||
|
|
|
@ -9,6 +9,8 @@ from __future__ import print_function
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from packaging.version import Version
|
||||||
|
|
||||||
|
|
||||||
class OutcomeException(BaseException):
|
class OutcomeException(BaseException):
|
||||||
""" OutcomeException and its subclass instances indicate and
|
""" OutcomeException and its subclass instances indicate and
|
||||||
|
@ -155,7 +157,7 @@ def importorskip(modname, minversion=None, reason=None):
|
||||||
|
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
compile(modname, "", "eval") # to catch syntaxerrors
|
compile(modname, "", "eval") # to catch syntaxerrors
|
||||||
should_skip = False
|
import_exc = None
|
||||||
|
|
||||||
with warnings.catch_warnings():
|
with warnings.catch_warnings():
|
||||||
# make sure to ignore ImportWarnings that might happen because
|
# make sure to ignore ImportWarnings that might happen because
|
||||||
|
@ -164,27 +166,19 @@ def importorskip(modname, minversion=None, reason=None):
|
||||||
warnings.simplefilter("ignore")
|
warnings.simplefilter("ignore")
|
||||||
try:
|
try:
|
||||||
__import__(modname)
|
__import__(modname)
|
||||||
except ImportError:
|
except ImportError as exc:
|
||||||
# Do not raise chained exception here(#1485)
|
# Do not raise chained exception here(#1485)
|
||||||
should_skip = True
|
import_exc = exc
|
||||||
if should_skip:
|
if import_exc:
|
||||||
if reason is None:
|
if reason is None:
|
||||||
reason = "could not import %r" % (modname,)
|
reason = "could not import %r: %s" % (modname, import_exc)
|
||||||
raise Skipped(reason, allow_module_level=True)
|
raise Skipped(reason, allow_module_level=True)
|
||||||
mod = sys.modules[modname]
|
mod = sys.modules[modname]
|
||||||
if minversion is None:
|
if minversion is None:
|
||||||
return mod
|
return mod
|
||||||
verattr = getattr(mod, "__version__", None)
|
verattr = getattr(mod, "__version__", None)
|
||||||
if minversion is not None:
|
if minversion is not None:
|
||||||
try:
|
if verattr is None or Version(verattr) < Version(minversion):
|
||||||
from pkg_resources import parse_version as pv
|
|
||||||
except ImportError:
|
|
||||||
raise Skipped(
|
|
||||||
"we have a required version for %r but can not import "
|
|
||||||
"pkg_resources to parse version strings." % (modname,),
|
|
||||||
allow_module_level=True,
|
|
||||||
)
|
|
||||||
if verattr is None or pv(verattr) < pv(minversion):
|
|
||||||
raise Skipped(
|
raise Skipped(
|
||||||
"module %r has __version__ %r, required is: %r"
|
"module %r has __version__ %r, required is: %r"
|
||||||
% (modname, verattr, minversion),
|
% (modname, verattr, minversion),
|
||||||
|
|
|
@ -153,15 +153,6 @@ class LsofFdLeakChecker(object):
|
||||||
item.warn(pytest.PytestWarning("\n".join(error)))
|
item.warn(pytest.PytestWarning("\n".join(error)))
|
||||||
|
|
||||||
|
|
||||||
# XXX copied from execnet's conftest.py - needs to be merged
|
|
||||||
winpymap = {
|
|
||||||
"python2.7": r"C:\Python27\python.exe",
|
|
||||||
"python3.4": r"C:\Python34\python.exe",
|
|
||||||
"python3.5": r"C:\Python35\python.exe",
|
|
||||||
"python3.6": r"C:\Python36\python.exe",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# used at least by pytest-xdist plugin
|
# used at least by pytest-xdist plugin
|
||||||
|
|
||||||
|
|
||||||
|
@ -517,6 +508,10 @@ class Testdir(object):
|
||||||
# Discard outer pytest options.
|
# Discard outer pytest options.
|
||||||
mp.delenv("PYTEST_ADDOPTS", raising=False)
|
mp.delenv("PYTEST_ADDOPTS", raising=False)
|
||||||
|
|
||||||
|
# Environment (updates) for inner runs.
|
||||||
|
tmphome = str(self.tmpdir)
|
||||||
|
self._env_run_update = {"HOME": tmphome, "USERPROFILE": tmphome}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Testdir %r>" % (self.tmpdir,)
|
return "<Testdir %r>" % (self.tmpdir,)
|
||||||
|
|
||||||
|
@ -813,8 +808,8 @@ class Testdir(object):
|
||||||
try:
|
try:
|
||||||
# Do not load user config (during runs only).
|
# Do not load user config (during runs only).
|
||||||
mp_run = MonkeyPatch()
|
mp_run = MonkeyPatch()
|
||||||
mp_run.setenv("HOME", str(self.tmpdir))
|
for k, v in self._env_run_update.items():
|
||||||
mp_run.setenv("USERPROFILE", str(self.tmpdir))
|
mp_run.setenv(k, v)
|
||||||
finalizers.append(mp_run.undo)
|
finalizers.append(mp_run.undo)
|
||||||
|
|
||||||
# When running pytest inline any plugins active in the main test
|
# When running pytest inline any plugins active in the main test
|
||||||
|
@ -1054,9 +1049,7 @@ class Testdir(object):
|
||||||
env["PYTHONPATH"] = os.pathsep.join(
|
env["PYTHONPATH"] = os.pathsep.join(
|
||||||
filter(None, [os.getcwd(), env.get("PYTHONPATH", "")])
|
filter(None, [os.getcwd(), env.get("PYTHONPATH", "")])
|
||||||
)
|
)
|
||||||
# Do not load user config.
|
env.update(self._env_run_update)
|
||||||
env["HOME"] = str(self.tmpdir)
|
|
||||||
env["USERPROFILE"] = env["HOME"]
|
|
||||||
kw["env"] = env
|
kw["env"] = env
|
||||||
|
|
||||||
if stdin is Testdir.CLOSE_STDIN:
|
if stdin is Testdir.CLOSE_STDIN:
|
||||||
|
@ -1242,7 +1235,12 @@ class Testdir(object):
|
||||||
if sys.platform.startswith("freebsd"):
|
if sys.platform.startswith("freebsd"):
|
||||||
pytest.xfail("pexpect does not work reliably on freebsd")
|
pytest.xfail("pexpect does not work reliably on freebsd")
|
||||||
logfile = self.tmpdir.join("spawn.out").open("wb")
|
logfile = self.tmpdir.join("spawn.out").open("wb")
|
||||||
child = pexpect.spawn(cmd, logfile=logfile)
|
|
||||||
|
# Do not load user config.
|
||||||
|
env = os.environ.copy()
|
||||||
|
env.update(self._env_run_update)
|
||||||
|
|
||||||
|
child = pexpect.spawn(cmd, logfile=logfile, env=env)
|
||||||
self.request.addfinalizer(logfile.close)
|
self.request.addfinalizer(logfile.close)
|
||||||
child.timeout = expect_timeout
|
child.timeout = expect_timeout
|
||||||
return child
|
return child
|
||||||
|
|
|
@ -31,7 +31,6 @@ from _pytest.compat import getlocation
|
||||||
from _pytest.compat import is_generator
|
from _pytest.compat import is_generator
|
||||||
from _pytest.compat import isclass
|
from _pytest.compat import isclass
|
||||||
from _pytest.compat import isfunction
|
from _pytest.compat import isfunction
|
||||||
from _pytest.compat import NoneType
|
|
||||||
from _pytest.compat import NOTSET
|
from _pytest.compat import NOTSET
|
||||||
from _pytest.compat import REGEX_TYPE
|
from _pytest.compat import REGEX_TYPE
|
||||||
from _pytest.compat import safe_getattr
|
from _pytest.compat import safe_getattr
|
||||||
|
@ -1194,7 +1193,7 @@ def _idval(val, argname, idx, idfn, item, config):
|
||||||
|
|
||||||
if isinstance(val, STRING_TYPES):
|
if isinstance(val, STRING_TYPES):
|
||||||
return _ascii_escaped_by_config(val, config)
|
return _ascii_escaped_by_config(val, config)
|
||||||
elif isinstance(val, (float, int, bool, NoneType)):
|
elif val is None or isinstance(val, (float, int, bool)):
|
||||||
return str(val)
|
return str(val)
|
||||||
elif isinstance(val, REGEX_TYPE):
|
elif isinstance(val, REGEX_TYPE):
|
||||||
return ascii_escaped(val.pattern)
|
return ascii_escaped(val.pattern)
|
||||||
|
|
|
@ -170,7 +170,7 @@ def getreportopt(config):
|
||||||
if char == "a":
|
if char == "a":
|
||||||
reportopts = "sxXwEf"
|
reportopts = "sxXwEf"
|
||||||
elif char == "A":
|
elif char == "A":
|
||||||
reportopts = "sxXwEfpP"
|
reportopts = "PpsxXwEf"
|
||||||
break
|
break
|
||||||
elif char not in reportopts:
|
elif char not in reportopts:
|
||||||
reportopts += char
|
reportopts += char
|
||||||
|
|
|
@ -60,29 +60,29 @@ class TempPathFactory(object):
|
||||||
|
|
||||||
def getbasetemp(self):
|
def getbasetemp(self):
|
||||||
""" return base temporary directory. """
|
""" return base temporary directory. """
|
||||||
if self._basetemp is None:
|
if self._basetemp is not None:
|
||||||
if self._given_basetemp is not None:
|
|
||||||
basetemp = self._given_basetemp
|
|
||||||
ensure_reset_dir(basetemp)
|
|
||||||
basetemp = basetemp.resolve()
|
|
||||||
else:
|
|
||||||
from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT")
|
|
||||||
temproot = Path(from_env or tempfile.gettempdir()).resolve()
|
|
||||||
user = get_user() or "unknown"
|
|
||||||
# use a sub-directory in the temproot to speed-up
|
|
||||||
# make_numbered_dir() call
|
|
||||||
rootdir = temproot.joinpath("pytest-of-{}".format(user))
|
|
||||||
rootdir.mkdir(exist_ok=True)
|
|
||||||
basetemp = make_numbered_dir_with_cleanup(
|
|
||||||
prefix="pytest-", root=rootdir, keep=3, lock_timeout=LOCK_TIMEOUT
|
|
||||||
)
|
|
||||||
assert basetemp is not None
|
|
||||||
self._basetemp = t = basetemp
|
|
||||||
self._trace("new basetemp", t)
|
|
||||||
return t
|
|
||||||
else:
|
|
||||||
return self._basetemp
|
return self._basetemp
|
||||||
|
|
||||||
|
if self._given_basetemp is not None:
|
||||||
|
basetemp = self._given_basetemp
|
||||||
|
ensure_reset_dir(basetemp)
|
||||||
|
basetemp = basetemp.resolve()
|
||||||
|
else:
|
||||||
|
from_env = os.environ.get("PYTEST_DEBUG_TEMPROOT")
|
||||||
|
temproot = Path(from_env or tempfile.gettempdir()).resolve()
|
||||||
|
user = get_user() or "unknown"
|
||||||
|
# use a sub-directory in the temproot to speed-up
|
||||||
|
# make_numbered_dir() call
|
||||||
|
rootdir = temproot.joinpath("pytest-of-{}".format(user))
|
||||||
|
rootdir.mkdir(exist_ok=True)
|
||||||
|
basetemp = make_numbered_dir_with_cleanup(
|
||||||
|
prefix="pytest-", root=rootdir, keep=3, lock_timeout=LOCK_TIMEOUT
|
||||||
|
)
|
||||||
|
assert basetemp is not None, basetemp
|
||||||
|
self._basetemp = t = basetemp
|
||||||
|
self._trace("new basetemp", t)
|
||||||
|
return t
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s
|
||||||
class TempdirFactory(object):
|
class TempdirFactory(object):
|
||||||
|
|
|
@ -9,6 +9,7 @@ import textwrap
|
||||||
import types
|
import types
|
||||||
|
|
||||||
import attr
|
import attr
|
||||||
|
import importlib_metadata
|
||||||
import py
|
import py
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
@ -111,8 +112,6 @@ class TestGeneralUsage(object):
|
||||||
|
|
||||||
@pytest.mark.parametrize("load_cov_early", [True, False])
|
@pytest.mark.parametrize("load_cov_early", [True, False])
|
||||||
def test_early_load_setuptools_name(self, testdir, monkeypatch, load_cov_early):
|
def test_early_load_setuptools_name(self, testdir, monkeypatch, load_cov_early):
|
||||||
pkg_resources = pytest.importorskip("pkg_resources")
|
|
||||||
|
|
||||||
testdir.makepyfile(mytestplugin1_module="")
|
testdir.makepyfile(mytestplugin1_module="")
|
||||||
testdir.makepyfile(mytestplugin2_module="")
|
testdir.makepyfile(mytestplugin2_module="")
|
||||||
testdir.makepyfile(mycov_module="")
|
testdir.makepyfile(mycov_module="")
|
||||||
|
@ -124,38 +123,28 @@ class TestGeneralUsage(object):
|
||||||
class DummyEntryPoint(object):
|
class DummyEntryPoint(object):
|
||||||
name = attr.ib()
|
name = attr.ib()
|
||||||
module = attr.ib()
|
module = attr.ib()
|
||||||
version = "1.0"
|
group = "pytest11"
|
||||||
|
|
||||||
@property
|
|
||||||
def project_name(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
__import__(self.module)
|
__import__(self.module)
|
||||||
loaded.append(self.name)
|
loaded.append(self.name)
|
||||||
return sys.modules[self.module]
|
return sys.modules[self.module]
|
||||||
|
|
||||||
@property
|
|
||||||
def dist(self):
|
|
||||||
return self
|
|
||||||
|
|
||||||
def _get_metadata(self, *args):
|
|
||||||
return []
|
|
||||||
|
|
||||||
entry_points = [
|
entry_points = [
|
||||||
DummyEntryPoint("myplugin1", "mytestplugin1_module"),
|
DummyEntryPoint("myplugin1", "mytestplugin1_module"),
|
||||||
DummyEntryPoint("myplugin2", "mytestplugin2_module"),
|
DummyEntryPoint("myplugin2", "mytestplugin2_module"),
|
||||||
DummyEntryPoint("mycov", "mycov_module"),
|
DummyEntryPoint("mycov", "mycov_module"),
|
||||||
]
|
]
|
||||||
|
|
||||||
def my_iter(group, name=None):
|
@attr.s
|
||||||
assert group == "pytest11"
|
class DummyDist(object):
|
||||||
for ep in entry_points:
|
entry_points = attr.ib()
|
||||||
if name is not None and ep.name != name:
|
files = ()
|
||||||
continue
|
|
||||||
yield ep
|
|
||||||
|
|
||||||
monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter)
|
def my_dists():
|
||||||
|
return (DummyDist(entry_points),)
|
||||||
|
|
||||||
|
monkeypatch.setattr(importlib_metadata, "distributions", my_dists)
|
||||||
params = ("-p", "mycov") if load_cov_early else ()
|
params = ("-p", "mycov") if load_cov_early else ()
|
||||||
testdir.runpytest_inprocess(*params)
|
testdir.runpytest_inprocess(*params)
|
||||||
if load_cov_early:
|
if load_cov_early:
|
||||||
|
|
|
@ -30,6 +30,7 @@ def pytest_collection_modifyitems(config, items):
|
||||||
slowest_items.append(item)
|
slowest_items.append(item)
|
||||||
else:
|
else:
|
||||||
slow_items.append(item)
|
slow_items.append(item)
|
||||||
|
item.add_marker(pytest.mark.slow)
|
||||||
else:
|
else:
|
||||||
marker = item.get_closest_marker("slow")
|
marker = item.get_closest_marker("slow")
|
||||||
if marker:
|
if marker:
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import py.io
|
import py.io
|
||||||
|
import six
|
||||||
|
|
||||||
|
import pytest
|
||||||
from _pytest.logging import ColoredLevelFormatter
|
from _pytest.logging import ColoredLevelFormatter
|
||||||
|
|
||||||
|
|
||||||
|
@ -35,3 +37,31 @@ def test_coloredlogformatter():
|
||||||
formatter = ColoredLevelFormatter(tw, logfmt)
|
formatter = ColoredLevelFormatter(tw, logfmt)
|
||||||
output = formatter.format(record)
|
output = formatter.format(record)
|
||||||
assert output == ("dummypath 10 INFO Test Message")
|
assert output == ("dummypath 10 INFO Test Message")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
six.PY2, reason="Formatter classes don't support format styles in PY2"
|
||||||
|
)
|
||||||
|
def test_multiline_message():
|
||||||
|
from _pytest.logging import PercentStyleMultiline
|
||||||
|
|
||||||
|
logfmt = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s"
|
||||||
|
|
||||||
|
record = logging.LogRecord(
|
||||||
|
name="dummy",
|
||||||
|
level=logging.INFO,
|
||||||
|
pathname="dummypath",
|
||||||
|
lineno=10,
|
||||||
|
msg="Test Message line1\nline2",
|
||||||
|
args=(),
|
||||||
|
exc_info=False,
|
||||||
|
)
|
||||||
|
# this is called by logging.Formatter.format
|
||||||
|
record.message = record.getMessage()
|
||||||
|
|
||||||
|
style = PercentStyleMultiline(logfmt)
|
||||||
|
output = style.format(record)
|
||||||
|
assert output == (
|
||||||
|
"dummypath 10 INFO Test Message line1\n"
|
||||||
|
" line2"
|
||||||
|
)
|
||||||
|
|
|
@ -1084,3 +1084,48 @@ def test_log_set_path(testdir):
|
||||||
with open(os.path.join(report_dir_base, "test_second"), "r") as rfh:
|
with open(os.path.join(report_dir_base, "test_second"), "r") as rfh:
|
||||||
content = rfh.read()
|
content = rfh.read()
|
||||||
assert "message from test 2" in content
|
assert "message from test 2" in content
|
||||||
|
|
||||||
|
|
||||||
|
def test_colored_captured_log(testdir):
|
||||||
|
"""
|
||||||
|
Test that the level names of captured log messages of a failing test are
|
||||||
|
colored.
|
||||||
|
"""
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def test_foo():
|
||||||
|
logger.info('text going to logger from call')
|
||||||
|
assert False
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest("--log-level=INFO", "--color=yes")
|
||||||
|
assert result.ret == 1
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"*-- Captured log call --*",
|
||||||
|
"\x1b[32mINFO \x1b[0m*text going to logger from call",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_colored_ansi_esc_caplogtext(testdir):
|
||||||
|
"""
|
||||||
|
Make sure that caplog.text does not contain ANSI escape sequences.
|
||||||
|
"""
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def test_foo(caplog):
|
||||||
|
logger.info('text going to logger from call')
|
||||||
|
assert '\x1b' not in caplog.text
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest("--log-level=INFO", "--color=yes")
|
||||||
|
assert result.ret == 0
|
||||||
|
|
|
@ -137,12 +137,12 @@ class TestImportHookInstallation(object):
|
||||||
def test_pytest_plugins_rewrite_module_names_correctly(self, testdir):
|
def test_pytest_plugins_rewrite_module_names_correctly(self, testdir):
|
||||||
"""Test that we match files correctly when they are marked for rewriting (#2939)."""
|
"""Test that we match files correctly when they are marked for rewriting (#2939)."""
|
||||||
contents = {
|
contents = {
|
||||||
"conftest.py": """
|
"conftest.py": """\
|
||||||
pytest_plugins = "ham"
|
pytest_plugins = "ham"
|
||||||
""",
|
""",
|
||||||
"ham.py": "",
|
"ham.py": "",
|
||||||
"hamster.py": "",
|
"hamster.py": "",
|
||||||
"test_foo.py": """
|
"test_foo.py": """\
|
||||||
def test_foo(pytestconfig):
|
def test_foo(pytestconfig):
|
||||||
assert pytestconfig.pluginmanager.rewrite_hook.find_module('ham') is not None
|
assert pytestconfig.pluginmanager.rewrite_hook.find_module('ham') is not None
|
||||||
assert pytestconfig.pluginmanager.rewrite_hook.find_module('hamster') is None
|
assert pytestconfig.pluginmanager.rewrite_hook.find_module('hamster') is None
|
||||||
|
@ -153,14 +153,13 @@ class TestImportHookInstallation(object):
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
@pytest.mark.parametrize("mode", ["plain", "rewrite"])
|
@pytest.mark.parametrize("mode", ["plain", "rewrite"])
|
||||||
@pytest.mark.parametrize("plugin_state", ["development", "installed"])
|
def test_installed_plugin_rewrite(self, testdir, mode, monkeypatch):
|
||||||
def test_installed_plugin_rewrite(self, testdir, mode, plugin_state, monkeypatch):
|
|
||||||
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
||||||
# Make sure the hook is installed early enough so that plugins
|
# Make sure the hook is installed early enough so that plugins
|
||||||
# installed via setuptools are rewritten.
|
# installed via setuptools are rewritten.
|
||||||
testdir.tmpdir.join("hampkg").ensure(dir=1)
|
testdir.tmpdir.join("hampkg").ensure(dir=1)
|
||||||
contents = {
|
contents = {
|
||||||
"hampkg/__init__.py": """
|
"hampkg/__init__.py": """\
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
|
@ -169,7 +168,7 @@ class TestImportHookInstallation(object):
|
||||||
assert values.pop(0) == value
|
assert values.pop(0) == value
|
||||||
return check
|
return check
|
||||||
""",
|
""",
|
||||||
"spamplugin.py": """
|
"spamplugin.py": """\
|
||||||
import pytest
|
import pytest
|
||||||
from hampkg import check_first2
|
from hampkg import check_first2
|
||||||
|
|
||||||
|
@ -179,46 +178,31 @@ class TestImportHookInstallation(object):
|
||||||
assert values.pop(0) == value
|
assert values.pop(0) == value
|
||||||
return check
|
return check
|
||||||
""",
|
""",
|
||||||
"mainwrapper.py": """
|
"mainwrapper.py": """\
|
||||||
import pytest, pkg_resources
|
import pytest, importlib_metadata
|
||||||
|
|
||||||
plugin_state = "{plugin_state}"
|
|
||||||
|
|
||||||
class DummyDistInfo(object):
|
|
||||||
project_name = 'spam'
|
|
||||||
version = '1.0'
|
|
||||||
|
|
||||||
def _get_metadata(self, name):
|
|
||||||
# 'RECORD' meta-data only available in installed plugins
|
|
||||||
if name == 'RECORD' and plugin_state == "installed":
|
|
||||||
return ['spamplugin.py,sha256=abc,123',
|
|
||||||
'hampkg/__init__.py,sha256=abc,123']
|
|
||||||
# 'SOURCES.txt' meta-data only available for plugins in development mode
|
|
||||||
elif name == 'SOURCES.txt' and plugin_state == "development":
|
|
||||||
return ['spamplugin.py',
|
|
||||||
'hampkg/__init__.py']
|
|
||||||
return []
|
|
||||||
|
|
||||||
class DummyEntryPoint(object):
|
class DummyEntryPoint(object):
|
||||||
name = 'spam'
|
name = 'spam'
|
||||||
module_name = 'spam.py'
|
module_name = 'spam.py'
|
||||||
attrs = ()
|
group = 'pytest11'
|
||||||
extras = None
|
|
||||||
dist = DummyDistInfo()
|
|
||||||
|
|
||||||
def load(self, require=True, *args, **kwargs):
|
def load(self):
|
||||||
import spamplugin
|
import spamplugin
|
||||||
return spamplugin
|
return spamplugin
|
||||||
|
|
||||||
def iter_entry_points(group, name=None):
|
class DummyDistInfo(object):
|
||||||
yield DummyEntryPoint()
|
version = '1.0'
|
||||||
|
files = ('spamplugin.py', 'hampkg/__init__.py')
|
||||||
|
entry_points = (DummyEntryPoint(),)
|
||||||
|
metadata = {'name': 'foo'}
|
||||||
|
|
||||||
pkg_resources.iter_entry_points = iter_entry_points
|
def distributions():
|
||||||
|
return (DummyDistInfo(),)
|
||||||
|
|
||||||
|
importlib_metadata.distributions = distributions
|
||||||
pytest.main()
|
pytest.main()
|
||||||
""".format(
|
""",
|
||||||
plugin_state=plugin_state
|
"test_foo.py": """\
|
||||||
),
|
|
||||||
"test_foo.py": """
|
|
||||||
def test(check_first):
|
def test(check_first):
|
||||||
check_first([10, 30], 30)
|
check_first([10, 30], 30)
|
||||||
|
|
||||||
|
@ -605,7 +589,10 @@ class TestAssert_reprcompare(object):
|
||||||
return "\xff"
|
return "\xff"
|
||||||
|
|
||||||
expl = callequal(A(), "1")
|
expl = callequal(A(), "1")
|
||||||
assert expl
|
if PY3:
|
||||||
|
assert expl == ["ÿ == '1'", "+ 1"]
|
||||||
|
else:
|
||||||
|
assert expl == [u"\ufffd == '1'", u"+ 1"]
|
||||||
|
|
||||||
def test_format_nonascii_explanation(self):
|
def test_format_nonascii_explanation(self):
|
||||||
assert util.format_explanation("λ")
|
assert util.format_explanation("λ")
|
||||||
|
|
|
@ -656,6 +656,12 @@ class TestAssertionRewrite(object):
|
||||||
else:
|
else:
|
||||||
assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"]
|
assert lines == ["assert 0 == 1\n + where 1 = \\n{ \\n~ \\n}.a"]
|
||||||
|
|
||||||
|
def test_unroll_expression(self):
|
||||||
|
def f():
|
||||||
|
assert all(x == 1 for x in range(10))
|
||||||
|
|
||||||
|
assert "0 == 1" in getmsg(f)
|
||||||
|
|
||||||
def test_custom_repr_non_ascii(self):
|
def test_custom_repr_non_ascii(self):
|
||||||
def f():
|
def f():
|
||||||
class A(object):
|
class A(object):
|
||||||
|
@ -671,6 +677,53 @@ class TestAssertionRewrite(object):
|
||||||
assert "UnicodeDecodeError" not in msg
|
assert "UnicodeDecodeError" not in msg
|
||||||
assert "UnicodeEncodeError" not in msg
|
assert "UnicodeEncodeError" not in msg
|
||||||
|
|
||||||
|
def test_unroll_generator(self, testdir):
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
def check_even(num):
|
||||||
|
if num % 2 == 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_generator():
|
||||||
|
odd_list = list(range(1,9,2))
|
||||||
|
assert all(check_even(num) for num in odd_list)"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines(["*assert False*", "*where False = check_even(1)*"])
|
||||||
|
|
||||||
|
def test_unroll_list_comprehension(self, testdir):
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
def check_even(num):
|
||||||
|
if num % 2 == 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_list_comprehension():
|
||||||
|
odd_list = list(range(1,9,2))
|
||||||
|
assert all([check_even(num) for num in odd_list])"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines(["*assert False*", "*where False = check_even(1)*"])
|
||||||
|
|
||||||
|
def test_for_loop(self, testdir):
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
def check_even(num):
|
||||||
|
if num % 2 == 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_for_loop():
|
||||||
|
odd_list = list(range(1,9,2))
|
||||||
|
for num in odd_list:
|
||||||
|
assert check_even(num)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = testdir.runpytest()
|
||||||
|
result.stdout.fnmatch_lines(["*assert False*", "*where False = check_even(1)*"])
|
||||||
|
|
||||||
|
|
||||||
class TestRewriteOnImport(object):
|
class TestRewriteOnImport(object):
|
||||||
def test_pycache_is_a_file(self, testdir):
|
def test_pycache_is_a_file(self, testdir):
|
||||||
|
|
|
@ -6,7 +6,7 @@ from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
import attr
|
import importlib_metadata
|
||||||
|
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
import pytest
|
import pytest
|
||||||
|
@ -532,32 +532,26 @@ def test_options_on_small_file_do_not_blow_up(testdir):
|
||||||
|
|
||||||
|
|
||||||
def test_preparse_ordering_with_setuptools(testdir, monkeypatch):
|
def test_preparse_ordering_with_setuptools(testdir, monkeypatch):
|
||||||
pkg_resources = pytest.importorskip("pkg_resources")
|
|
||||||
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
||||||
|
|
||||||
def my_iter(group, name=None):
|
class EntryPoint(object):
|
||||||
assert group == "pytest11"
|
name = "mytestplugin"
|
||||||
|
group = "pytest11"
|
||||||
|
|
||||||
class Dist(object):
|
def load(self):
|
||||||
project_name = "spam"
|
class PseudoPlugin(object):
|
||||||
version = "1.0"
|
x = 42
|
||||||
|
|
||||||
def _get_metadata(self, name):
|
return PseudoPlugin()
|
||||||
return ["foo.txt,sha256=abc,123"]
|
|
||||||
|
|
||||||
class EntryPoint(object):
|
class Dist(object):
|
||||||
name = "mytestplugin"
|
files = ()
|
||||||
dist = Dist()
|
entry_points = (EntryPoint(),)
|
||||||
|
|
||||||
def load(self):
|
def my_dists():
|
||||||
class PseudoPlugin(object):
|
return (Dist,)
|
||||||
x = 42
|
|
||||||
|
|
||||||
return PseudoPlugin()
|
monkeypatch.setattr(importlib_metadata, "distributions", my_dists)
|
||||||
|
|
||||||
return iter([EntryPoint()])
|
|
||||||
|
|
||||||
monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter)
|
|
||||||
testdir.makeconftest(
|
testdir.makeconftest(
|
||||||
"""
|
"""
|
||||||
pytest_plugins = "mytestplugin",
|
pytest_plugins = "mytestplugin",
|
||||||
|
@ -570,60 +564,50 @@ def test_preparse_ordering_with_setuptools(testdir, monkeypatch):
|
||||||
|
|
||||||
|
|
||||||
def test_setuptools_importerror_issue1479(testdir, monkeypatch):
|
def test_setuptools_importerror_issue1479(testdir, monkeypatch):
|
||||||
pkg_resources = pytest.importorskip("pkg_resources")
|
|
||||||
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
||||||
|
|
||||||
def my_iter(group, name=None):
|
class DummyEntryPoint(object):
|
||||||
assert group == "pytest11"
|
name = "mytestplugin"
|
||||||
|
group = "pytest11"
|
||||||
|
|
||||||
class Dist(object):
|
def load(self):
|
||||||
project_name = "spam"
|
raise ImportError("Don't hide me!")
|
||||||
version = "1.0"
|
|
||||||
|
|
||||||
def _get_metadata(self, name):
|
class Distribution(object):
|
||||||
return ["foo.txt,sha256=abc,123"]
|
version = "1.0"
|
||||||
|
files = ("foo.txt",)
|
||||||
|
entry_points = (DummyEntryPoint(),)
|
||||||
|
|
||||||
class EntryPoint(object):
|
def distributions():
|
||||||
name = "mytestplugin"
|
return (Distribution(),)
|
||||||
dist = Dist()
|
|
||||||
|
|
||||||
def load(self):
|
monkeypatch.setattr(importlib_metadata, "distributions", distributions)
|
||||||
raise ImportError("Don't hide me!")
|
|
||||||
|
|
||||||
return iter([EntryPoint()])
|
|
||||||
|
|
||||||
monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter)
|
|
||||||
with pytest.raises(ImportError):
|
with pytest.raises(ImportError):
|
||||||
testdir.parseconfig()
|
testdir.parseconfig()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("block_it", [True, False])
|
@pytest.mark.parametrize("block_it", [True, False])
|
||||||
def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch, block_it):
|
def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch, block_it):
|
||||||
pkg_resources = pytest.importorskip("pkg_resources")
|
|
||||||
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
||||||
|
|
||||||
plugin_module_placeholder = object()
|
plugin_module_placeholder = object()
|
||||||
|
|
||||||
def my_iter(group, name=None):
|
class DummyEntryPoint(object):
|
||||||
assert group == "pytest11"
|
name = "mytestplugin"
|
||||||
|
group = "pytest11"
|
||||||
|
|
||||||
class Dist(object):
|
def load(self):
|
||||||
project_name = "spam"
|
return plugin_module_placeholder
|
||||||
version = "1.0"
|
|
||||||
|
|
||||||
def _get_metadata(self, name):
|
class Distribution(object):
|
||||||
return ["foo.txt,sha256=abc,123"]
|
version = "1.0"
|
||||||
|
files = ("foo.txt",)
|
||||||
|
entry_points = (DummyEntryPoint(),)
|
||||||
|
|
||||||
class EntryPoint(object):
|
def distributions():
|
||||||
name = "mytestplugin"
|
return (Distribution(),)
|
||||||
dist = Dist()
|
|
||||||
|
|
||||||
def load(self):
|
monkeypatch.setattr(importlib_metadata, "distributions", distributions)
|
||||||
return plugin_module_placeholder
|
|
||||||
|
|
||||||
return iter([EntryPoint()])
|
|
||||||
|
|
||||||
monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter)
|
|
||||||
args = ("-p", "no:mytestplugin") if block_it else ()
|
args = ("-p", "no:mytestplugin") if block_it else ()
|
||||||
config = testdir.parseconfig(*args)
|
config = testdir.parseconfig(*args)
|
||||||
config.pluginmanager.import_plugin("mytestplugin")
|
config.pluginmanager.import_plugin("mytestplugin")
|
||||||
|
@ -640,37 +624,26 @@ def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch, block
|
||||||
"parse_args,should_load", [(("-p", "mytestplugin"), True), ((), False)]
|
"parse_args,should_load", [(("-p", "mytestplugin"), True), ((), False)]
|
||||||
)
|
)
|
||||||
def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load):
|
def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load):
|
||||||
pkg_resources = pytest.importorskip("pkg_resources")
|
|
||||||
|
|
||||||
def my_iter(group, name=None):
|
|
||||||
assert group == "pytest11"
|
|
||||||
assert name == "mytestplugin"
|
|
||||||
return iter([DummyEntryPoint()])
|
|
||||||
|
|
||||||
@attr.s
|
|
||||||
class DummyEntryPoint(object):
|
class DummyEntryPoint(object):
|
||||||
name = "mytestplugin"
|
project_name = name = "mytestplugin"
|
||||||
|
group = "pytest11"
|
||||||
version = "1.0"
|
version = "1.0"
|
||||||
|
|
||||||
@property
|
|
||||||
def project_name(self):
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
def load(self):
|
def load(self):
|
||||||
return sys.modules[self.name]
|
return sys.modules[self.name]
|
||||||
|
|
||||||
@property
|
class Distribution(object):
|
||||||
def dist(self):
|
entry_points = (DummyEntryPoint(),)
|
||||||
return self
|
files = ()
|
||||||
|
|
||||||
def _get_metadata(self, *args):
|
|
||||||
return []
|
|
||||||
|
|
||||||
class PseudoPlugin(object):
|
class PseudoPlugin(object):
|
||||||
x = 42
|
x = 42
|
||||||
|
|
||||||
|
def distributions():
|
||||||
|
return (Distribution(),)
|
||||||
|
|
||||||
monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1")
|
monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1")
|
||||||
monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter)
|
monkeypatch.setattr(importlib_metadata, "distributions", distributions)
|
||||||
monkeypatch.setitem(sys.modules, "mytestplugin", PseudoPlugin())
|
monkeypatch.setitem(sys.modules, "mytestplugin", PseudoPlugin())
|
||||||
config = testdir.parseconfig(*parse_args)
|
config = testdir.parseconfig(*parse_args)
|
||||||
has_loaded = config.pluginmanager.get_plugin("mytestplugin") is not None
|
has_loaded = config.pluginmanager.get_plugin("mytestplugin") is not None
|
||||||
|
|
|
@ -3,16 +3,10 @@ from __future__ import absolute_import
|
||||||
from __future__ import division
|
from __future__ import division
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
|
|
||||||
import pkg_resources
|
import importlib_metadata
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("entrypoint", ["py.test", "pytest"])
|
|
||||||
def test_entry_point_exist(entrypoint):
|
|
||||||
assert entrypoint in pkg_resources.get_entry_map("pytest")["console_scripts"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_pytest_entry_points_are_identical():
|
def test_pytest_entry_points_are_identical():
|
||||||
entryMap = pkg_resources.get_entry_map("pytest")["console_scripts"]
|
dist = importlib_metadata.distribution("pytest")
|
||||||
assert entryMap["pytest"].module_name == entryMap["py.test"].module_name
|
entry_map = {ep.name: ep for ep in dist.entry_points}
|
||||||
|
assert entry_map["pytest"].value == entry_map["py.test"].value
|
||||||
|
|
|
@ -1332,3 +1332,30 @@ def test_escaped_skipreason_issue3533(testdir):
|
||||||
snode = node.find_first_by_tag("skipped")
|
snode = node.find_first_by_tag("skipped")
|
||||||
assert "1 <> 2" in snode.text
|
assert "1 <> 2" in snode.text
|
||||||
snode.assert_attr(message="1 <> 2")
|
snode.assert_attr(message="1 <> 2")
|
||||||
|
|
||||||
|
|
||||||
|
def test_logging_passing_tests_disabled_does_not_log_test_output(testdir):
|
||||||
|
testdir.makeini(
|
||||||
|
"""
|
||||||
|
[pytest]
|
||||||
|
junit_log_passing_tests=False
|
||||||
|
junit_logging=system-out
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def test_func():
|
||||||
|
sys.stdout.write('This is stdout')
|
||||||
|
sys.stderr.write('This is stderr')
|
||||||
|
logging.warning('hello')
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result, dom = runandparse(testdir)
|
||||||
|
assert result.ret == 0
|
||||||
|
node = dom.find_first_by_tag("testcase")
|
||||||
|
assert len(node.find_by_tag("system-err")) == 0
|
||||||
|
assert len(node.find_by_tag("system-out")) == 0
|
||||||
|
|
|
@ -638,36 +638,35 @@ class TestPDB(object):
|
||||||
class pytestPDBTest(_pytest.debugging.pytestPDB):
|
class pytestPDBTest(_pytest.debugging.pytestPDB):
|
||||||
@classmethod
|
@classmethod
|
||||||
def set_trace(cls, *args, **kwargs):
|
def set_trace(cls, *args, **kwargs):
|
||||||
# Init _PdbWrapper to handle capturing.
|
# Init PytestPdbWrapper to handle capturing.
|
||||||
_pdb = cls._init_pdb(*args, **kwargs)
|
_pdb = cls._init_pdb("set_trace", *args, **kwargs)
|
||||||
|
|
||||||
# Mock out pdb.Pdb.do_continue.
|
# Mock out pdb.Pdb.do_continue.
|
||||||
import pdb
|
import pdb
|
||||||
pdb.Pdb.do_continue = lambda self, arg: None
|
pdb.Pdb.do_continue = lambda self, arg: None
|
||||||
|
|
||||||
print("=== SET_TRACE ===")
|
print("===" + " SET_TRACE ===")
|
||||||
assert input() == "debug set_trace()"
|
assert input() == "debug set_trace()"
|
||||||
|
|
||||||
# Simulate _PdbWrapper.do_debug
|
# Simulate PytestPdbWrapper.do_debug
|
||||||
cls._recursive_debug += 1
|
cls._recursive_debug += 1
|
||||||
print("ENTERING RECURSIVE DEBUGGER")
|
print("ENTERING RECURSIVE DEBUGGER")
|
||||||
print("=== SET_TRACE_2 ===")
|
print("===" + " SET_TRACE_2 ===")
|
||||||
|
|
||||||
assert input() == "c"
|
assert input() == "c"
|
||||||
_pdb.do_continue("")
|
_pdb.do_continue("")
|
||||||
print("=== SET_TRACE_3 ===")
|
print("===" + " SET_TRACE_3 ===")
|
||||||
|
|
||||||
# Simulate _PdbWrapper.do_debug
|
# Simulate PytestPdbWrapper.do_debug
|
||||||
print("LEAVING RECURSIVE DEBUGGER")
|
print("LEAVING RECURSIVE DEBUGGER")
|
||||||
cls._recursive_debug -= 1
|
cls._recursive_debug -= 1
|
||||||
|
|
||||||
print("=== SET_TRACE_4 ===")
|
print("===" + " SET_TRACE_4 ===")
|
||||||
assert input() == "c"
|
assert input() == "c"
|
||||||
_pdb.do_continue("")
|
_pdb.do_continue("")
|
||||||
|
|
||||||
def do_continue(self, arg):
|
def do_continue(self, arg):
|
||||||
print("=== do_continue")
|
print("=== do_continue")
|
||||||
# _PdbWrapper.do_continue("")
|
|
||||||
|
|
||||||
monkeypatch.setattr(_pytest.debugging, "pytestPDB", pytestPDBTest)
|
monkeypatch.setattr(_pytest.debugging, "pytestPDB", pytestPDBTest)
|
||||||
|
|
||||||
|
@ -677,7 +676,7 @@ class TestPDB(object):
|
||||||
set_trace()
|
set_trace()
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
child = testdir.spawn_pytest("%s %s" % (p1, capture_arg))
|
child = testdir.spawn_pytest("--tb=short %s %s" % (p1, capture_arg))
|
||||||
child.expect("=== SET_TRACE ===")
|
child.expect("=== SET_TRACE ===")
|
||||||
before = child.before.decode("utf8")
|
before = child.before.decode("utf8")
|
||||||
if not capture_arg:
|
if not capture_arg:
|
||||||
|
@ -827,7 +826,7 @@ class TestPDB(object):
|
||||||
result.stdout.fnmatch_lines(["*NameError*xxx*", "*1 error*"])
|
result.stdout.fnmatch_lines(["*NameError*xxx*", "*1 error*"])
|
||||||
assert custom_pdb_calls == []
|
assert custom_pdb_calls == []
|
||||||
|
|
||||||
def test_pdb_custom_cls_with_settrace(self, testdir, monkeypatch):
|
def test_pdb_custom_cls_with_set_trace(self, testdir, monkeypatch):
|
||||||
testdir.makepyfile(
|
testdir.makepyfile(
|
||||||
custom_pdb="""
|
custom_pdb="""
|
||||||
class CustomPdb(object):
|
class CustomPdb(object):
|
||||||
|
@ -1037,24 +1036,28 @@ def test_trace_after_runpytest(testdir):
|
||||||
from _pytest.debugging import pytestPDB
|
from _pytest.debugging import pytestPDB
|
||||||
|
|
||||||
def test_outer(testdir):
|
def test_outer(testdir):
|
||||||
from _pytest.debugging import pytestPDB
|
|
||||||
|
|
||||||
assert len(pytestPDB._saved) == 1
|
assert len(pytestPDB._saved) == 1
|
||||||
|
|
||||||
testdir.runpytest("-k test_inner")
|
testdir.makepyfile(
|
||||||
|
\"""
|
||||||
|
from _pytest.debugging import pytestPDB
|
||||||
|
|
||||||
__import__('pdb').set_trace()
|
def test_inner():
|
||||||
|
assert len(pytestPDB._saved) == 2
|
||||||
|
print()
|
||||||
|
print("test_inner_" + "end")
|
||||||
|
\"""
|
||||||
|
)
|
||||||
|
|
||||||
def test_inner(testdir):
|
result = testdir.runpytest("-s", "-k", "test_inner")
|
||||||
assert len(pytestPDB._saved) == 2
|
assert result.ret == 0
|
||||||
|
|
||||||
|
assert len(pytestPDB._saved) == 1
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
child = testdir.spawn_pytest("-p pytester %s -k test_outer" % p1)
|
result = testdir.runpytest_subprocess("-s", "-p", "pytester", str(p1))
|
||||||
child.expect(r"\(Pdb")
|
result.stdout.fnmatch_lines(["test_inner_end"])
|
||||||
child.sendline("c")
|
assert result.ret == 0
|
||||||
rest = child.read().decode("utf8")
|
|
||||||
TestPDB.flush(child)
|
|
||||||
assert child.exitstatus == 0, rest
|
|
||||||
|
|
||||||
|
|
||||||
def test_quit_with_swallowed_SystemExit(testdir):
|
def test_quit_with_swallowed_SystemExit(testdir):
|
||||||
|
@ -1139,14 +1142,14 @@ def test_pdbcls_via_local_module(testdir):
|
||||||
p1 = testdir.makepyfile(
|
p1 = testdir.makepyfile(
|
||||||
"""
|
"""
|
||||||
def test():
|
def test():
|
||||||
print("before_settrace")
|
print("before_set_trace")
|
||||||
__import__("pdb").set_trace()
|
__import__("pdb").set_trace()
|
||||||
""",
|
""",
|
||||||
mypdb="""
|
mypdb="""
|
||||||
class Wrapped:
|
class Wrapped:
|
||||||
class MyPdb:
|
class MyPdb:
|
||||||
def set_trace(self, *args):
|
def set_trace(self, *args):
|
||||||
print("settrace_called", args)
|
print("set_trace_called", args)
|
||||||
|
|
||||||
def runcall(self, *args, **kwds):
|
def runcall(self, *args, **kwds):
|
||||||
print("runcall_called", args, kwds)
|
print("runcall_called", args, kwds)
|
||||||
|
@ -1168,7 +1171,7 @@ def test_pdbcls_via_local_module(testdir):
|
||||||
str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", syspathinsert=True
|
str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", syspathinsert=True
|
||||||
)
|
)
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
result.stdout.fnmatch_lines(["*settrace_called*", "* 1 passed in *"])
|
result.stdout.fnmatch_lines(["*set_trace_called*", "* 1 passed in *"])
|
||||||
|
|
||||||
# Ensure that it also works with --trace.
|
# Ensure that it also works with --trace.
|
||||||
result = testdir.runpytest(
|
result = testdir.runpytest(
|
||||||
|
@ -1202,3 +1205,33 @@ def test_raises_bdbquit_with_eoferror(testdir):
|
||||||
result = testdir.runpytest(str(p1))
|
result = testdir.runpytest(str(p1))
|
||||||
result.stdout.fnmatch_lines(["E *BdbQuit", "*= 1 failed in*"])
|
result.stdout.fnmatch_lines(["E *BdbQuit", "*= 1 failed in*"])
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_pdb_wrapper_class_is_reused(testdir):
|
||||||
|
p1 = testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
def test():
|
||||||
|
__import__("pdb").set_trace()
|
||||||
|
__import__("pdb").set_trace()
|
||||||
|
|
||||||
|
import mypdb
|
||||||
|
instances = mypdb.instances
|
||||||
|
assert len(instances) == 2
|
||||||
|
assert instances[0].__class__ is instances[1].__class__
|
||||||
|
""",
|
||||||
|
mypdb="""
|
||||||
|
instances = []
|
||||||
|
|
||||||
|
class MyPdb:
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
instances.append(self)
|
||||||
|
|
||||||
|
def set_trace(self, *args):
|
||||||
|
print("set_trace_called", args)
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
result = testdir.runpytest(str(p1), "--pdbcls=mypdb:MyPdb", syspathinsert=True)
|
||||||
|
assert result.ret == 0
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
["*set_trace_called*", "*set_trace_called*", "* 1 passed in *"]
|
||||||
|
)
|
||||||
|
|
|
@ -559,3 +559,29 @@ def test_popen_default_stdin_stderr_and_stdin_None(testdir):
|
||||||
assert stdout.splitlines() == [b"", b"stdout"]
|
assert stdout.splitlines() == [b"", b"stdout"]
|
||||||
assert stderr.splitlines() == [b"stderr"]
|
assert stderr.splitlines() == [b"stderr"]
|
||||||
assert proc.returncode == 0
|
assert proc.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_spawn_uses_tmphome(testdir):
|
||||||
|
import os
|
||||||
|
|
||||||
|
tmphome = str(testdir.tmpdir)
|
||||||
|
|
||||||
|
# Does use HOME only during run.
|
||||||
|
assert os.environ.get("HOME") != tmphome
|
||||||
|
|
||||||
|
testdir._env_run_update["CUSTOMENV"] = "42"
|
||||||
|
|
||||||
|
p1 = testdir.makepyfile(
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
|
||||||
|
def test():
|
||||||
|
assert os.environ["HOME"] == {tmphome!r}
|
||||||
|
assert os.environ["CUSTOMENV"] == "42"
|
||||||
|
""".format(
|
||||||
|
tmphome=tmphome
|
||||||
|
)
|
||||||
|
)
|
||||||
|
child = testdir.spawn_pytest(str(p1))
|
||||||
|
out = child.read()
|
||||||
|
assert child.wait() == 0, out.decode("utf8")
|
||||||
|
|
|
@ -172,7 +172,7 @@ class SessionTests(object):
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
reprec = testdir.inline_run(testdir.tmpdir)
|
reprec = testdir.inline_run(testdir.tmpdir)
|
||||||
except pytest.skip.Exception: # pragma: no covers
|
except pytest.skip.Exception: # pragma: no cover
|
||||||
pytest.fail("wrong skipped caught")
|
pytest.fail("wrong skipped caught")
|
||||||
reports = reprec.getreports("pytest_collectreport")
|
reports = reprec.getreports("pytest_collectreport")
|
||||||
assert len(reports) == 1
|
assert len(reports) == 1
|
||||||
|
|
|
@ -1177,3 +1177,11 @@ def test_summary_list_after_errors(testdir):
|
||||||
"FAILED test_summary_list_after_errors.py::test_fail - assert 0",
|
"FAILED test_summary_list_after_errors.py::test_fail - assert 0",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_importorskip():
|
||||||
|
with pytest.raises(
|
||||||
|
pytest.skip.Exception,
|
||||||
|
match="^could not import 'doesnotexist': No module named .*",
|
||||||
|
):
|
||||||
|
pytest.importorskip("doesnotexist")
|
||||||
|
|
|
@ -893,7 +893,7 @@ def test_getreportopt():
|
||||||
assert getreportopt(config) == "sxXwEf" # NOTE: "w" included!
|
assert getreportopt(config) == "sxXwEf" # NOTE: "w" included!
|
||||||
|
|
||||||
config.option.reportchars = "A"
|
config.option.reportchars = "A"
|
||||||
assert getreportopt(config) == "sxXwEfpP"
|
assert getreportopt(config) == "PpsxXwEf"
|
||||||
|
|
||||||
|
|
||||||
def test_terminalreporter_reportopt_addopts(testdir):
|
def test_terminalreporter_reportopt_addopts(testdir):
|
||||||
|
|
Loading…
Reference in New Issue