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