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