commit
917195ea8e
|
@ -112,11 +112,9 @@ matrix:
|
|||
allow_failures:
|
||||
- python: '3.8-dev'
|
||||
env: TOXENV=py38-xdist
|
||||
# temporary until pytest 4.6 is released
|
||||
- env: TOXENV=py27-pluggymaster-xdist
|
||||
python: '2.7'
|
||||
- env: TOXENV=py37-pluggymaster-xdist
|
||||
|
||||
# Temporary (https://github.com/pytest-dev/pytest/pull/5334).
|
||||
- env: TOXENV=pypy3-xdist
|
||||
python: 'pypy3'
|
||||
|
||||
before_script:
|
||||
- |
|
||||
|
|
|
@ -18,6 +18,84 @@ with advance notice in the **Deprecations** section of releases.
|
|||
|
||||
.. 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)
|
||||
=========================
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
release-4.6.0
|
||||
release-4.5.0
|
||||
release-4.4.2
|
||||
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
|
||||
...sss...sssssssss...sss... [100%]
|
||||
========================= 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
|
||||
|
||||
Indirect parametrization of optional implementations/imports
|
||||
|
@ -494,7 +494,7 @@ If you run this with reporting for skips enabled:
|
|||
test_module.py .s [100%]
|
||||
|
||||
========================= 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 ====================
|
||||
|
||||
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
|
||||
E assert (3 * 2) < 6
|
||||
|
||||
failure_demo.py:20: AssertionError
|
||||
failure_demo.py:21: AssertionError
|
||||
_________________________ TestFailing.test_simple __________________________
|
||||
|
||||
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 + and 43 = <function TestFailing.test_simple.<locals>.g at 0xdeadbeef>()
|
||||
|
||||
failure_demo.py:31: AssertionError
|
||||
failure_demo.py:32: AssertionError
|
||||
____________________ TestFailing.test_simple_multiline _____________________
|
||||
|
||||
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):
|
||||
> otherfunc_multi(42, 6 * 9)
|
||||
|
||||
failure_demo.py:34:
|
||||
failure_demo.py:35:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
a = 42, b = 54
|
||||
|
@ -60,7 +60,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
> assert a == b
|
||||
E assert 42 == 54
|
||||
|
||||
failure_demo.py:15: AssertionError
|
||||
failure_demo.py:16: AssertionError
|
||||
___________________________ TestFailing.test_not ___________________________
|
||||
|
||||
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 + where 42 = <function TestFailing.test_not.<locals>.f at 0xdeadbeef>()
|
||||
|
||||
failure_demo.py:40: AssertionError
|
||||
failure_demo.py:41: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_text _________________
|
||||
|
||||
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 + eggs
|
||||
|
||||
failure_demo.py:45: AssertionError
|
||||
failure_demo.py:46: AssertionError
|
||||
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
|
||||
|
||||
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 ? ^
|
||||
|
||||
failure_demo.py:48: AssertionError
|
||||
failure_demo.py:49: AssertionError
|
||||
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
|
||||
|
||||
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 bar
|
||||
|
||||
failure_demo.py:51: AssertionError
|
||||
failure_demo.py:52: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_long_text _______________
|
||||
|
||||
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 ? ^
|
||||
|
||||
failure_demo.py:56: AssertionError
|
||||
failure_demo.py:57: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
|
||||
|
||||
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 ...Full output truncated (7 lines hidden), use '-vv' to show
|
||||
|
||||
failure_demo.py:61: AssertionError
|
||||
failure_demo.py:62: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_list _________________
|
||||
|
||||
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 Use -v to get the full diff
|
||||
|
||||
failure_demo.py:64: AssertionError
|
||||
failure_demo.py:65: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_list_long _______________
|
||||
|
||||
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 Use -v to get the full diff
|
||||
|
||||
failure_demo.py:69: AssertionError
|
||||
failure_demo.py:70: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_dict _________________
|
||||
|
||||
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 ...Full output truncated (2 lines hidden), use '-vv' to show
|
||||
|
||||
failure_demo.py:72: AssertionError
|
||||
failure_demo.py:73: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_eq_set __________________
|
||||
|
||||
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 ...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 ______________
|
||||
|
||||
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 Use -v to get the full diff
|
||||
|
||||
failure_demo.py:78: AssertionError
|
||||
failure_demo.py:79: AssertionError
|
||||
_________________ TestSpecialisedExplanations.test_in_list _________________
|
||||
|
||||
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]
|
||||
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 __________
|
||||
|
||||
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 ...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 ____________
|
||||
|
||||
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 ? +++
|
||||
|
||||
failure_demo.py:89: AssertionError
|
||||
failure_demo.py:90: AssertionError
|
||||
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
|
||||
|
||||
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 ? +++
|
||||
|
||||
failure_demo.py:93: AssertionError
|
||||
failure_demo.py:94: AssertionError
|
||||
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
|
||||
|
||||
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 ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
failure_demo.py:97: AssertionError
|
||||
failure_demo.py:98: AssertionError
|
||||
______________ TestSpecialisedExplanations.test_eq_dataclass _______________
|
||||
|
||||
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 b: 'b' != 'c'
|
||||
|
||||
failure_demo.py:109: AssertionError
|
||||
failure_demo.py:110: AssertionError
|
||||
________________ TestSpecialisedExplanations.test_eq_attrs _________________
|
||||
|
||||
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 b: 'b' != 'c'
|
||||
|
||||
failure_demo.py:121: AssertionError
|
||||
failure_demo.py:122: AssertionError
|
||||
______________________________ 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 + 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 __________________________
|
||||
|
||||
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 <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 __________________________
|
||||
|
||||
def test_attribute_failure():
|
||||
|
@ -364,7 +364,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
i = Foo()
|
||||
> assert i.b == 2
|
||||
|
||||
failure_demo.py:147:
|
||||
failure_demo.py:148:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
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")
|
||||
E Exception: Failed to get attrib
|
||||
|
||||
failure_demo.py:142: Exception
|
||||
failure_demo.py:143: Exception
|
||||
_________________________ 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 + 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 __________________________
|
||||
|
||||
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)
|
||||
E ValueError: invalid literal for int() with base 10: 'qwe'
|
||||
|
||||
failure_demo.py:167: ValueError
|
||||
failure_demo.py:168: ValueError
|
||||
______________________ TestRaises.test_raises_doesnt _______________________
|
||||
|
||||
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")
|
||||
E Failed: DID NOT RAISE <class 'OSError'>
|
||||
|
||||
failure_demo.py:170: Failed
|
||||
failure_demo.py:171: Failed
|
||||
__________________________ TestRaises.test_raise ___________________________
|
||||
|
||||
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")
|
||||
E ValueError: demo error
|
||||
|
||||
failure_demo.py:173: ValueError
|
||||
failure_demo.py:174: ValueError
|
||||
________________________ TestRaises.test_tupleerror ________________________
|
||||
|
||||
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
|
||||
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 ______
|
||||
|
||||
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()
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:181: TypeError
|
||||
failure_demo.py:182: TypeError
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
items is [1, 2, 3]
|
||||
________________________ TestRaises.test_some_error ________________________
|
||||
|
@ -449,7 +449,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
> if namenotexi: # NOQA
|
||||
E NameError: name 'namenotexi' is not defined
|
||||
|
||||
failure_demo.py:184: NameError
|
||||
failure_demo.py:185: NameError
|
||||
____________________ 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
|
||||
> module.foo()
|
||||
|
||||
failure_demo.py:202:
|
||||
failure_demo.py:203:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
def foo():
|
||||
> assert 1 == 0
|
||||
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 _____________________
|
||||
|
||||
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())
|
||||
|
||||
failure_demo.py:213:
|
||||
failure_demo.py:214:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
failure_demo.py:11: in somefunc
|
||||
failure_demo.py:12: in somefunc
|
||||
otherfunc(x, y)
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
|
@ -497,7 +497,7 @@ Here is a nice run of several failures and how ``pytest`` presents things:
|
|||
> assert a == b
|
||||
E assert 44 == 43
|
||||
|
||||
failure_demo.py:7: AssertionError
|
||||
failure_demo.py:8: AssertionError
|
||||
___________________ TestMoreErrors.test_z1_unpack_error ____________________
|
||||
|
||||
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
|
||||
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 _____________________
|
||||
|
||||
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
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:221: TypeError
|
||||
failure_demo.py:222: TypeError
|
||||
______________________ TestMoreErrors.test_startswith ______________________
|
||||
|
||||
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 <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 ___________________
|
||||
|
||||
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 + 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 ______________________
|
||||
|
||||
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 43 = globf(42)
|
||||
|
||||
failure_demo.py:238: AssertionError
|
||||
failure_demo.py:239: AssertionError
|
||||
_______________________ TestMoreErrors.test_instance _______________________
|
||||
|
||||
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 + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
|
||||
|
||||
failure_demo.py:242: AssertionError
|
||||
failure_demo.py:243: AssertionError
|
||||
_______________________ TestMoreErrors.test_compare ________________________
|
||||
|
||||
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 + where 11 = globf(10)
|
||||
|
||||
failure_demo.py:245: AssertionError
|
||||
failure_demo.py:246: AssertionError
|
||||
_____________________ TestMoreErrors.test_try_finally ______________________
|
||||
|
||||
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
|
||||
E assert 1 == 0
|
||||
|
||||
failure_demo.py:250: AssertionError
|
||||
failure_demo.py:251: AssertionError
|
||||
___________________ TestCustomAssertMsg.test_single_line ___________________
|
||||
|
||||
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 + 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 ____________________
|
||||
|
||||
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 + 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 ___________________
|
||||
|
||||
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 + 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 =========================
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
||||
.. versionadded:: 4.2
|
||||
|
@ -1102,10 +1118,35 @@ passed multiple times. The expected format is ``name=value``. For example::
|
|||
[pytest]
|
||||
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
|
||||
|
||||
|
||||
|
||||
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
|
||||
|
|
|
@ -400,7 +400,7 @@ defines an ``__init__`` constructor, as this prevents the class from being insta
|
|||
|
||||
============================= warnings summary =============================
|
||||
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:
|
||||
|
||||
-- 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 = [
|
||||
"py>=1.5.0",
|
||||
"six>=1.10.0",
|
||||
"setuptools",
|
||||
"packaging",
|
||||
"attrs>=17.4.0",
|
||||
'more-itertools>=4.0.0,<6.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"',
|
||||
'pathlib2>=2.2.0;python_version<"3.6"',
|
||||
'colorama;sys_platform=="win32"',
|
||||
"pluggy==0.11", # temporary until 4.6 is released
|
||||
"pluggy>=0.12,<1.0",
|
||||
"importlib-metadata>=0.12",
|
||||
"wcwidth",
|
||||
]
|
||||
|
||||
|
|
|
@ -68,7 +68,6 @@ class AssertionRewritingHook(object):
|
|||
self.session = None
|
||||
self.modules = {}
|
||||
self._rewritten_names = set()
|
||||
self._register_with_pkg_resources()
|
||||
self._must_rewrite = set()
|
||||
# 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)
|
||||
|
@ -319,24 +318,6 @@ class AssertionRewritingHook(object):
|
|||
tp = desc[2]
|
||||
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):
|
||||
"""Optional PEP302 get_data API.
|
||||
"""
|
||||
|
@ -972,6 +953,8 @@ warn_explicit(
|
|||
"""
|
||||
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)
|
||||
arg_expls = []
|
||||
new_args = []
|
||||
|
@ -995,6 +978,27 @@ warn_explicit(
|
|||
outer_expl = "%s\n{%s = %s\n}" % (res_expl, res_expl, 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):
|
||||
# From Python 3.5, a Starred node can appear in a function call
|
||||
res, expl = self.visit(starred.value)
|
||||
|
@ -1005,6 +1009,8 @@ warn_explicit(
|
|||
"""
|
||||
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)
|
||||
arg_expls = []
|
||||
new_args = []
|
||||
|
|
|
@ -37,7 +37,6 @@ if _PY3:
|
|||
else:
|
||||
from funcsigs import signature, Parameter as Parameter
|
||||
|
||||
NoneType = type(None)
|
||||
NOTSET = object()
|
||||
|
||||
PY35 = sys.version_info[:2] >= (3, 5)
|
||||
|
|
|
@ -13,8 +13,10 @@ import sys
|
|||
import types
|
||||
import warnings
|
||||
|
||||
import importlib_metadata
|
||||
import py
|
||||
import six
|
||||
from packaging.version import Version
|
||||
from pluggy import HookimplMarker
|
||||
from pluggy import HookspecMarker
|
||||
from pluggy import PluginManager
|
||||
|
@ -788,25 +790,17 @@ class Config(object):
|
|||
modules or packages in the distribution package for
|
||||
all pytest plugins.
|
||||
"""
|
||||
import pkg_resources
|
||||
|
||||
self.pluginmanager.rewrite_hook = hook
|
||||
|
||||
if os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"):
|
||||
# We don't autoload from setuptools entry points, no need to continue.
|
||||
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 = (
|
||||
entry.split(",")[0]
|
||||
for entrypoint in pkg_resources.iter_entry_points("pytest11")
|
||||
for metadata in metadata_files
|
||||
for entry in entrypoint.dist._get_metadata(metadata)
|
||||
str(file)
|
||||
for dist in importlib_metadata.distributions()
|
||||
if any(ep.group == "pytest11" for ep in dist.entry_points)
|
||||
for file in dist.files
|
||||
)
|
||||
|
||||
for name in _iter_rewritable_modules(package_files):
|
||||
|
@ -875,11 +869,10 @@ class Config(object):
|
|||
|
||||
def _checkversion(self):
|
||||
import pytest
|
||||
from pkg_resources import parse_version
|
||||
|
||||
minver = self.inicfg.get("minversion", None)
|
||||
if minver:
|
||||
if parse_version(minver) > parse_version(pytest.__version__):
|
||||
if Version(minver) > Version(pytest.__version__):
|
||||
raise pytest.UsageError(
|
||||
"%s:%d: requires pytest-%s, actual pytest-%s'"
|
||||
% (
|
||||
|
|
|
@ -81,6 +81,7 @@ class pytestPDB(object):
|
|||
_config = None
|
||||
_saved = []
|
||||
_recursive_debug = 0
|
||||
_wrapped_pdb_cls = None
|
||||
|
||||
@classmethod
|
||||
def _is_capturing(cls, capman):
|
||||
|
@ -89,16 +90,18 @@ class pytestPDB(object):
|
|||
return False
|
||||
|
||||
@classmethod
|
||||
def _import_pdb_cls(cls):
|
||||
def _import_pdb_cls(cls, capman):
|
||||
if not cls._config:
|
||||
# Happens when using pytest.set_trace outside of a test.
|
||||
return pdb.Pdb
|
||||
|
||||
pdb_cls = cls._config.getvalue("usepdb_cls")
|
||||
if not pdb_cls:
|
||||
return pdb.Pdb
|
||||
usepdb_cls = cls._config.getvalue("usepdb_cls")
|
||||
|
||||
modname, classname = pdb_cls
|
||||
if cls._wrapped_pdb_cls and cls._wrapped_pdb_cls[0] == usepdb_cls:
|
||||
return cls._wrapped_pdb_cls[1]
|
||||
|
||||
if usepdb_cls:
|
||||
modname, classname = usepdb_cls
|
||||
|
||||
try:
|
||||
__import__(modname)
|
||||
|
@ -109,43 +112,21 @@ class pytestPDB(object):
|
|||
pdb_cls = getattr(mod, parts[0])
|
||||
for part in parts[1:]:
|
||||
pdb_cls = getattr(pdb_cls, part)
|
||||
|
||||
return pdb_cls
|
||||
except Exception as exc:
|
||||
value = ":".join((modname, classname))
|
||||
raise UsageError("--pdbcls: could not import {!r}: {}".format(value, exc))
|
||||
|
||||
@classmethod
|
||||
def _init_pdb(cls, *args, **kwargs):
|
||||
""" Initialize PDB debugging, dropping any IO capturing. """
|
||||
import _pytest.config
|
||||
|
||||
if cls._pluginmanager is not None:
|
||||
capman = cls._pluginmanager.getplugin("capturemanager")
|
||||
if capman:
|
||||
capman.suspend(in_=True)
|
||||
tw = _pytest.config.create_terminal_writer(cls._config)
|
||||
tw.line()
|
||||
if cls._recursive_debug == 0:
|
||||
# Handle header similar to pdb.set_trace in py37+.
|
||||
header = kwargs.pop("header", None)
|
||||
if header is not None:
|
||||
tw.sep(">", header)
|
||||
else:
|
||||
capturing = cls._is_capturing(capman)
|
||||
if capturing:
|
||||
if capturing == "global":
|
||||
tw.sep(">", "PDB set_trace (IO-capturing turned off)")
|
||||
else:
|
||||
tw.sep(
|
||||
">",
|
||||
"PDB set_trace (IO-capturing turned off for %s)"
|
||||
% capturing,
|
||||
raise UsageError(
|
||||
"--pdbcls: could not import {!r}: {}".format(value, exc)
|
||||
)
|
||||
else:
|
||||
tw.sep(">", "PDB set_trace")
|
||||
pdb_cls = pdb.Pdb
|
||||
|
||||
pdb_cls = cls._import_pdb_cls()
|
||||
wrapped_cls = cls._get_pdb_wrapper_class(pdb_cls, capman)
|
||||
cls._wrapped_pdb_cls = (usepdb_cls, wrapped_cls)
|
||||
return wrapped_cls
|
||||
|
||||
@classmethod
|
||||
def _get_pdb_wrapper_class(cls, pdb_cls, capman):
|
||||
import _pytest.config
|
||||
|
||||
class PytestPdbWrapper(pdb_cls, object):
|
||||
_pytest_capman = capman
|
||||
|
@ -177,9 +158,7 @@ class pytestPDB(object):
|
|||
capman.resume()
|
||||
else:
|
||||
tw.sep(">", "PDB continue")
|
||||
cls._pluginmanager.hook.pytest_leave_pdb(
|
||||
config=cls._config, pdb=self
|
||||
)
|
||||
cls._pluginmanager.hook.pytest_leave_pdb(config=cls._config, pdb=self)
|
||||
self._continued = True
|
||||
return ret
|
||||
|
||||
|
@ -221,24 +200,57 @@ class pytestPDB(object):
|
|||
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
|
||||
):
|
||||
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)
|
||||
return PytestPdbWrapper
|
||||
|
||||
@classmethod
|
||||
def _init_pdb(cls, method, *args, **kwargs):
|
||||
""" Initialize PDB debugging, dropping any IO capturing. """
|
||||
import _pytest.config
|
||||
|
||||
if cls._pluginmanager is not None:
|
||||
capman = cls._pluginmanager.getplugin("capturemanager")
|
||||
else:
|
||||
pdb_cls = cls._import_pdb_cls()
|
||||
_pdb = pdb_cls(**kwargs)
|
||||
capman = None
|
||||
if capman:
|
||||
capman.suspend(in_=True)
|
||||
|
||||
if cls._config:
|
||||
tw = _pytest.config.create_terminal_writer(cls._config)
|
||||
tw.line()
|
||||
|
||||
if cls._recursive_debug == 0:
|
||||
# Handle header similar to pdb.set_trace in py37+.
|
||||
header = kwargs.pop("header", None)
|
||||
if header is not None:
|
||||
tw.sep(">", header)
|
||||
else:
|
||||
capturing = cls._is_capturing(capman)
|
||||
if capturing == "global":
|
||||
tw.sep(">", "PDB %s (IO-capturing turned off)" % (method,))
|
||||
elif capturing:
|
||||
tw.sep(
|
||||
">",
|
||||
"PDB %s (IO-capturing turned off for %s)"
|
||||
% (method, capturing),
|
||||
)
|
||||
else:
|
||||
tw.sep(">", "PDB %s" % (method,))
|
||||
|
||||
_pdb = cls._import_pdb_cls(capman)(**kwargs)
|
||||
|
||||
if cls._pluginmanager:
|
||||
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb)
|
||||
return _pdb
|
||||
|
||||
@classmethod
|
||||
def set_trace(cls, *args, **kwargs):
|
||||
"""Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing."""
|
||||
frame = sys._getframe().f_back
|
||||
_pdb = cls._init_pdb(*args, **kwargs)
|
||||
_pdb = cls._init_pdb("set_trace", *args, **kwargs)
|
||||
_pdb.set_trace(frame)
|
||||
|
||||
|
||||
|
@ -265,7 +277,7 @@ class PdbTrace(object):
|
|||
|
||||
|
||||
def _test_pytest_function(pyfuncitem):
|
||||
_pdb = pytestPDB._init_pdb()
|
||||
_pdb = pytestPDB._init_pdb("runcall")
|
||||
testfunction = pyfuncitem.obj
|
||||
pyfuncitem.obj = _pdb.runcall
|
||||
if "func" in pyfuncitem._fixtureinfo.argnames: # pragma: no branch
|
||||
|
@ -315,7 +327,7 @@ def _postmortem_traceback(excinfo):
|
|||
|
||||
|
||||
def post_mortem(t):
|
||||
p = pytestPDB._init_pdb()
|
||||
p = pytestPDB._init_pdb("post_mortem")
|
||||
p.reset()
|
||||
p.interaction(None, t)
|
||||
if p.quitting:
|
||||
|
|
|
@ -854,11 +854,9 @@ class FixtureDef(object):
|
|||
exceptions.append(sys.exc_info())
|
||||
if exceptions:
|
||||
e = exceptions[0]
|
||||
del (
|
||||
exceptions
|
||||
) # ensure we don't keep all frames alive because of the traceback
|
||||
# Ensure to not keep frame references through traceback.
|
||||
del exceptions
|
||||
six.reraise(*e)
|
||||
|
||||
finally:
|
||||
hook = self._fixturemanager.session.gethookproxy(request.node.fspath)
|
||||
hook.pytest_fixture_post_finalizer(fixturedef=self, request=request)
|
||||
|
|
|
@ -142,24 +142,48 @@ def pytest_cmdline_main(config):
|
|||
|
||||
|
||||
def showhelp(config):
|
||||
import textwrap
|
||||
|
||||
reporter = config.pluginmanager.get_plugin("terminalreporter")
|
||||
tw = reporter._tw
|
||||
tw.write(config._parser.optparser.format_help())
|
||||
tw.line()
|
||||
tw.line()
|
||||
tw.line(
|
||||
"[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:"
|
||||
)
|
||||
tw.line()
|
||||
|
||||
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:
|
||||
help, type, default = config._parser._inidict[name]
|
||||
if type is None:
|
||||
type = "string"
|
||||
spec = "%s (%s)" % (name, type)
|
||||
line = " %-24s %s" % (spec, help)
|
||||
tw.line(line[:columns])
|
||||
spec = "%s (%s):" % (name, type)
|
||||
tw.write(" %s" % spec)
|
||||
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("environment variables:")
|
||||
|
|
|
@ -167,6 +167,9 @@ class _NodeReporter(object):
|
|||
self.append(node)
|
||||
|
||||
def write_captured_output(self, report):
|
||||
if not self.xml.log_passing_tests and report.passed:
|
||||
return
|
||||
|
||||
content_out = report.capstdout
|
||||
content_log = report.caplog
|
||||
content_err = report.capstderr
|
||||
|
@ -414,6 +417,12 @@ def pytest_addoption(parser):
|
|||
"one of no|system-out|system-err",
|
||||
default="no",
|
||||
) # 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(
|
||||
"junit_duration_report",
|
||||
"Duration time to report: one of total|call",
|
||||
|
@ -437,6 +446,7 @@ def pytest_configure(config):
|
|||
config.getini("junit_logging"),
|
||||
config.getini("junit_duration_report"),
|
||||
config.getini("junit_family"),
|
||||
config.getini("junit_log_passing_tests"),
|
||||
)
|
||||
config.pluginmanager.register(config._xml)
|
||||
|
||||
|
@ -472,12 +482,14 @@ class LogXML(object):
|
|||
logging="no",
|
||||
report_duration="total",
|
||||
family="xunit1",
|
||||
log_passing_tests=True,
|
||||
):
|
||||
logfile = os.path.expanduser(os.path.expandvars(logfile))
|
||||
self.logfile = os.path.normpath(os.path.abspath(logfile))
|
||||
self.prefix = prefix
|
||||
self.suite_name = suite_name
|
||||
self.logging = logging
|
||||
self.log_passing_tests = log_passing_tests
|
||||
self.report_duration = report_duration
|
||||
self.family = family
|
||||
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_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):
|
||||
|
@ -72,6 +77,36 @@ class ColoredLevelFormatter(logging.Formatter):
|
|||
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):
|
||||
for name in names:
|
||||
ret = config.getoption(name) # 'default' arg won't work as expected
|
||||
|
@ -257,8 +292,8 @@ class LogCaptureFixture(object):
|
|||
|
||||
@property
|
||||
def text(self):
|
||||
"""Returns the log text."""
|
||||
return self.handler.stream.getvalue()
|
||||
"""Returns the formatted log text."""
|
||||
return _remove_ansi_escape_sequences(self.handler.stream.getvalue())
|
||||
|
||||
@property
|
||||
def records(self):
|
||||
|
@ -394,7 +429,7 @@ class LoggingPlugin(object):
|
|||
config.option.verbose = 1
|
||||
|
||||
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_date_format"),
|
||||
)
|
||||
|
@ -428,6 +463,22 @@ class LoggingPlugin(object):
|
|||
if self._log_cli_enabled():
|
||||
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):
|
||||
config = self._config
|
||||
terminal_reporter = config.pluginmanager.get_plugin("terminalreporter")
|
||||
|
@ -438,23 +489,12 @@ class LoggingPlugin(object):
|
|||
capture_manager = config.pluginmanager.get_plugin("capturemanager")
|
||||
# if capturemanager plugin is disabled, live logging still works.
|
||||
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(
|
||||
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_formatter = self._create_formatter(
|
||||
get_option_ini(config, "log_cli_format", "log_format"),
|
||||
get_option_ini(config, "log_cli_date_format", "log_date_format"),
|
||||
)
|
||||
|
||||
log_cli_level = get_actual_log_level(config, "log_cli_level", "log_level")
|
||||
self.log_cli_handler = log_cli_handler
|
||||
self.live_logs_context = lambda: catching_logs(
|
||||
|
|
|
@ -9,6 +9,8 @@ from __future__ import print_function
|
|||
|
||||
import sys
|
||||
|
||||
from packaging.version import Version
|
||||
|
||||
|
||||
class OutcomeException(BaseException):
|
||||
""" OutcomeException and its subclass instances indicate and
|
||||
|
@ -155,7 +157,7 @@ def importorskip(modname, minversion=None, reason=None):
|
|||
|
||||
__tracebackhide__ = True
|
||||
compile(modname, "", "eval") # to catch syntaxerrors
|
||||
should_skip = False
|
||||
import_exc = None
|
||||
|
||||
with warnings.catch_warnings():
|
||||
# make sure to ignore ImportWarnings that might happen because
|
||||
|
@ -164,27 +166,19 @@ def importorskip(modname, minversion=None, reason=None):
|
|||
warnings.simplefilter("ignore")
|
||||
try:
|
||||
__import__(modname)
|
||||
except ImportError:
|
||||
except ImportError as exc:
|
||||
# Do not raise chained exception here(#1485)
|
||||
should_skip = True
|
||||
if should_skip:
|
||||
import_exc = exc
|
||||
if import_exc:
|
||||
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)
|
||||
mod = sys.modules[modname]
|
||||
if minversion is None:
|
||||
return mod
|
||||
verattr = getattr(mod, "__version__", None)
|
||||
if minversion is not None:
|
||||
try:
|
||||
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):
|
||||
if verattr is None or Version(verattr) < Version(minversion):
|
||||
raise Skipped(
|
||||
"module %r has __version__ %r, required is: %r"
|
||||
% (modname, verattr, minversion),
|
||||
|
|
|
@ -153,15 +153,6 @@ class LsofFdLeakChecker(object):
|
|||
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
|
||||
|
||||
|
||||
|
@ -517,6 +508,10 @@ class Testdir(object):
|
|||
# Discard outer pytest options.
|
||||
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):
|
||||
return "<Testdir %r>" % (self.tmpdir,)
|
||||
|
||||
|
@ -813,8 +808,8 @@ class Testdir(object):
|
|||
try:
|
||||
# Do not load user config (during runs only).
|
||||
mp_run = MonkeyPatch()
|
||||
mp_run.setenv("HOME", str(self.tmpdir))
|
||||
mp_run.setenv("USERPROFILE", str(self.tmpdir))
|
||||
for k, v in self._env_run_update.items():
|
||||
mp_run.setenv(k, v)
|
||||
finalizers.append(mp_run.undo)
|
||||
|
||||
# When running pytest inline any plugins active in the main test
|
||||
|
@ -1054,9 +1049,7 @@ class Testdir(object):
|
|||
env["PYTHONPATH"] = os.pathsep.join(
|
||||
filter(None, [os.getcwd(), env.get("PYTHONPATH", "")])
|
||||
)
|
||||
# Do not load user config.
|
||||
env["HOME"] = str(self.tmpdir)
|
||||
env["USERPROFILE"] = env["HOME"]
|
||||
env.update(self._env_run_update)
|
||||
kw["env"] = env
|
||||
|
||||
if stdin is Testdir.CLOSE_STDIN:
|
||||
|
@ -1242,7 +1235,12 @@ class Testdir(object):
|
|||
if sys.platform.startswith("freebsd"):
|
||||
pytest.xfail("pexpect does not work reliably on freebsd")
|
||||
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)
|
||||
child.timeout = expect_timeout
|
||||
return child
|
||||
|
|
|
@ -31,7 +31,6 @@ from _pytest.compat import getlocation
|
|||
from _pytest.compat import is_generator
|
||||
from _pytest.compat import isclass
|
||||
from _pytest.compat import isfunction
|
||||
from _pytest.compat import NoneType
|
||||
from _pytest.compat import NOTSET
|
||||
from _pytest.compat import REGEX_TYPE
|
||||
from _pytest.compat import safe_getattr
|
||||
|
@ -1194,7 +1193,7 @@ def _idval(val, argname, idx, idfn, item, config):
|
|||
|
||||
if isinstance(val, STRING_TYPES):
|
||||
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)
|
||||
elif isinstance(val, REGEX_TYPE):
|
||||
return ascii_escaped(val.pattern)
|
||||
|
|
|
@ -170,7 +170,7 @@ def getreportopt(config):
|
|||
if char == "a":
|
||||
reportopts = "sxXwEf"
|
||||
elif char == "A":
|
||||
reportopts = "sxXwEfpP"
|
||||
reportopts = "PpsxXwEf"
|
||||
break
|
||||
elif char not in reportopts:
|
||||
reportopts += char
|
||||
|
|
|
@ -60,7 +60,9 @@ class TempPathFactory(object):
|
|||
|
||||
def getbasetemp(self):
|
||||
""" return base temporary directory. """
|
||||
if self._basetemp is None:
|
||||
if self._basetemp is not None:
|
||||
return self._basetemp
|
||||
|
||||
if self._given_basetemp is not None:
|
||||
basetemp = self._given_basetemp
|
||||
ensure_reset_dir(basetemp)
|
||||
|
@ -76,12 +78,10 @@ class TempPathFactory(object):
|
|||
basetemp = make_numbered_dir_with_cleanup(
|
||||
prefix="pytest-", root=rootdir, keep=3, lock_timeout=LOCK_TIMEOUT
|
||||
)
|
||||
assert basetemp is not None
|
||||
assert basetemp is not None, basetemp
|
||||
self._basetemp = t = basetemp
|
||||
self._trace("new basetemp", t)
|
||||
return t
|
||||
else:
|
||||
return self._basetemp
|
||||
|
||||
|
||||
@attr.s
|
||||
|
|
|
@ -9,6 +9,7 @@ import textwrap
|
|||
import types
|
||||
|
||||
import attr
|
||||
import importlib_metadata
|
||||
import py
|
||||
import six
|
||||
|
||||
|
@ -111,8 +112,6 @@ class TestGeneralUsage(object):
|
|||
|
||||
@pytest.mark.parametrize("load_cov_early", [True, False])
|
||||
def test_early_load_setuptools_name(self, testdir, monkeypatch, load_cov_early):
|
||||
pkg_resources = pytest.importorskip("pkg_resources")
|
||||
|
||||
testdir.makepyfile(mytestplugin1_module="")
|
||||
testdir.makepyfile(mytestplugin2_module="")
|
||||
testdir.makepyfile(mycov_module="")
|
||||
|
@ -124,38 +123,28 @@ class TestGeneralUsage(object):
|
|||
class DummyEntryPoint(object):
|
||||
name = attr.ib()
|
||||
module = attr.ib()
|
||||
version = "1.0"
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
return self.name
|
||||
group = "pytest11"
|
||||
|
||||
def load(self):
|
||||
__import__(self.module)
|
||||
loaded.append(self.name)
|
||||
return sys.modules[self.module]
|
||||
|
||||
@property
|
||||
def dist(self):
|
||||
return self
|
||||
|
||||
def _get_metadata(self, *args):
|
||||
return []
|
||||
|
||||
entry_points = [
|
||||
DummyEntryPoint("myplugin1", "mytestplugin1_module"),
|
||||
DummyEntryPoint("myplugin2", "mytestplugin2_module"),
|
||||
DummyEntryPoint("mycov", "mycov_module"),
|
||||
]
|
||||
|
||||
def my_iter(group, name=None):
|
||||
assert group == "pytest11"
|
||||
for ep in entry_points:
|
||||
if name is not None and ep.name != name:
|
||||
continue
|
||||
yield ep
|
||||
@attr.s
|
||||
class DummyDist(object):
|
||||
entry_points = attr.ib()
|
||||
files = ()
|
||||
|
||||
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 ()
|
||||
testdir.runpytest_inprocess(*params)
|
||||
if load_cov_early:
|
||||
|
|
|
@ -30,6 +30,7 @@ def pytest_collection_modifyitems(config, items):
|
|||
slowest_items.append(item)
|
||||
else:
|
||||
slow_items.append(item)
|
||||
item.add_marker(pytest.mark.slow)
|
||||
else:
|
||||
marker = item.get_closest_marker("slow")
|
||||
if marker:
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
import logging
|
||||
|
||||
import py.io
|
||||
import six
|
||||
|
||||
import pytest
|
||||
from _pytest.logging import ColoredLevelFormatter
|
||||
|
||||
|
||||
|
@ -35,3 +37,31 @@ def test_coloredlogformatter():
|
|||
formatter = ColoredLevelFormatter(tw, logfmt)
|
||||
output = formatter.format(record)
|
||||
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:
|
||||
content = rfh.read()
|
||||
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):
|
||||
"""Test that we match files correctly when they are marked for rewriting (#2939)."""
|
||||
contents = {
|
||||
"conftest.py": """
|
||||
"conftest.py": """\
|
||||
pytest_plugins = "ham"
|
||||
""",
|
||||
"ham.py": "",
|
||||
"hamster.py": "",
|
||||
"test_foo.py": """
|
||||
"test_foo.py": """\
|
||||
def test_foo(pytestconfig):
|
||||
assert pytestconfig.pluginmanager.rewrite_hook.find_module('ham') is not None
|
||||
assert pytestconfig.pluginmanager.rewrite_hook.find_module('hamster') is None
|
||||
|
@ -153,14 +153,13 @@ class TestImportHookInstallation(object):
|
|||
assert result.ret == 0
|
||||
|
||||
@pytest.mark.parametrize("mode", ["plain", "rewrite"])
|
||||
@pytest.mark.parametrize("plugin_state", ["development", "installed"])
|
||||
def test_installed_plugin_rewrite(self, testdir, mode, plugin_state, monkeypatch):
|
||||
def test_installed_plugin_rewrite(self, testdir, mode, monkeypatch):
|
||||
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
||||
# Make sure the hook is installed early enough so that plugins
|
||||
# installed via setuptools are rewritten.
|
||||
testdir.tmpdir.join("hampkg").ensure(dir=1)
|
||||
contents = {
|
||||
"hampkg/__init__.py": """
|
||||
"hampkg/__init__.py": """\
|
||||
import pytest
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -169,7 +168,7 @@ class TestImportHookInstallation(object):
|
|||
assert values.pop(0) == value
|
||||
return check
|
||||
""",
|
||||
"spamplugin.py": """
|
||||
"spamplugin.py": """\
|
||||
import pytest
|
||||
from hampkg import check_first2
|
||||
|
||||
|
@ -179,46 +178,31 @@ class TestImportHookInstallation(object):
|
|||
assert values.pop(0) == value
|
||||
return check
|
||||
""",
|
||||
"mainwrapper.py": """
|
||||
import pytest, pkg_resources
|
||||
|
||||
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 []
|
||||
"mainwrapper.py": """\
|
||||
import pytest, importlib_metadata
|
||||
|
||||
class DummyEntryPoint(object):
|
||||
name = 'spam'
|
||||
module_name = 'spam.py'
|
||||
attrs = ()
|
||||
extras = None
|
||||
dist = DummyDistInfo()
|
||||
group = 'pytest11'
|
||||
|
||||
def load(self, require=True, *args, **kwargs):
|
||||
def load(self):
|
||||
import spamplugin
|
||||
return spamplugin
|
||||
|
||||
def iter_entry_points(group, name=None):
|
||||
yield DummyEntryPoint()
|
||||
class DummyDistInfo(object):
|
||||
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()
|
||||
""".format(
|
||||
plugin_state=plugin_state
|
||||
),
|
||||
"test_foo.py": """
|
||||
""",
|
||||
"test_foo.py": """\
|
||||
def test(check_first):
|
||||
check_first([10, 30], 30)
|
||||
|
||||
|
@ -605,7 +589,10 @@ class TestAssert_reprcompare(object):
|
|||
return "\xff"
|
||||
|
||||
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):
|
||||
assert util.format_explanation("λ")
|
||||
|
|
|
@ -656,6 +656,12 @@ class TestAssertionRewrite(object):
|
|||
else:
|
||||
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 f():
|
||||
class A(object):
|
||||
|
@ -671,6 +677,53 @@ class TestAssertionRewrite(object):
|
|||
assert "UnicodeDecodeError" 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):
|
||||
def test_pycache_is_a_file(self, testdir):
|
||||
|
|
|
@ -6,7 +6,7 @@ from __future__ import print_function
|
|||
import sys
|
||||
import textwrap
|
||||
|
||||
import attr
|
||||
import importlib_metadata
|
||||
|
||||
import _pytest._code
|
||||
import pytest
|
||||
|
@ -532,22 +532,11 @@ def test_options_on_small_file_do_not_blow_up(testdir):
|
|||
|
||||
|
||||
def test_preparse_ordering_with_setuptools(testdir, monkeypatch):
|
||||
pkg_resources = pytest.importorskip("pkg_resources")
|
||||
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
||||
|
||||
def my_iter(group, name=None):
|
||||
assert group == "pytest11"
|
||||
|
||||
class Dist(object):
|
||||
project_name = "spam"
|
||||
version = "1.0"
|
||||
|
||||
def _get_metadata(self, name):
|
||||
return ["foo.txt,sha256=abc,123"]
|
||||
|
||||
class EntryPoint(object):
|
||||
name = "mytestplugin"
|
||||
dist = Dist()
|
||||
group = "pytest11"
|
||||
|
||||
def load(self):
|
||||
class PseudoPlugin(object):
|
||||
|
@ -555,9 +544,14 @@ def test_preparse_ordering_with_setuptools(testdir, monkeypatch):
|
|||
|
||||
return PseudoPlugin()
|
||||
|
||||
return iter([EntryPoint()])
|
||||
class Dist(object):
|
||||
files = ()
|
||||
entry_points = (EntryPoint(),)
|
||||
|
||||
monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter)
|
||||
def my_dists():
|
||||
return (Dist,)
|
||||
|
||||
monkeypatch.setattr(importlib_metadata, "distributions", my_dists)
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
pytest_plugins = "mytestplugin",
|
||||
|
@ -570,60 +564,50 @@ def test_preparse_ordering_with_setuptools(testdir, monkeypatch):
|
|||
|
||||
|
||||
def test_setuptools_importerror_issue1479(testdir, monkeypatch):
|
||||
pkg_resources = pytest.importorskip("pkg_resources")
|
||||
monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False)
|
||||
|
||||
def my_iter(group, name=None):
|
||||
assert group == "pytest11"
|
||||
|
||||
class Dist(object):
|
||||
project_name = "spam"
|
||||
version = "1.0"
|
||||
|
||||
def _get_metadata(self, name):
|
||||
return ["foo.txt,sha256=abc,123"]
|
||||
|
||||
class EntryPoint(object):
|
||||
class DummyEntryPoint(object):
|
||||
name = "mytestplugin"
|
||||
dist = Dist()
|
||||
group = "pytest11"
|
||||
|
||||
def load(self):
|
||||
raise ImportError("Don't hide me!")
|
||||
|
||||
return iter([EntryPoint()])
|
||||
class Distribution(object):
|
||||
version = "1.0"
|
||||
files = ("foo.txt",)
|
||||
entry_points = (DummyEntryPoint(),)
|
||||
|
||||
monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter)
|
||||
def distributions():
|
||||
return (Distribution(),)
|
||||
|
||||
monkeypatch.setattr(importlib_metadata, "distributions", distributions)
|
||||
with pytest.raises(ImportError):
|
||||
testdir.parseconfig()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("block_it", [True, False])
|
||||
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)
|
||||
|
||||
plugin_module_placeholder = object()
|
||||
|
||||
def my_iter(group, name=None):
|
||||
assert group == "pytest11"
|
||||
|
||||
class Dist(object):
|
||||
project_name = "spam"
|
||||
version = "1.0"
|
||||
|
||||
def _get_metadata(self, name):
|
||||
return ["foo.txt,sha256=abc,123"]
|
||||
|
||||
class EntryPoint(object):
|
||||
class DummyEntryPoint(object):
|
||||
name = "mytestplugin"
|
||||
dist = Dist()
|
||||
group = "pytest11"
|
||||
|
||||
def load(self):
|
||||
return plugin_module_placeholder
|
||||
|
||||
return iter([EntryPoint()])
|
||||
class Distribution(object):
|
||||
version = "1.0"
|
||||
files = ("foo.txt",)
|
||||
entry_points = (DummyEntryPoint(),)
|
||||
|
||||
monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter)
|
||||
def distributions():
|
||||
return (Distribution(),)
|
||||
|
||||
monkeypatch.setattr(importlib_metadata, "distributions", distributions)
|
||||
args = ("-p", "no:mytestplugin") if block_it else ()
|
||||
config = testdir.parseconfig(*args)
|
||||
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)]
|
||||
)
|
||||
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):
|
||||
name = "mytestplugin"
|
||||
project_name = name = "mytestplugin"
|
||||
group = "pytest11"
|
||||
version = "1.0"
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
return self.name
|
||||
|
||||
def load(self):
|
||||
return sys.modules[self.name]
|
||||
|
||||
@property
|
||||
def dist(self):
|
||||
return self
|
||||
|
||||
def _get_metadata(self, *args):
|
||||
return []
|
||||
class Distribution(object):
|
||||
entry_points = (DummyEntryPoint(),)
|
||||
files = ()
|
||||
|
||||
class PseudoPlugin(object):
|
||||
x = 42
|
||||
|
||||
def distributions():
|
||||
return (Distribution(),)
|
||||
|
||||
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())
|
||||
config = testdir.parseconfig(*parse_args)
|
||||
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 print_function
|
||||
|
||||
import pkg_resources
|
||||
|
||||
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"]
|
||||
import importlib_metadata
|
||||
|
||||
|
||||
def test_pytest_entry_points_are_identical():
|
||||
entryMap = pkg_resources.get_entry_map("pytest")["console_scripts"]
|
||||
assert entryMap["pytest"].module_name == entryMap["py.test"].module_name
|
||||
dist = importlib_metadata.distribution("pytest")
|
||||
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")
|
||||
assert "1 <> 2" in snode.text
|
||||
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):
|
||||
@classmethod
|
||||
def set_trace(cls, *args, **kwargs):
|
||||
# Init _PdbWrapper to handle capturing.
|
||||
_pdb = cls._init_pdb(*args, **kwargs)
|
||||
# Init PytestPdbWrapper to handle capturing.
|
||||
_pdb = cls._init_pdb("set_trace", *args, **kwargs)
|
||||
|
||||
# Mock out pdb.Pdb.do_continue.
|
||||
import pdb
|
||||
pdb.Pdb.do_continue = lambda self, arg: None
|
||||
|
||||
print("=== SET_TRACE ===")
|
||||
print("===" + " SET_TRACE ===")
|
||||
assert input() == "debug set_trace()"
|
||||
|
||||
# Simulate _PdbWrapper.do_debug
|
||||
# Simulate PytestPdbWrapper.do_debug
|
||||
cls._recursive_debug += 1
|
||||
print("ENTERING RECURSIVE DEBUGGER")
|
||||
print("=== SET_TRACE_2 ===")
|
||||
print("===" + " SET_TRACE_2 ===")
|
||||
|
||||
assert input() == "c"
|
||||
_pdb.do_continue("")
|
||||
print("=== SET_TRACE_3 ===")
|
||||
print("===" + " SET_TRACE_3 ===")
|
||||
|
||||
# Simulate _PdbWrapper.do_debug
|
||||
# Simulate PytestPdbWrapper.do_debug
|
||||
print("LEAVING RECURSIVE DEBUGGER")
|
||||
cls._recursive_debug -= 1
|
||||
|
||||
print("=== SET_TRACE_4 ===")
|
||||
print("===" + " SET_TRACE_4 ===")
|
||||
assert input() == "c"
|
||||
_pdb.do_continue("")
|
||||
|
||||
def do_continue(self, arg):
|
||||
print("=== do_continue")
|
||||
# _PdbWrapper.do_continue("")
|
||||
|
||||
monkeypatch.setattr(_pytest.debugging, "pytestPDB", pytestPDBTest)
|
||||
|
||||
|
@ -677,7 +676,7 @@ class TestPDB(object):
|
|||
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 ===")
|
||||
before = child.before.decode("utf8")
|
||||
if not capture_arg:
|
||||
|
@ -827,7 +826,7 @@ class TestPDB(object):
|
|||
result.stdout.fnmatch_lines(["*NameError*xxx*", "*1 error*"])
|
||||
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(
|
||||
custom_pdb="""
|
||||
class CustomPdb(object):
|
||||
|
@ -1037,24 +1036,28 @@ def test_trace_after_runpytest(testdir):
|
|||
from _pytest.debugging import pytestPDB
|
||||
|
||||
def test_outer(testdir):
|
||||
from _pytest.debugging import pytestPDB
|
||||
|
||||
assert len(pytestPDB._saved) == 1
|
||||
|
||||
testdir.runpytest("-k test_inner")
|
||||
testdir.makepyfile(
|
||||
\"""
|
||||
from _pytest.debugging import pytestPDB
|
||||
|
||||
__import__('pdb').set_trace()
|
||||
|
||||
def test_inner(testdir):
|
||||
def test_inner():
|
||||
assert len(pytestPDB._saved) == 2
|
||||
print()
|
||||
print("test_inner_" + "end")
|
||||
\"""
|
||||
)
|
||||
|
||||
result = testdir.runpytest("-s", "-k", "test_inner")
|
||||
assert result.ret == 0
|
||||
|
||||
assert len(pytestPDB._saved) == 1
|
||||
"""
|
||||
)
|
||||
child = testdir.spawn_pytest("-p pytester %s -k test_outer" % p1)
|
||||
child.expect(r"\(Pdb")
|
||||
child.sendline("c")
|
||||
rest = child.read().decode("utf8")
|
||||
TestPDB.flush(child)
|
||||
assert child.exitstatus == 0, rest
|
||||
result = testdir.runpytest_subprocess("-s", "-p", "pytester", str(p1))
|
||||
result.stdout.fnmatch_lines(["test_inner_end"])
|
||||
assert result.ret == 0
|
||||
|
||||
|
||||
def test_quit_with_swallowed_SystemExit(testdir):
|
||||
|
@ -1139,14 +1142,14 @@ def test_pdbcls_via_local_module(testdir):
|
|||
p1 = testdir.makepyfile(
|
||||
"""
|
||||
def test():
|
||||
print("before_settrace")
|
||||
print("before_set_trace")
|
||||
__import__("pdb").set_trace()
|
||||
""",
|
||||
mypdb="""
|
||||
class Wrapped:
|
||||
class MyPdb:
|
||||
def set_trace(self, *args):
|
||||
print("settrace_called", args)
|
||||
print("set_trace_called", args)
|
||||
|
||||
def runcall(self, *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
|
||||
)
|
||||
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.
|
||||
result = testdir.runpytest(
|
||||
|
@ -1202,3 +1205,33 @@ def test_raises_bdbquit_with_eoferror(testdir):
|
|||
result = testdir.runpytest(str(p1))
|
||||
result.stdout.fnmatch_lines(["E *BdbQuit", "*= 1 failed in*"])
|
||||
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 stderr.splitlines() == [b"stderr"]
|
||||
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:
|
||||
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")
|
||||
reports = reprec.getreports("pytest_collectreport")
|
||||
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",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
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!
|
||||
|
||||
config.option.reportchars = "A"
|
||||
assert getreportopt(config) == "sxXwEfpP"
|
||||
assert getreportopt(config) == "PpsxXwEf"
|
||||
|
||||
|
||||
def test_terminalreporter_reportopt_addopts(testdir):
|
||||
|
|
Loading…
Reference in New Issue