commit
						6a43c8cd94
					
				
							
								
								
									
										2
									
								
								AUTHORS
								
								
								
								
							
							
						
						
									
										2
									
								
								AUTHORS
								
								
								
								
							|  | @ -194,6 +194,7 @@ Paweł Adamczak | ||||||
| Pedro Algarvio | Pedro Algarvio | ||||||
| Pieter Mulder | Pieter Mulder | ||||||
| Piotr Banaszkiewicz | Piotr Banaszkiewicz | ||||||
|  | Pulkit Goyal | ||||||
| Punyashloka Biswal | Punyashloka Biswal | ||||||
| Quentin Pradet | Quentin Pradet | ||||||
| Ralf Schmitt | Ralf Schmitt | ||||||
|  | @ -211,6 +212,7 @@ Ross Lawley | ||||||
| Russel Winder | Russel Winder | ||||||
| Ryan Wooden | Ryan Wooden | ||||||
| Samuel Dion-Girardeau | Samuel Dion-Girardeau | ||||||
|  | Samuel Searles-Bryant | ||||||
| Samuele Pedroni | Samuele Pedroni | ||||||
| Sankt Petersbug | Sankt Petersbug | ||||||
| Segev Finer | Segev Finer | ||||||
|  |  | ||||||
							
								
								
									
										133
									
								
								CHANGELOG.rst
								
								
								
								
							
							
						
						
									
										133
									
								
								CHANGELOG.rst
								
								
								
								
							|  | @ -18,6 +18,139 @@ with advance notice in the **Deprecations** section of releases. | ||||||
| 
 | 
 | ||||||
| .. towncrier release notes start | .. towncrier release notes start | ||||||
| 
 | 
 | ||||||
|  | pytest 4.5.0 (2019-05-11) | ||||||
|  | ========================= | ||||||
|  | 
 | ||||||
|  | Features | ||||||
|  | -------- | ||||||
|  | 
 | ||||||
|  | - `#4826 <https://github.com/pytest-dev/pytest/issues/4826>`_: A warning is now emitted when unknown marks are used as a decorator. | ||||||
|  |   This is often due to a typo, which can lead to silently broken tests. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#4907 <https://github.com/pytest-dev/pytest/issues/4907>`_: Show XFail reason as part of JUnitXML message field. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5013 <https://github.com/pytest-dev/pytest/issues/5013>`_: Messages from crash reports are displayed within test summaries now, truncated to the terminal width. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5023 <https://github.com/pytest-dev/pytest/issues/5023>`_: New flag ``--strict-markers`` that triggers an error when unknown markers (e.g. those not registered using the `markers option`_ in the configuration file) are used in the test suite. | ||||||
|  | 
 | ||||||
|  |   The existing ``--strict`` option has the same behavior currently, but can be augmented in the future for additional checks. | ||||||
|  | 
 | ||||||
|  |   .. _`markers option`: https://docs.pytest.org/en/latest/reference.html#confval-markers | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5026 <https://github.com/pytest-dev/pytest/issues/5026>`_: Assertion failure messages for sequences and dicts contain the number of different items now. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5034 <https://github.com/pytest-dev/pytest/issues/5034>`_: Improve reporting with ``--lf`` and ``--ff`` (run-last-failure). | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5035 <https://github.com/pytest-dev/pytest/issues/5035>`_: The ``--cache-show`` option/action accepts an optional glob to show only matching cache entries. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5059 <https://github.com/pytest-dev/pytest/issues/5059>`_: Standard input (stdin) can be given to pytester's ``Testdir.run()`` and ``Testdir.popen()``. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5068 <https://github.com/pytest-dev/pytest/issues/5068>`_: The ``-r`` option learnt about ``A`` to display all reports (including passed ones) in the short test summary. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5108 <https://github.com/pytest-dev/pytest/issues/5108>`_: The short test summary is displayed after passes with output (``-rP``). | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5172 <https://github.com/pytest-dev/pytest/issues/5172>`_: The ``--last-failed`` (``--lf``) option got smarter and will now skip entire files if all tests | ||||||
|  |   of that test file have passed in previous runs, greatly speeding up collection. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5177 <https://github.com/pytest-dev/pytest/issues/5177>`_: Introduce new specific warning ``PytestWarning`` subclasses to make it easier to filter warnings based on the class, rather than on the message. The new subclasses are: | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |   * ``PytestAssertRewriteWarning`` | ||||||
|  | 
 | ||||||
|  |   * ``PytestCacheWarning`` | ||||||
|  | 
 | ||||||
|  |   * ``PytestCollectionWarning`` | ||||||
|  | 
 | ||||||
|  |   * ``PytestConfigWarning`` | ||||||
|  | 
 | ||||||
|  |   * ``PytestUnhandledCoroutineWarning`` | ||||||
|  | 
 | ||||||
|  |   * ``PytestUnknownMarkWarning`` | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5202 <https://github.com/pytest-dev/pytest/issues/5202>`_: New ``record_testsuite_property`` session-scoped fixture allows users to log ``<property>`` tags at the ``testsuite`` | ||||||
|  |   level with the ``junitxml`` plugin. | ||||||
|  | 
 | ||||||
|  |   The generated XML is compatible with the latest xunit standard, contrary to | ||||||
|  |   the properties recorded by ``record_property`` and ``record_xml_attribute``. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5214 <https://github.com/pytest-dev/pytest/issues/5214>`_: The default logging format has been changed to improve readability. Here is an | ||||||
|  |   example of a previous logging message:: | ||||||
|  | 
 | ||||||
|  |       test_log_cli_enabled_disabled.py    3 CRITICAL critical message logged by test | ||||||
|  | 
 | ||||||
|  |   This has now become:: | ||||||
|  | 
 | ||||||
|  |       CRITICAL root:test_log_cli_enabled_disabled.py:3 critical message logged by test | ||||||
|  | 
 | ||||||
|  |   The formatting can be changed through the `log_format <https://docs.pytest.org/en/latest/reference.html#confval-log_format>`__ configuration option. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5220 <https://github.com/pytest-dev/pytest/issues/5220>`_: ``--fixtures`` now also shows fixture scope for scopes other than ``"function"``. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Bug Fixes | ||||||
|  | --------- | ||||||
|  | 
 | ||||||
|  | - `#5113 <https://github.com/pytest-dev/pytest/issues/5113>`_: Deselected items from plugins using ``pytest_collect_modifyitems`` as a hookwrapper are correctly reported now. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5144 <https://github.com/pytest-dev/pytest/issues/5144>`_: With usage errors ``exitstatus`` is set to ``EXIT_USAGEERROR`` in the ``pytest_sessionfinish`` hook now as expected. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5235 <https://github.com/pytest-dev/pytest/issues/5235>`_: ``outcome.exit`` is not used with ``EOF`` in the pdb wrapper anymore, but only with ``quit``. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Improved Documentation | ||||||
|  | ---------------------- | ||||||
|  | 
 | ||||||
|  | - `#4935 <https://github.com/pytest-dev/pytest/issues/4935>`_: Expand docs on registering marks and the effect of ``--strict``. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Trivial/Internal Changes | ||||||
|  | ------------------------ | ||||||
|  | 
 | ||||||
|  | - `#4942 <https://github.com/pytest-dev/pytest/issues/4942>`_: ``logging.raiseExceptions`` is not set to ``False`` anymore. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5013 <https://github.com/pytest-dev/pytest/issues/5013>`_: pytest now depends on `wcwidth <https://pypi.org/project/wcwidth>`__ to properly track unicode character sizes for more precise terminal output. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5059 <https://github.com/pytest-dev/pytest/issues/5059>`_: pytester's ``Testdir.popen()`` uses ``stdout`` and ``stderr`` via keyword arguments with defaults now (``subprocess.PIPE``). | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5069 <https://github.com/pytest-dev/pytest/issues/5069>`_: The code for the short test summary in the terminal was moved to the terminal plugin. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5082 <https://github.com/pytest-dev/pytest/issues/5082>`_: Improved validation of kwargs for various methods in the pytester plugin. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5202 <https://github.com/pytest-dev/pytest/issues/5202>`_: ``record_property`` now emits a ``PytestWarning`` when used with ``junit_family=xunit2``: the fixture generates | ||||||
|  |   ``property`` tags as children of ``testcase``, which is not permitted according to the most | ||||||
|  |   `recent schema <https://github.com/jenkinsci/xunit-plugin/blob/master/ | ||||||
|  |   src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd>`__. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | - `#5239 <https://github.com/pytest-dev/pytest/issues/5239>`_: Pin ``pluggy`` to ``< 1.0`` so we don't update to ``1.0`` automatically when | ||||||
|  |   it gets released: there are planned breaking changes, and we want to ensure | ||||||
|  |   pytest properly supports ``pluggy 1.0``. | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| pytest 4.4.2 (2019-05-08) | pytest 4.4.2 (2019-05-08) | ||||||
| ========================= | ========================= | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,3 +0,0 @@ | ||||||
| Pin ``pluggy`` to ``< 1.0`` so we don't update to ``1.0`` automatically when |  | ||||||
| it gets released: there are planned breaking changes, and we want to ensure |  | ||||||
| pytest properly supports ``pluggy 1.0``. |  | ||||||
|  | @ -43,7 +43,7 @@ clean: | ||||||
| 
 | 
 | ||||||
| regen: REGENDOC_FILES:=*.rst */*.rst | regen: REGENDOC_FILES:=*.rst */*.rst | ||||||
| regen: | regen: | ||||||
| 	PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS=-pno:hypothesis COLUMNS=76 regendoc --update ${REGENDOC_FILES} ${REGENDOC_ARGS} | 	PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS="-pno:hypothesis -Wignore::pytest.PytestUnknownMarkWarning" COLUMNS=76 regendoc --update ${REGENDOC_FILES} ${REGENDOC_ARGS} | ||||||
| 
 | 
 | ||||||
| html: | html: | ||||||
| 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html | 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ Release announcements | ||||||
|    :maxdepth: 2 |    :maxdepth: 2 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |    release-4.5.0 | ||||||
|    release-4.4.2 |    release-4.4.2 | ||||||
|    release-4.4.1 |    release-4.4.1 | ||||||
|    release-4.4.0 |    release-4.4.0 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,35 @@ | ||||||
|  | pytest-4.5.0 | ||||||
|  | ======================================= | ||||||
|  | 
 | ||||||
|  | The pytest team is proud to announce the 4.5.0 release! | ||||||
|  | 
 | ||||||
|  | pytest is a mature Python testing tool with more than a 2000 tests | ||||||
|  | against itself, passing on many different interpreters and platforms. | ||||||
|  | 
 | ||||||
|  | This release contains a number of bugs fixes and improvements, so users are encouraged | ||||||
|  | to take a look at the CHANGELOG: | ||||||
|  | 
 | ||||||
|  |     https://docs.pytest.org/en/latest/changelog.html | ||||||
|  | 
 | ||||||
|  | For complete documentation, please visit: | ||||||
|  | 
 | ||||||
|  |     https://docs.pytest.org/en/latest/ | ||||||
|  | 
 | ||||||
|  | As usual, you can upgrade from pypi via: | ||||||
|  | 
 | ||||||
|  |     pip install -U pytest | ||||||
|  | 
 | ||||||
|  | Thanks to all who contributed to this release, among them: | ||||||
|  | 
 | ||||||
|  | * Anthony Sottile | ||||||
|  | * Bruno Oliveira | ||||||
|  | * Daniel Hahler | ||||||
|  | * Floris Bruynooghe | ||||||
|  | * Pulkit Goyal | ||||||
|  | * Samuel Searles-Bryant | ||||||
|  | * Zac Hatfield-Dodds | ||||||
|  | * Zac-HD | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | Happy testing, | ||||||
|  | The Pytest Development Team | ||||||
|  | @ -27,33 +27,39 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a | ||||||
|         name of your plugin or application to avoid clashes with other cache users. |         name of your plugin or application to avoid clashes with other cache users. | ||||||
| 
 | 
 | ||||||
|         Values can be any object handled by the json stdlib module. |         Values can be any object handled by the json stdlib module. | ||||||
|  | 
 | ||||||
|     capsys |     capsys | ||||||
|         Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. |         Enable text capturing of writes to ``sys.stdout`` and ``sys.stderr``. | ||||||
| 
 | 
 | ||||||
|         The captured output is made available via ``capsys.readouterr()`` method |         The captured output is made available via ``capsys.readouterr()`` method | ||||||
|         calls, which return a ``(out, err)`` namedtuple. |         calls, which return a ``(out, err)`` namedtuple. | ||||||
|         ``out`` and ``err`` will be ``text`` objects. |         ``out`` and ``err`` will be ``text`` objects. | ||||||
|  | 
 | ||||||
|     capsysbinary |     capsysbinary | ||||||
|         Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. |         Enable bytes capturing of writes to ``sys.stdout`` and ``sys.stderr``. | ||||||
| 
 | 
 | ||||||
|         The captured output is made available via ``capsysbinary.readouterr()`` |         The captured output is made available via ``capsysbinary.readouterr()`` | ||||||
|         method calls, which return a ``(out, err)`` namedtuple. |         method calls, which return a ``(out, err)`` namedtuple. | ||||||
|         ``out`` and ``err`` will be ``bytes`` objects. |         ``out`` and ``err`` will be ``bytes`` objects. | ||||||
|  | 
 | ||||||
|     capfd |     capfd | ||||||
|         Enable text capturing of writes to file descriptors ``1`` and ``2``. |         Enable text capturing of writes to file descriptors ``1`` and ``2``. | ||||||
| 
 | 
 | ||||||
|         The captured output is made available via ``capfd.readouterr()`` method |         The captured output is made available via ``capfd.readouterr()`` method | ||||||
|         calls, which return a ``(out, err)`` namedtuple. |         calls, which return a ``(out, err)`` namedtuple. | ||||||
|         ``out`` and ``err`` will be ``text`` objects. |         ``out`` and ``err`` will be ``text`` objects. | ||||||
|  | 
 | ||||||
|     capfdbinary |     capfdbinary | ||||||
|         Enable bytes capturing of writes to file descriptors ``1`` and ``2``. |         Enable bytes capturing of writes to file descriptors ``1`` and ``2``. | ||||||
| 
 | 
 | ||||||
|         The captured output is made available via ``capfd.readouterr()`` method |         The captured output is made available via ``capfd.readouterr()`` method | ||||||
|         calls, which return a ``(out, err)`` namedtuple. |         calls, which return a ``(out, err)`` namedtuple. | ||||||
|         ``out`` and ``err`` will be ``byte`` objects. |         ``out`` and ``err`` will be ``byte`` objects. | ||||||
|     doctest_namespace | 
 | ||||||
|  |     doctest_namespace [session scope] | ||||||
|         Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. |         Fixture that returns a :py:class:`dict` that will be injected into the namespace of doctests. | ||||||
|     pytestconfig | 
 | ||||||
|  |     pytestconfig [session scope] | ||||||
|         Session-scoped fixture that returns the :class:`_pytest.config.Config` object. |         Session-scoped fixture that returns the :class:`_pytest.config.Config` object. | ||||||
| 
 | 
 | ||||||
|         Example:: |         Example:: | ||||||
|  | @ -61,6 +67,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a | ||||||
|             def test_foo(pytestconfig): |             def test_foo(pytestconfig): | ||||||
|                 if pytestconfig.getoption("verbose") > 0: |                 if pytestconfig.getoption("verbose") > 0: | ||||||
|                     ... |                     ... | ||||||
|  | 
 | ||||||
|     record_property |     record_property | ||||||
|         Add an extra properties the calling test. |         Add an extra properties the calling test. | ||||||
|         User properties become part of the test report and are available to the |         User properties become part of the test report and are available to the | ||||||
|  | @ -72,10 +79,26 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a | ||||||
| 
 | 
 | ||||||
|             def test_function(record_property): |             def test_function(record_property): | ||||||
|                 record_property("example_key", 1) |                 record_property("example_key", 1) | ||||||
|  | 
 | ||||||
|     record_xml_attribute |     record_xml_attribute | ||||||
|         Add extra xml attributes to the tag for the calling test. |         Add extra xml attributes to the tag for the calling test. | ||||||
|         The fixture is callable with ``(name, value)``, with value being |         The fixture is callable with ``(name, value)``, with value being | ||||||
|         automatically xml-encoded |         automatically xml-encoded | ||||||
|  | 
 | ||||||
|  |     record_testsuite_property [session scope] | ||||||
|  |         Records a new ``<property>`` tag as child of the root ``<testsuite>``. This is suitable to | ||||||
|  |         writing global information regarding the entire test suite, and is compatible with ``xunit2`` JUnit family. | ||||||
|  | 
 | ||||||
|  |         This is a ``session``-scoped fixture which is called with ``(name, value)``. Example: | ||||||
|  | 
 | ||||||
|  |         .. code-block:: python | ||||||
|  | 
 | ||||||
|  |             def test_foo(record_testsuite_property): | ||||||
|  |                 record_testsuite_property("ARCH", "PPC") | ||||||
|  |                 record_testsuite_property("STORAGE_TYPE", "CEPH") | ||||||
|  | 
 | ||||||
|  |         ``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped. | ||||||
|  | 
 | ||||||
|     caplog |     caplog | ||||||
|         Access and control log capturing. |         Access and control log capturing. | ||||||
| 
 | 
 | ||||||
|  | @ -85,6 +108,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a | ||||||
|         * caplog.records         -> list of logging.LogRecord instances |         * caplog.records         -> list of logging.LogRecord instances | ||||||
|         * caplog.record_tuples   -> list of (logger_name, level, message) tuples |         * caplog.record_tuples   -> list of (logger_name, level, message) tuples | ||||||
|         * caplog.clear()         -> clear captured records and formatted log output string |         * caplog.clear()         -> clear captured records and formatted log output string | ||||||
|  | 
 | ||||||
|     monkeypatch |     monkeypatch | ||||||
|         The returned ``monkeypatch`` fixture provides these |         The returned ``monkeypatch`` fixture provides these | ||||||
|         helper methods to modify objects, dictionaries or os.environ:: |         helper methods to modify objects, dictionaries or os.environ:: | ||||||
|  | @ -102,15 +126,19 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a | ||||||
|         test function or fixture has finished. The ``raising`` |         test function or fixture has finished. The ``raising`` | ||||||
|         parameter determines if a KeyError or AttributeError |         parameter determines if a KeyError or AttributeError | ||||||
|         will be raised if the set/deletion operation has no target. |         will be raised if the set/deletion operation has no target. | ||||||
|  | 
 | ||||||
|     recwarn |     recwarn | ||||||
|         Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. |         Return a :class:`WarningsRecorder` instance that records all warnings emitted by test functions. | ||||||
| 
 | 
 | ||||||
|         See http://docs.python.org/library/warnings.html for information |         See http://docs.python.org/library/warnings.html for information | ||||||
|         on warning categories. |         on warning categories. | ||||||
|     tmpdir_factory | 
 | ||||||
|  |     tmpdir_factory [session scope] | ||||||
|         Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session. |         Return a :class:`_pytest.tmpdir.TempdirFactory` instance for the test session. | ||||||
|     tmp_path_factory | 
 | ||||||
|  |     tmp_path_factory [session scope] | ||||||
|         Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session. |         Return a :class:`_pytest.tmpdir.TempPathFactory` instance for the test session. | ||||||
|  | 
 | ||||||
|     tmpdir |     tmpdir | ||||||
|         Return a temporary directory path object |         Return a temporary directory path object | ||||||
|         which is unique to each test function invocation, |         which is unique to each test function invocation, | ||||||
|  | @ -119,6 +147,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a | ||||||
|         path object. |         path object. | ||||||
| 
 | 
 | ||||||
|         .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html |         .. _`py.path.local`: https://py.readthedocs.io/en/latest/path.html | ||||||
|  | 
 | ||||||
|     tmp_path |     tmp_path | ||||||
|         Return a temporary directory path object |         Return a temporary directory path object | ||||||
|         which is unique to each test function invocation, |         which is unique to each test function invocation, | ||||||
|  | @ -130,6 +159,7 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a | ||||||
| 
 | 
 | ||||||
|             in python < 3.6 this is a pathlib2.Path |             in python < 3.6 this is a pathlib2.Path | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     no tests ran in 0.12 seconds |     no tests ran in 0.12 seconds | ||||||
| 
 | 
 | ||||||
| You can also interactively ask for help, e.g. by typing on the Python interactive prompt something like:: | You can also interactively ask for help, e.g. by typing on the Python interactive prompt something like:: | ||||||
|  |  | ||||||
|  | @ -247,7 +247,7 @@ See the :ref:`cache-api` for more details. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Inspecting Cache content | Inspecting Cache content | ||||||
| ------------------------------- | ------------------------ | ||||||
| 
 | 
 | ||||||
| You can always peek at the content of the cache using the | You can always peek at the content of the cache using the | ||||||
| ``--cache-show`` command line option: | ``--cache-show`` command line option: | ||||||
|  | @ -260,7 +260,7 @@ You can always peek at the content of the cache using the | ||||||
|     cachedir: $PYTHON_PREFIX/.pytest_cache |     cachedir: $PYTHON_PREFIX/.pytest_cache | ||||||
|     rootdir: $REGENDOC_TMPDIR |     rootdir: $REGENDOC_TMPDIR | ||||||
|     cachedir: $PYTHON_PREFIX/.pytest_cache |     cachedir: $PYTHON_PREFIX/.pytest_cache | ||||||
|     ------------------------------- cache values ------------------------------- |     --------------------------- cache values for '*' --------------------------- | ||||||
|     cache/lastfailed contains: |     cache/lastfailed contains: | ||||||
|       {'test_50.py::test_num[17]': True, |       {'test_50.py::test_num[17]': True, | ||||||
|        'test_50.py::test_num[25]': True, |        'test_50.py::test_num[25]': True, | ||||||
|  | @ -277,8 +277,25 @@ You can always peek at the content of the cache using the | ||||||
| 
 | 
 | ||||||
|     ======================= no tests ran in 0.12 seconds ======================= |     ======================= no tests ran in 0.12 seconds ======================= | ||||||
| 
 | 
 | ||||||
|  | ``--cache-show`` takes an optional argument to specify a glob pattern for | ||||||
|  | filtering: | ||||||
|  | 
 | ||||||
|  | .. code-block:: pytest | ||||||
|  | 
 | ||||||
|  |     $ pytest --cache-show example/* | ||||||
|  |     =========================== test session starts ============================ | ||||||
|  |     platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y | ||||||
|  |     cachedir: $PYTHON_PREFIX/.pytest_cache | ||||||
|  |     rootdir: $REGENDOC_TMPDIR | ||||||
|  |     cachedir: $PYTHON_PREFIX/.pytest_cache | ||||||
|  |     ----------------------- cache values for 'example/*' ----------------------- | ||||||
|  |     example/value contains: | ||||||
|  |       42 | ||||||
|  | 
 | ||||||
|  |     ======================= no tests ran in 0.12 seconds ======================= | ||||||
|  | 
 | ||||||
| Clearing Cache content | Clearing Cache content | ||||||
| ------------------------------- | ---------------------- | ||||||
| 
 | 
 | ||||||
| You can instruct pytest to clear all cache files and values | You can instruct pytest to clear all cache files and values | ||||||
| by adding the ``--cache-clear`` option like this: | by adding the ``--cache-clear`` option like this: | ||||||
|  |  | ||||||
|  | @ -259,7 +259,7 @@ For an example on how to add and work with markers from a plugin, see | ||||||
|     * Asking for existing markers via ``pytest --markers`` gives good output |     * Asking for existing markers via ``pytest --markers`` gives good output | ||||||
| 
 | 
 | ||||||
|     * Typos in function markers are treated as an error if you use |     * Typos in function markers are treated as an error if you use | ||||||
|       the ``--strict`` option. |       the ``--strict-markers`` option. | ||||||
| 
 | 
 | ||||||
| .. _`scoped-marking`: | .. _`scoped-marking`: | ||||||
| 
 | 
 | ||||||
|  | @ -619,9 +619,9 @@ then you will see two tests skipped and two executed tests as expected: | ||||||
|     collected 4 items |     collected 4 items | ||||||
| 
 | 
 | ||||||
|     test_plat.py s.s.                                                    [100%] |     test_plat.py s.s.                                                    [100%] | ||||||
|  | 
 | ||||||
|     ========================= short test summary info ========================== |     ========================= short test summary info ========================== | ||||||
|     SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:13: cannot run on platform linux |     SKIPPED [2] $REGENDOC_TMPDIR/conftest.py:13: cannot run on platform linux | ||||||
| 
 |  | ||||||
|     =================== 2 passed, 2 skipped in 0.12 seconds ==================== |     =================== 2 passed, 2 skipped in 0.12 seconds ==================== | ||||||
| 
 | 
 | ||||||
| Note that if you specify a platform via the marker-command line option like this: | Note that if you specify a platform via the marker-command line option like this: | ||||||
|  |  | ||||||
|  | @ -492,9 +492,9 @@ If you run this with reporting for skips enabled: | ||||||
|     collected 2 items |     collected 2 items | ||||||
| 
 | 
 | ||||||
|     test_module.py .s                                                    [100%] |     test_module.py .s                                                    [100%] | ||||||
|  | 
 | ||||||
|     ========================= short test summary info ========================== |     ========================= short test summary info ========================== | ||||||
|     SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2' |     SKIPPED [1] $REGENDOC_TMPDIR/conftest.py:11: could not import 'opt2' | ||||||
| 
 |  | ||||||
|     =================== 1 passed, 1 skipped in 0.12 seconds ==================== |     =================== 1 passed, 1 skipped in 0.12 seconds ==================== | ||||||
| 
 | 
 | ||||||
| You'll see that we don't have an ``opt2`` module and thus the second test run | You'll see that we don't have an ``opt2`` module and thus the second test run | ||||||
|  |  | ||||||
|  | @ -182,9 +182,9 @@ Here is a nice run of several failures and how ``pytest`` presents things: | ||||||
|     E         Omitting 1 identical items, use -vv to show |     E         Omitting 1 identical items, use -vv to show | ||||||
|     E         Differing items: |     E         Differing items: | ||||||
|     E         {'b': 1} != {'b': 2} |     E         {'b': 1} != {'b': 2} | ||||||
|     E         Left contains more items: |     E         Left contains 1 more item: | ||||||
|     E         {'c': 0} |     E         {'c': 0} | ||||||
|     E         Right contains more items: |     E         Right contains 1 more item: | ||||||
|     E         {'d': 0}... |     E         {'d': 0}... | ||||||
|     E |     E | ||||||
|     E         ...Full output truncated (2 lines hidden), use '-vv' to show |     E         ...Full output truncated (2 lines hidden), use '-vv' to show | ||||||
|  | @ -215,7 +215,7 @@ Here is a nice run of several failures and how ``pytest`` presents things: | ||||||
|         def test_eq_longer_list(self): |         def test_eq_longer_list(self): | ||||||
|     >       assert [1, 2] == [1, 2, 3] |     >       assert [1, 2] == [1, 2, 3] | ||||||
|     E       assert [1, 2] == [1, 2, 3] |     E       assert [1, 2] == [1, 2, 3] | ||||||
|     E         Right contains more items, first extra item: 3 |     E         Right contains one more item: 3 | ||||||
|     E         Use -v to get the full diff |     E         Use -v to get the full diff | ||||||
| 
 | 
 | ||||||
|     failure_demo.py:78: AssertionError |     failure_demo.py:78: AssertionError | ||||||
|  |  | ||||||
|  | @ -194,9 +194,9 @@ and when running it will see a skipped "slow" test: | ||||||
|     collected 2 items |     collected 2 items | ||||||
| 
 | 
 | ||||||
|     test_module.py .s                                                    [100%] |     test_module.py .s                                                    [100%] | ||||||
|  | 
 | ||||||
|     ========================= short test summary info ========================== |     ========================= short test summary info ========================== | ||||||
|     SKIPPED [1] test_module.py:8: need --runslow option to run |     SKIPPED [1] test_module.py:8: need --runslow option to run | ||||||
| 
 |  | ||||||
|     =================== 1 passed, 1 skipped in 0.12 seconds ==================== |     =================== 1 passed, 1 skipped in 0.12 seconds ==================== | ||||||
| 
 | 
 | ||||||
| Or run it including the ``slow`` marked test: | Or run it including the ``slow`` marked test: | ||||||
|  | @ -606,7 +606,7 @@ We can run this: | ||||||
|     file $REGENDOC_TMPDIR/b/test_error.py, line 1 |     file $REGENDOC_TMPDIR/b/test_error.py, line 1 | ||||||
|       def test_root(db):  # no db here, will error out |       def test_root(db):  # no db here, will error out | ||||||
|     E       fixture 'db' not found |     E       fixture 'db' not found | ||||||
|     >       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory |     >       available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_testsuite_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory | ||||||
|     >       use 'pytest --fixtures [testpath]' for help on them. |     >       use 'pytest --fixtures [testpath]' for help on them. | ||||||
| 
 | 
 | ||||||
|     $REGENDOC_TMPDIR/b/test_error.py:1 |     $REGENDOC_TMPDIR/b/test_error.py:1 | ||||||
|  |  | ||||||
|  | @ -1,9 +1,7 @@ | ||||||
| 
 |  | ||||||
| .. _mark: | .. _mark: | ||||||
| 
 | 
 | ||||||
| Marking test functions with attributes | Marking test functions with attributes | ||||||
| ================================================================= | ====================================== | ||||||
| 
 |  | ||||||
| 
 | 
 | ||||||
| By using the ``pytest.mark`` helper you can easily set | By using the ``pytest.mark`` helper you can easily set | ||||||
| metadata on your test functions. There are | metadata on your test functions. There are | ||||||
|  | @ -26,32 +24,38 @@ which also serve as documentation. | ||||||
|     :ref:`fixtures <fixtures>`. |     :ref:`fixtures <fixtures>`. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| Raising errors on unknown marks: --strict | .. _unknown-marks: | ||||||
| ----------------------------------------- |  | ||||||
| 
 | 
 | ||||||
| When the ``--strict`` command-line flag is passed, any unknown marks applied | Raising errors on unknown marks | ||||||
|  | ------------------------------- | ||||||
|  | 
 | ||||||
|  | Unknown marks applied with the ``@pytest.mark.name_of_the_mark`` decorator | ||||||
|  | will always emit a warning, in order to avoid silently doing something | ||||||
|  | surprising due to mis-typed names.  You can disable the warning for custom | ||||||
|  | marks by registering them in ``pytest.ini`` like this: | ||||||
|  | 
 | ||||||
|  | .. code-block:: ini | ||||||
|  | 
 | ||||||
|  |     [pytest] | ||||||
|  |     markers = | ||||||
|  |         slow | ||||||
|  |         serial | ||||||
|  | 
 | ||||||
|  | When the ``--strict-markers`` command-line flag is passed, any unknown marks applied | ||||||
| with the ``@pytest.mark.name_of_the_mark`` decorator will trigger an error. | with the ``@pytest.mark.name_of_the_mark`` decorator will trigger an error. | ||||||
| Marks defined or added by pytest or by a plugin will not trigger an error. | Marks added by pytest or by a plugin instead of the decorator will not trigger | ||||||
| 
 | this error.  To enforce validation of markers, add ``--strict-markers`` to ``addopts``: | ||||||
| Marks can be registered in ``pytest.ini`` like this: |  | ||||||
| 
 | 
 | ||||||
| .. code-block:: ini | .. code-block:: ini | ||||||
| 
 | 
 | ||||||
|     [pytest] |     [pytest] | ||||||
|  |     addopts = --strict-markers | ||||||
|     markers = |     markers = | ||||||
|         slow |         slow | ||||||
|         serial |         serial | ||||||
| 
 | 
 | ||||||
| This can be used to prevent users mistyping mark names by accident. Test suites that want to enforce this | Third-party plugins should always :ref:`register their markers <registering-markers>` | ||||||
| should add ``--strict`` to ``addopts``: | so that they appear in pytest's help text and do not emit warnings. | ||||||
| 
 |  | ||||||
| .. code-block:: ini |  | ||||||
| 
 |  | ||||||
|     [pytest] |  | ||||||
|     addopts = --strict |  | ||||||
|     markers = |  | ||||||
|         slow |  | ||||||
|         serial |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| .. _marker-revamp: | .. _marker-revamp: | ||||||
|  | @ -158,4 +162,4 @@ More details can be found in the `original PR <https://github.com/pytest-dev/pyt | ||||||
| .. note:: | .. note:: | ||||||
| 
 | 
 | ||||||
|     in a future major relase of pytest we will introduce class based markers, |     in a future major relase of pytest we will introduce class based markers, | ||||||
|     at which point markers will no longer be limited to instances of :py:class:`Mark` |     at which point markers will no longer be limited to instances of :py:class:`Mark`. | ||||||
|  |  | ||||||
|  | @ -424,6 +424,14 @@ record_property | ||||||
| 
 | 
 | ||||||
| .. autofunction:: _pytest.junitxml.record_property() | .. autofunction:: _pytest.junitxml.record_property() | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | record_testsuite_property | ||||||
|  | ~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  | 
 | ||||||
|  | **Tutorial**: :ref:`record_testsuite_property example`. | ||||||
|  | 
 | ||||||
|  | .. autofunction:: _pytest.junitxml.record_testsuite_property() | ||||||
|  | 
 | ||||||
| caplog | caplog | ||||||
| ~~~~~~ | ~~~~~~ | ||||||
| 
 | 
 | ||||||
|  | @ -1261,15 +1269,17 @@ passed multiple times. The expected format is ``name=value``. For example:: | ||||||
| 
 | 
 | ||||||
| .. confval:: markers | .. confval:: markers | ||||||
| 
 | 
 | ||||||
|     When the ``--strict`` command-line argument is used, only known markers - |     When the ``--strict-markers`` or ``--strict`` command-line arguments are used, | ||||||
|     defined in code by core pytest or some plugin - are allowed. |     only known markers - defined in code by core pytest or some plugin - are allowed. | ||||||
|     You can list additional markers in this setting to add them to the whitelist. |  | ||||||
| 
 | 
 | ||||||
|     You can list one marker name per line, indented from the option name. |     You can list additional markers in this setting to add them to the whitelist, | ||||||
|  |     in which case you probably want to add ``--strict-markers`` to ``addopts`` | ||||||
|  |     to avoid future regressions: | ||||||
| 
 | 
 | ||||||
|     .. code-block:: ini |     .. code-block:: ini | ||||||
| 
 | 
 | ||||||
|         [pytest] |         [pytest] | ||||||
|  |         addopts = --strict-markers | ||||||
|         markers = |         markers = | ||||||
|             slow |             slow | ||||||
|             serial |             serial | ||||||
|  |  | ||||||
|  | @ -352,6 +352,7 @@ Running it with the report-on-xfail option gives this output: | ||||||
|     collected 7 items |     collected 7 items | ||||||
| 
 | 
 | ||||||
|     xfail_demo.py xxxxxxx                                                [100%] |     xfail_demo.py xxxxxxx                                                [100%] | ||||||
|  | 
 | ||||||
|     ========================= short test summary info ========================== |     ========================= short test summary info ========================== | ||||||
|     XFAIL xfail_demo.py::test_hello |     XFAIL xfail_demo.py::test_hello | ||||||
|     XFAIL xfail_demo.py::test_hello2 |     XFAIL xfail_demo.py::test_hello2 | ||||||
|  | @ -365,7 +366,6 @@ Running it with the report-on-xfail option gives this output: | ||||||
|     XFAIL xfail_demo.py::test_hello6 |     XFAIL xfail_demo.py::test_hello6 | ||||||
|       reason: reason |       reason: reason | ||||||
|     XFAIL xfail_demo.py::test_hello7 |     XFAIL xfail_demo.py::test_hello7 | ||||||
| 
 |  | ||||||
|     ======================== 7 xfailed in 0.12 seconds ========================= |     ======================== 7 xfailed in 0.12 seconds ========================= | ||||||
| 
 | 
 | ||||||
| .. _`skip/xfail with parametrize`: | .. _`skip/xfail with parametrize`: | ||||||
|  |  | ||||||
|  | @ -231,11 +231,12 @@ Example: | ||||||
|     XFAIL test_example.py::test_xfail |     XFAIL test_example.py::test_xfail | ||||||
|       reason: xfailing this test |       reason: xfailing this test | ||||||
|     XPASS test_example.py::test_xpass always xfail |     XPASS test_example.py::test_xpass always xfail | ||||||
|     ERROR test_example.py::test_error |     ERROR test_example.py::test_error - assert 0 | ||||||
|     FAILED test_example.py::test_fail |     FAILED test_example.py::test_fail - assert 0 | ||||||
|     = 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds = |     = 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds = | ||||||
| 
 | 
 | ||||||
| The ``-r`` options accepts a number of characters after it, with ``a`` used above meaning "all except passes". | The ``-r`` options accepts a number of characters after it, with ``a`` used | ||||||
|  | above meaning "all except passes". | ||||||
| 
 | 
 | ||||||
| Here is the full list of available characters that can be used: | Here is the full list of available characters that can be used: | ||||||
| 
 | 
 | ||||||
|  | @ -247,6 +248,7 @@ Here is the full list of available characters that can be used: | ||||||
|  - ``p`` - passed |  - ``p`` - passed | ||||||
|  - ``P`` - passed with output |  - ``P`` - passed with output | ||||||
|  - ``a`` - all except ``pP`` |  - ``a`` - all except ``pP`` | ||||||
|  |  - ``A`` - all | ||||||
| 
 | 
 | ||||||
| More than one character can be used, so for example to only see failed and skipped tests, you can execute: | More than one character can be used, so for example to only see failed and skipped tests, you can execute: | ||||||
| 
 | 
 | ||||||
|  | @ -279,7 +281,7 @@ More than one character can be used, so for example to only see failed and skipp | ||||||
| 
 | 
 | ||||||
|     test_example.py:14: AssertionError |     test_example.py:14: AssertionError | ||||||
|     ========================= short test summary info ========================== |     ========================= short test summary info ========================== | ||||||
|     FAILED test_example.py::test_fail |     FAILED test_example.py::test_fail - assert 0 | ||||||
|     SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test |     SKIPPED [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test | ||||||
|     = 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds = |     = 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds = | ||||||
| 
 | 
 | ||||||
|  | @ -314,12 +316,12 @@ captured output: | ||||||
|     E       assert 0 |     E       assert 0 | ||||||
| 
 | 
 | ||||||
|     test_example.py:14: AssertionError |     test_example.py:14: AssertionError | ||||||
|     ========================= short test summary info ========================== |  | ||||||
|     PASSED test_example.py::test_ok |  | ||||||
|     ================================== PASSES ================================== |     ================================== PASSES ================================== | ||||||
|     _________________________________ test_ok __________________________________ |     _________________________________ test_ok __________________________________ | ||||||
|     --------------------------- Captured stdout call --------------------------- |     --------------------------- Captured stdout call --------------------------- | ||||||
|     ok |     ok | ||||||
|  |     ========================= short test summary info ========================== | ||||||
|  |     PASSED test_example.py::test_ok | ||||||
|     = 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds = |     = 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds = | ||||||
| 
 | 
 | ||||||
| .. _pdb-option: | .. _pdb-option: | ||||||
|  | @ -456,13 +458,6 @@ instead, configure the ``junit_duration_report`` option like this: | ||||||
| record_property | record_property | ||||||
| ^^^^^^^^^^^^^^^ | ^^^^^^^^^^^^^^^ | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|    Fixture renamed from ``record_xml_property`` to ``record_property`` as user |  | ||||||
|    properties are now available to all reporters. |  | ||||||
|    ``record_xml_property`` is now deprecated. |  | ||||||
| 
 |  | ||||||
| If you want to log additional information for a test, you can use the | If you want to log additional information for a test, you can use the | ||||||
| ``record_property`` fixture: | ``record_property`` fixture: | ||||||
| 
 | 
 | ||||||
|  | @ -520,9 +515,7 @@ Will result in: | ||||||
| 
 | 
 | ||||||
| .. warning:: | .. warning:: | ||||||
| 
 | 
 | ||||||
|     ``record_property`` is an experimental feature and may change in the future. |     Please note that using this feature will break schema verifications for the latest JUnitXML schema. | ||||||
| 
 |  | ||||||
|     Also please note that using this feature will break any schema verification. |  | ||||||
|     This might be a problem when used with some CI servers. |     This might be a problem when used with some CI servers. | ||||||
| 
 | 
 | ||||||
| record_xml_attribute | record_xml_attribute | ||||||
|  | @ -585,43 +578,45 @@ Instead, this will add an attribute ``assertions="REQ-1234"`` inside the generat | ||||||
|             </xs:complexType> |             </xs:complexType> | ||||||
|         </xs:element> |         </xs:element> | ||||||
| 
 | 
 | ||||||
| LogXML: add_global_property | .. warning:: | ||||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ |  | ||||||
| 
 | 
 | ||||||
|  |     Please note that using this feature will break schema verifications for the latest JUnitXML schema. | ||||||
|  |     This might be a problem when used with some CI servers. | ||||||
| 
 | 
 | ||||||
|  | .. _record_testsuite_property example: | ||||||
| 
 | 
 | ||||||
| If you want to add a properties node in the testsuite level, which may contains properties that are relevant | record_testsuite_property | ||||||
| to all testcases you can use ``LogXML.add_global_properties`` | ^^^^^^^^^^^^^^^^^^^^^^^^^ | ||||||
|  | 
 | ||||||
|  | .. versionadded:: 4.5 | ||||||
|  | 
 | ||||||
|  | If you want to add a properties node at the test-suite level, which may contains properties | ||||||
|  | that are relevant to all tests, you can use the ``record_testsuite_property`` session-scoped fixture: | ||||||
|  | 
 | ||||||
|  | The ``record_testsuite_property`` session-scoped fixture can be used to add properties relevant | ||||||
|  | to all tests. | ||||||
| 
 | 
 | ||||||
| .. code-block:: python | .. code-block:: python | ||||||
| 
 | 
 | ||||||
|     import pytest |     import pytest | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     @pytest.fixture(scope="session") |     @pytest.fixture(scope="session", autouse=True) | ||||||
|     def log_global_env_facts(f): |     def log_global_env_facts(record_testsuite_property): | ||||||
| 
 |         record_testsuite_property("ARCH", "PPC") | ||||||
|         if pytest.config.pluginmanager.hasplugin("junitxml"): |         record_testsuite_property("STORAGE_TYPE", "CEPH") | ||||||
|             my_junit = getattr(pytest.config, "_xml", None) |  | ||||||
| 
 |  | ||||||
|         my_junit.add_global_property("ARCH", "PPC") |  | ||||||
|         my_junit.add_global_property("STORAGE_TYPE", "CEPH") |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|     @pytest.mark.usefixtures(log_global_env_facts.__name__) |  | ||||||
|     def start_and_prepare_env(): |  | ||||||
|         pass |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     class TestMe(object): |     class TestMe(object): | ||||||
|         def test_foo(self): |         def test_foo(self): | ||||||
|             assert True |             assert True | ||||||
| 
 | 
 | ||||||
| This will add a property node below the testsuite node to the generated xml: | The fixture is a callable which receives ``name`` and ``value`` of a ``<property>`` tag | ||||||
|  | added at the test-suite level of the generated xml: | ||||||
| 
 | 
 | ||||||
| .. code-block:: xml | .. code-block:: xml | ||||||
| 
 | 
 | ||||||
|     <testsuite errors="0" failures="0" name="pytest" skips="0" tests="1" time="0.006"> |     <testsuite errors="0" failures="0" name="pytest" skipped="0" tests="1" time="0.006"> | ||||||
|       <properties> |       <properties> | ||||||
|         <property name="ARCH" value="PPC"/> |         <property name="ARCH" value="PPC"/> | ||||||
|         <property name="STORAGE_TYPE" value="CEPH"/> |         <property name="STORAGE_TYPE" value="CEPH"/> | ||||||
|  | @ -629,11 +624,11 @@ This will add a property node below the testsuite node to the generated xml: | ||||||
|       <testcase classname="test_me.TestMe" file="test_me.py" line="16" name="test_foo" time="0.000243663787842"/> |       <testcase classname="test_me.TestMe" file="test_me.py" line="16" name="test_foo" time="0.000243663787842"/> | ||||||
|     </testsuite> |     </testsuite> | ||||||
| 
 | 
 | ||||||
| .. warning:: | ``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped. | ||||||
|  | 
 | ||||||
|  | The generated XML is compatible with the latest ``xunit`` standard, contrary to `record_property`_ | ||||||
|  | and `record_xml_attribute`_. | ||||||
| 
 | 
 | ||||||
|     This is an experimental feature, and its interface might be replaced |  | ||||||
|     by something more powerful and general in future versions. The |  | ||||||
|     functionality per-se will be kept. |  | ||||||
| 
 | 
 | ||||||
| Creating resultlog format files | Creating resultlog format files | ||||||
| ---------------------------------------------------- | ---------------------------------------------------- | ||||||
|  |  | ||||||
|  | @ -400,7 +400,7 @@ defines an ``__init__`` constructor, as this prevents the class from being insta | ||||||
| 
 | 
 | ||||||
|     ============================= warnings summary ============================= |     ============================= warnings summary ============================= | ||||||
|     test_pytest_warnings.py:1 |     test_pytest_warnings.py:1 | ||||||
|       $REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestWarning: cannot collect test class 'Test' because it has a __init__ constructor |       $REGENDOC_TMPDIR/test_pytest_warnings.py:1: PytestCollectionWarning: cannot collect test class 'Test' because it has a __init__ constructor | ||||||
|         class Test: |         class Test: | ||||||
| 
 | 
 | ||||||
|     -- Docs: https://docs.pytest.org/en/latest/warnings.html |     -- Docs: https://docs.pytest.org/en/latest/warnings.html | ||||||
|  | @ -415,8 +415,20 @@ The following warning types ares used by pytest and are part of the public API: | ||||||
| 
 | 
 | ||||||
| .. autoclass:: pytest.PytestWarning | .. autoclass:: pytest.PytestWarning | ||||||
| 
 | 
 | ||||||
|  | .. autoclass:: pytest.PytestAssertRewriteWarning | ||||||
|  | 
 | ||||||
|  | .. autoclass:: pytest.PytestCacheWarning | ||||||
|  | 
 | ||||||
|  | .. autoclass:: pytest.PytestCollectionWarning | ||||||
|  | 
 | ||||||
|  | .. autoclass:: pytest.PytestConfigWarning | ||||||
|  | 
 | ||||||
| .. autoclass:: pytest.PytestDeprecationWarning | .. autoclass:: pytest.PytestDeprecationWarning | ||||||
| 
 | 
 | ||||||
| .. autoclass:: pytest.RemovedInPytest4Warning |  | ||||||
| 
 |  | ||||||
| .. autoclass:: pytest.PytestExperimentalApiWarning | .. autoclass:: pytest.PytestExperimentalApiWarning | ||||||
|  | 
 | ||||||
|  | .. autoclass:: pytest.PytestUnhandledCoroutineWarning | ||||||
|  | 
 | ||||||
|  | .. autoclass:: pytest.PytestUnknownMarkWarning | ||||||
|  | 
 | ||||||
|  | .. autoclass:: pytest.RemovedInPytest4Warning | ||||||
|  |  | ||||||
|  | @ -223,7 +223,6 @@ import ``helper.py`` normally.  The contents of | ||||||
|    pytest.register_assert_rewrite("pytest_foo.helper") |    pytest.register_assert_rewrite("pytest_foo.helper") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| Requiring/Loading plugins in a test module or conftest file | Requiring/Loading plugins in a test module or conftest file | ||||||
| ----------------------------------------------------------- | ----------------------------------------------------------- | ||||||
| 
 | 
 | ||||||
|  | @ -286,6 +285,26 @@ the plugin manager like this: | ||||||
| If you want to look at the names of existing plugins, use | If you want to look at the names of existing plugins, use | ||||||
| the ``--trace-config`` option. | the ``--trace-config`` option. | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | .. _registering-markers: | ||||||
|  | 
 | ||||||
|  | Registering custom markers | ||||||
|  | -------------------------- | ||||||
|  | 
 | ||||||
|  | If your plugin uses any markers, you should register them so that they appear in | ||||||
|  | pytest's help text and do not :ref:`cause spurious warnings <unknown-marks>`. | ||||||
|  | For example, the following plugin would register ``cool_marker`` and | ||||||
|  | ``mark_with`` for all users: | ||||||
|  | 
 | ||||||
|  | .. code-block:: python | ||||||
|  | 
 | ||||||
|  |     def pytest_configure(config): | ||||||
|  |         config.addinivalue_line("markers", "cool_marker: this one is for cool tests.") | ||||||
|  |         config.addinivalue_line( | ||||||
|  |             "markers", "mark_with(arg, arg2): this marker takes arguments." | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| Testing plugins | Testing plugins | ||||||
| --------------- | --------------- | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										1
									
								
								setup.py
								
								
								
								
							
							
						
						
									
										1
									
								
								setup.py
								
								
								
								
							|  | @ -14,6 +14,7 @@ INSTALL_REQUIRES = [ | ||||||
|     'pathlib2>=2.2.0;python_version<"3.6"', |     'pathlib2>=2.2.0;python_version<"3.6"', | ||||||
|     'colorama;sys_platform=="win32"', |     'colorama;sys_platform=="win32"', | ||||||
|     "pluggy>=0.9,!=0.10,<1.0", |     "pluggy>=0.9,!=0.10,<1.0", | ||||||
|  |     "wcwidth", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -268,11 +268,13 @@ class AssertionRewritingHook(object): | ||||||
|         self._marked_for_rewrite_cache.clear() |         self._marked_for_rewrite_cache.clear() | ||||||
| 
 | 
 | ||||||
|     def _warn_already_imported(self, name): |     def _warn_already_imported(self, name): | ||||||
|         from _pytest.warning_types import PytestWarning |         from _pytest.warning_types import PytestAssertRewriteWarning | ||||||
|         from _pytest.warnings import _issue_warning_captured |         from _pytest.warnings import _issue_warning_captured | ||||||
| 
 | 
 | ||||||
|         _issue_warning_captured( |         _issue_warning_captured( | ||||||
|             PytestWarning("Module already imported so cannot be rewritten: %s" % name), |             PytestAssertRewriteWarning( | ||||||
|  |                 "Module already imported so cannot be rewritten: %s" % name | ||||||
|  |             ), | ||||||
|             self.config.hook, |             self.config.hook, | ||||||
|             stacklevel=5, |             stacklevel=5, | ||||||
|         ) |         ) | ||||||
|  | @ -744,12 +746,12 @@ class AssertionRewriter(ast.NodeVisitor): | ||||||
| 
 | 
 | ||||||
|     def display(self, expr): |     def display(self, expr): | ||||||
|         """Call saferepr on the expression.""" |         """Call saferepr on the expression.""" | ||||||
|         return self.helper("saferepr", expr) |         return self.helper("_saferepr", expr) | ||||||
| 
 | 
 | ||||||
|     def helper(self, name, *args): |     def helper(self, name, *args): | ||||||
|         """Call a helper in this module.""" |         """Call a helper in this module.""" | ||||||
|         py_name = ast.Name("@pytest_ar", ast.Load()) |         py_name = ast.Name("@pytest_ar", ast.Load()) | ||||||
|         attr = ast.Attribute(py_name, "_" + name, ast.Load()) |         attr = ast.Attribute(py_name, name, ast.Load()) | ||||||
|         return ast_Call(attr, list(args), []) |         return ast_Call(attr, list(args), []) | ||||||
| 
 | 
 | ||||||
|     def builtin(self, name): |     def builtin(self, name): | ||||||
|  | @ -819,11 +821,13 @@ class AssertionRewriter(ast.NodeVisitor): | ||||||
| 
 | 
 | ||||||
|         """ |         """ | ||||||
|         if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1: |         if isinstance(assert_.test, ast.Tuple) and len(assert_.test.elts) >= 1: | ||||||
|             from _pytest.warning_types import PytestWarning |             from _pytest.warning_types import PytestAssertRewriteWarning | ||||||
|             import warnings |             import warnings | ||||||
| 
 | 
 | ||||||
|             warnings.warn_explicit( |             warnings.warn_explicit( | ||||||
|                 PytestWarning("assertion is always true, perhaps remove parentheses?"), |                 PytestAssertRewriteWarning( | ||||||
|  |                     "assertion is always true, perhaps remove parentheses?" | ||||||
|  |                 ), | ||||||
|                 category=None, |                 category=None, | ||||||
|                 filename=str(self.module_path), |                 filename=str(self.module_path), | ||||||
|                 lineno=assert_.lineno, |                 lineno=assert_.lineno, | ||||||
|  | @ -849,14 +853,14 @@ class AssertionRewriter(ast.NodeVisitor): | ||||||
|         negation = ast.UnaryOp(ast.Not(), top_condition) |         negation = ast.UnaryOp(ast.Not(), top_condition) | ||||||
|         self.statements.append(ast.If(negation, body, [])) |         self.statements.append(ast.If(negation, body, [])) | ||||||
|         if assert_.msg: |         if assert_.msg: | ||||||
|             assertmsg = self.helper("format_assertmsg", assert_.msg) |             assertmsg = self.helper("_format_assertmsg", assert_.msg) | ||||||
|             explanation = "\n>assert " + explanation |             explanation = "\n>assert " + explanation | ||||||
|         else: |         else: | ||||||
|             assertmsg = ast.Str("") |             assertmsg = ast.Str("") | ||||||
|             explanation = "assert " + explanation |             explanation = "assert " + explanation | ||||||
|         template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation)) |         template = ast.BinOp(assertmsg, ast.Add(), ast.Str(explanation)) | ||||||
|         msg = self.pop_format_context(template) |         msg = self.pop_format_context(template) | ||||||
|         fmt = self.helper("format_explanation", msg) |         fmt = self.helper("_format_explanation", msg) | ||||||
|         err_name = ast.Name("AssertionError", ast.Load()) |         err_name = ast.Name("AssertionError", ast.Load()) | ||||||
|         exc = ast_Call(err_name, [fmt], []) |         exc = ast_Call(err_name, [fmt], []) | ||||||
|         if sys.version_info[0] >= 3: |         if sys.version_info[0] >= 3: | ||||||
|  | @ -887,10 +891,10 @@ class AssertionRewriter(ast.NodeVisitor): | ||||||
|         val_is_none = ast.Compare(node, [ast.Is()], [AST_NONE]) |         val_is_none = ast.Compare(node, [ast.Is()], [AST_NONE]) | ||||||
|         send_warning = ast.parse( |         send_warning = ast.parse( | ||||||
|             """ |             """ | ||||||
| from _pytest.warning_types import PytestWarning | from _pytest.warning_types import PytestAssertRewriteWarning | ||||||
| from warnings import warn_explicit | from warnings import warn_explicit | ||||||
| warn_explicit( | warn_explicit( | ||||||
|     PytestWarning('asserting the value None, please use "assert is None"'), |     PytestAssertRewriteWarning('asserting the value None, please use "assert is None"'), | ||||||
|     category=None, |     category=None, | ||||||
|     filename={filename!r}, |     filename={filename!r}, | ||||||
|     lineno={lineno}, |     lineno={lineno}, | ||||||
|  | @ -906,7 +910,7 @@ warn_explicit( | ||||||
|         # _should_repr_global_name() thinks it's acceptable. |         # _should_repr_global_name() thinks it's acceptable. | ||||||
|         locs = ast_Call(self.builtin("locals"), [], []) |         locs = ast_Call(self.builtin("locals"), [], []) | ||||||
|         inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs]) |         inlocs = ast.Compare(ast.Str(name.id), [ast.In()], [locs]) | ||||||
|         dorepr = self.helper("should_repr_global_name", name) |         dorepr = self.helper("_should_repr_global_name", name) | ||||||
|         test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) |         test = ast.BoolOp(ast.Or(), [inlocs, dorepr]) | ||||||
|         expr = ast.IfExp(test, self.display(name), ast.Str(name.id)) |         expr = ast.IfExp(test, self.display(name), ast.Str(name.id)) | ||||||
|         return name, self.explanation_param(expr) |         return name, self.explanation_param(expr) | ||||||
|  | @ -942,7 +946,7 @@ warn_explicit( | ||||||
|                 self.statements = body = inner |                 self.statements = body = inner | ||||||
|         self.statements = save |         self.statements = save | ||||||
|         self.on_failure = fail_save |         self.on_failure = fail_save | ||||||
|         expl_template = self.helper("format_boolop", expl_list, ast.Num(is_or)) |         expl_template = self.helper("_format_boolop", expl_list, ast.Num(is_or)) | ||||||
|         expl = self.pop_format_context(expl_template) |         expl = self.pop_format_context(expl_template) | ||||||
|         return ast.Name(res_var, ast.Load()), self.explanation_param(expl) |         return ast.Name(res_var, ast.Load()), self.explanation_param(expl) | ||||||
| 
 | 
 | ||||||
|  | @ -1067,7 +1071,7 @@ warn_explicit( | ||||||
|             left_res, left_expl = next_res, next_expl |             left_res, left_expl = next_res, next_expl | ||||||
|         # Use pytest.assertion.util._reprcompare if that's available. |         # Use pytest.assertion.util._reprcompare if that's available. | ||||||
|         expl_call = self.helper( |         expl_call = self.helper( | ||||||
|             "call_reprcompare", |             "_call_reprcompare", | ||||||
|             ast.Tuple(syms, ast.Load()), |             ast.Tuple(syms, ast.Load()), | ||||||
|             ast.Tuple(load_names, ast.Load()), |             ast.Tuple(load_names, ast.Load()), | ||||||
|             ast.Tuple(expls, ast.Load()), |             ast.Tuple(expls, ast.Load()), | ||||||
|  |  | ||||||
|  | @ -285,20 +285,30 @@ def _compare_eq_iterable(left, right, verbose=0): | ||||||
| 
 | 
 | ||||||
| def _compare_eq_sequence(left, right, verbose=0): | def _compare_eq_sequence(left, right, verbose=0): | ||||||
|     explanation = [] |     explanation = [] | ||||||
|     for i in range(min(len(left), len(right))): |     len_left = len(left) | ||||||
|  |     len_right = len(right) | ||||||
|  |     for i in range(min(len_left, len_right)): | ||||||
|         if left[i] != right[i]: |         if left[i] != right[i]: | ||||||
|             explanation += [u"At index %s diff: %r != %r" % (i, left[i], right[i])] |             explanation += [u"At index %s diff: %r != %r" % (i, left[i], right[i])] | ||||||
|             break |             break | ||||||
|     if len(left) > len(right): |     len_diff = len_left - len_right | ||||||
|         explanation += [ | 
 | ||||||
|             u"Left contains more items, first extra item: %s" |     if len_diff: | ||||||
|             % saferepr(left[len(right)]) |         if len_diff > 0: | ||||||
|         ] |             dir_with_more = "Left" | ||||||
|     elif len(left) < len(right): |             extra = saferepr(left[len_right]) | ||||||
|         explanation += [ |         else: | ||||||
|             u"Right contains more items, first extra item: %s" |             len_diff = 0 - len_diff | ||||||
|             % saferepr(right[len(left)]) |             dir_with_more = "Right" | ||||||
|         ] |             extra = saferepr(right[len_left]) | ||||||
|  | 
 | ||||||
|  |         if len_diff == 1: | ||||||
|  |             explanation += [u"%s contains one more item: %s" % (dir_with_more, extra)] | ||||||
|  |         else: | ||||||
|  |             explanation += [ | ||||||
|  |                 u"%s contains %d more items, first extra item: %s" | ||||||
|  |                 % (dir_with_more, len_diff, extra) | ||||||
|  |             ] | ||||||
|     return explanation |     return explanation | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -319,7 +329,9 @@ def _compare_eq_set(left, right, verbose=0): | ||||||
| 
 | 
 | ||||||
| def _compare_eq_dict(left, right, verbose=0): | def _compare_eq_dict(left, right, verbose=0): | ||||||
|     explanation = [] |     explanation = [] | ||||||
|     common = set(left).intersection(set(right)) |     set_left = set(left) | ||||||
|  |     set_right = set(right) | ||||||
|  |     common = set_left.intersection(set_right) | ||||||
|     same = {k: left[k] for k in common if left[k] == right[k]} |     same = {k: left[k] for k in common if left[k] == right[k]} | ||||||
|     if same and verbose < 2: |     if same and verbose < 2: | ||||||
|         explanation += [u"Omitting %s identical items, use -vv to show" % len(same)] |         explanation += [u"Omitting %s identical items, use -vv to show" % len(same)] | ||||||
|  | @ -331,15 +343,23 @@ def _compare_eq_dict(left, right, verbose=0): | ||||||
|         explanation += [u"Differing items:"] |         explanation += [u"Differing items:"] | ||||||
|         for k in diff: |         for k in diff: | ||||||
|             explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})] |             explanation += [saferepr({k: left[k]}) + " != " + saferepr({k: right[k]})] | ||||||
|     extra_left = set(left) - set(right) |     extra_left = set_left - set_right | ||||||
|     if extra_left: |     len_extra_left = len(extra_left) | ||||||
|         explanation.append(u"Left contains more items:") |     if len_extra_left: | ||||||
|  |         explanation.append( | ||||||
|  |             u"Left contains %d more item%s:" | ||||||
|  |             % (len_extra_left, "" if len_extra_left == 1 else "s") | ||||||
|  |         ) | ||||||
|         explanation.extend( |         explanation.extend( | ||||||
|             pprint.pformat({k: left[k] for k in extra_left}).splitlines() |             pprint.pformat({k: left[k] for k in extra_left}).splitlines() | ||||||
|         ) |         ) | ||||||
|     extra_right = set(right) - set(left) |     extra_right = set_right - set_left | ||||||
|     if extra_right: |     len_extra_right = len(extra_right) | ||||||
|         explanation.append(u"Right contains more items:") |     if len_extra_right: | ||||||
|  |         explanation.append( | ||||||
|  |             u"Right contains %d more item%s:" | ||||||
|  |             % (len_extra_right, "" if len_extra_right == 1 else "s") | ||||||
|  |         ) | ||||||
|         explanation.extend( |         explanation.extend( | ||||||
|             pprint.pformat({k: right[k] for k in extra_right}).splitlines() |             pprint.pformat({k: right[k] for k in extra_right}).splitlines() | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -60,10 +60,10 @@ class Cache(object): | ||||||
| 
 | 
 | ||||||
|     def warn(self, fmt, **args): |     def warn(self, fmt, **args): | ||||||
|         from _pytest.warnings import _issue_warning_captured |         from _pytest.warnings import _issue_warning_captured | ||||||
|         from _pytest.warning_types import PytestWarning |         from _pytest.warning_types import PytestCacheWarning | ||||||
| 
 | 
 | ||||||
|         _issue_warning_captured( |         _issue_warning_captured( | ||||||
|             PytestWarning(fmt.format(**args) if args else fmt), |             PytestCacheWarning(fmt.format(**args) if args else fmt), | ||||||
|             self._config.hook, |             self._config.hook, | ||||||
|             stacklevel=3, |             stacklevel=3, | ||||||
|         ) |         ) | ||||||
|  | @ -157,18 +157,38 @@ class LFPlugin(object): | ||||||
|         self.active = any(config.getoption(key) for key in active_keys) |         self.active = any(config.getoption(key) for key in active_keys) | ||||||
|         self.lastfailed = config.cache.get("cache/lastfailed", {}) |         self.lastfailed = config.cache.get("cache/lastfailed", {}) | ||||||
|         self._previously_failed_count = None |         self._previously_failed_count = None | ||||||
|         self._no_failures_behavior = self.config.getoption("last_failed_no_failures") |         self._report_status = None | ||||||
|  |         self._skipped_files = 0  # count skipped files during collection due to --lf | ||||||
|  | 
 | ||||||
|  |     def last_failed_paths(self): | ||||||
|  |         """Returns a set with all Paths()s of the previously failed nodeids (cached). | ||||||
|  |         """ | ||||||
|  |         result = getattr(self, "_last_failed_paths", None) | ||||||
|  |         if result is None: | ||||||
|  |             rootpath = Path(self.config.rootdir) | ||||||
|  |             result = {rootpath / nodeid.split("::")[0] for nodeid in self.lastfailed} | ||||||
|  |             self._last_failed_paths = result | ||||||
|  |         return result | ||||||
|  | 
 | ||||||
|  |     def pytest_ignore_collect(self, path): | ||||||
|  |         """ | ||||||
|  |         Ignore this file path if we are in --lf mode and it is not in the list of | ||||||
|  |         previously failed files. | ||||||
|  |         """ | ||||||
|  |         if ( | ||||||
|  |             self.active | ||||||
|  |             and self.config.getoption("lf") | ||||||
|  |             and path.isfile() | ||||||
|  |             and self.lastfailed | ||||||
|  |         ): | ||||||
|  |             skip_it = Path(path) not in self.last_failed_paths() | ||||||
|  |             if skip_it: | ||||||
|  |                 self._skipped_files += 1 | ||||||
|  |             return skip_it | ||||||
| 
 | 
 | ||||||
|     def pytest_report_collectionfinish(self): |     def pytest_report_collectionfinish(self): | ||||||
|         if self.active and self.config.getoption("verbose") >= 0: |         if self.active and self.config.getoption("verbose") >= 0: | ||||||
|             if not self._previously_failed_count: |             return "run-last-failure: %s" % self._report_status | ||||||
|                 return None |  | ||||||
|             noun = "failure" if self._previously_failed_count == 1 else "failures" |  | ||||||
|             suffix = " first" if self.config.getoption("failedfirst") else "" |  | ||||||
|             mode = "rerun previous {count} {noun}{suffix}".format( |  | ||||||
|                 count=self._previously_failed_count, suffix=suffix, noun=noun |  | ||||||
|             ) |  | ||||||
|             return "run-last-failure: %s" % mode |  | ||||||
| 
 | 
 | ||||||
|     def pytest_runtest_logreport(self, report): |     def pytest_runtest_logreport(self, report): | ||||||
|         if (report.when == "call" and report.passed) or report.skipped: |         if (report.when == "call" and report.passed) or report.skipped: | ||||||
|  | @ -186,28 +206,55 @@ class LFPlugin(object): | ||||||
|             self.lastfailed[report.nodeid] = True |             self.lastfailed[report.nodeid] = True | ||||||
| 
 | 
 | ||||||
|     def pytest_collection_modifyitems(self, session, config, items): |     def pytest_collection_modifyitems(self, session, config, items): | ||||||
|         if self.active: |         if not self.active: | ||||||
|             if self.lastfailed: |             return | ||||||
|                 previously_failed = [] | 
 | ||||||
|                 previously_passed = [] |         if self.lastfailed: | ||||||
|                 for item in items: |             previously_failed = [] | ||||||
|                     if item.nodeid in self.lastfailed: |             previously_passed = [] | ||||||
|                         previously_failed.append(item) |             for item in items: | ||||||
|                     else: |                 if item.nodeid in self.lastfailed: | ||||||
|                         previously_passed.append(item) |                     previously_failed.append(item) | ||||||
|                 self._previously_failed_count = len(previously_failed) |                 else: | ||||||
|                 if not previously_failed: |                     previously_passed.append(item) | ||||||
|                     # running a subset of all tests with recorded failures outside |             self._previously_failed_count = len(previously_failed) | ||||||
|                     # of the set of tests currently executing | 
 | ||||||
|                     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), | ||||||
|  |                 ) | ||||||
|  |             else: | ||||||
|                 if self.config.getoption("lf"): |                 if self.config.getoption("lf"): | ||||||
|                     items[:] = previously_failed |                     items[:] = previously_failed | ||||||
|                     config.hook.pytest_deselected(items=previously_passed) |                     config.hook.pytest_deselected(items=previously_passed) | ||||||
|                 else: |                 else:  # --failedfirst | ||||||
|                     items[:] = previously_failed + previously_passed |                     items[:] = previously_failed + previously_passed | ||||||
|             elif self._no_failures_behavior == "none": | 
 | ||||||
|  |                 noun = "failure" if self._previously_failed_count == 1 else "failures" | ||||||
|  |                 if self._skipped_files > 0: | ||||||
|  |                     files_noun = "file" if self._skipped_files == 1 else "files" | ||||||
|  |                     skipped_files_msg = " (skipped {files} {files_noun})".format( | ||||||
|  |                         files=self._skipped_files, files_noun=files_noun | ||||||
|  |                     ) | ||||||
|  |                 else: | ||||||
|  |                     skipped_files_msg = "" | ||||||
|  |                 suffix = " first" if self.config.getoption("failedfirst") else "" | ||||||
|  |                 self._report_status = "rerun previous {count} {noun}{suffix}{skipped_files}".format( | ||||||
|  |                     count=self._previously_failed_count, | ||||||
|  |                     suffix=suffix, | ||||||
|  |                     noun=noun, | ||||||
|  |                     skipped_files=skipped_files_msg, | ||||||
|  |                 ) | ||||||
|  |         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) |                 config.hook.pytest_deselected(items=items) | ||||||
|                 items[:] = [] |                 items[:] = [] | ||||||
|  |             else: | ||||||
|  |                 self._report_status += "not deselecting items." | ||||||
| 
 | 
 | ||||||
|     def pytest_sessionfinish(self, session): |     def pytest_sessionfinish(self, session): | ||||||
|         config = self.config |         config = self.config | ||||||
|  | @ -282,9 +329,13 @@ def pytest_addoption(parser): | ||||||
|     ) |     ) | ||||||
|     group.addoption( |     group.addoption( | ||||||
|         "--cache-show", |         "--cache-show", | ||||||
|         action="store_true", |         action="append", | ||||||
|  |         nargs="?", | ||||||
|         dest="cacheshow", |         dest="cacheshow", | ||||||
|         help="show cache contents, don't perform collection or tests", |         help=( | ||||||
|  |             "show cache contents, don't perform collection or tests. " | ||||||
|  |             "Optional argument: glob (default: '*')." | ||||||
|  |         ), | ||||||
|     ) |     ) | ||||||
|     group.addoption( |     group.addoption( | ||||||
|         "--cache-clear", |         "--cache-clear", | ||||||
|  | @ -303,8 +354,7 @@ def pytest_addoption(parser): | ||||||
|         dest="last_failed_no_failures", |         dest="last_failed_no_failures", | ||||||
|         choices=("all", "none"), |         choices=("all", "none"), | ||||||
|         default="all", |         default="all", | ||||||
|         help="change the behavior when no test failed in the last run or no " |         help="which tests to run with no previously (known) failures.", | ||||||
|         "information about the last failures was found in the cache", |  | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -360,11 +410,16 @@ def cacheshow(config, session): | ||||||
|     if not config.cache._cachedir.is_dir(): |     if not config.cache._cachedir.is_dir(): | ||||||
|         tw.line("cache is empty") |         tw.line("cache is empty") | ||||||
|         return 0 |         return 0 | ||||||
|  | 
 | ||||||
|  |     glob = config.option.cacheshow[0] | ||||||
|  |     if glob is None: | ||||||
|  |         glob = "*" | ||||||
|  | 
 | ||||||
|     dummy = object() |     dummy = object() | ||||||
|     basedir = config.cache._cachedir |     basedir = config.cache._cachedir | ||||||
|     vdir = basedir / "v" |     vdir = basedir / "v" | ||||||
|     tw.sep("-", "cache values") |     tw.sep("-", "cache values for %r" % glob) | ||||||
|     for valpath in sorted(x for x in vdir.rglob("*") if x.is_file()): |     for valpath in sorted(x for x in vdir.rglob(glob) if x.is_file()): | ||||||
|         key = valpath.relative_to(vdir) |         key = valpath.relative_to(vdir) | ||||||
|         val = config.cache.get(key, dummy) |         val = config.cache.get(key, dummy) | ||||||
|         if val is dummy: |         if val is dummy: | ||||||
|  | @ -376,8 +431,8 @@ def cacheshow(config, session): | ||||||
| 
 | 
 | ||||||
|     ddir = basedir / "d" |     ddir = basedir / "d" | ||||||
|     if ddir.is_dir(): |     if ddir.is_dir(): | ||||||
|         contents = sorted(ddir.rglob("*")) |         contents = sorted(ddir.rglob(glob)) | ||||||
|         tw.sep("-", "cache directories") |         tw.sep("-", "cache directories for %r" % glob) | ||||||
|         for p in contents: |         for p in contents: | ||||||
|             # if p.check(dir=1): |             # if p.check(dir=1): | ||||||
|             #    print("%s/" % p.relto(basedir)) |             #    print("%s/" % p.relto(basedir)) | ||||||
|  |  | ||||||
|  | @ -56,13 +56,6 @@ def pytest_load_initial_conftests(early_config, parser, args): | ||||||
|     # make sure that capturemanager is properly reset at final shutdown |     # make sure that capturemanager is properly reset at final shutdown | ||||||
|     early_config.add_cleanup(capman.stop_global_capturing) |     early_config.add_cleanup(capman.stop_global_capturing) | ||||||
| 
 | 
 | ||||||
|     # make sure logging does not raise exceptions at the end |  | ||||||
|     def silence_logging_at_shutdown(): |  | ||||||
|         if "logging" in sys.modules: |  | ||||||
|             sys.modules["logging"].raiseExceptions = False |  | ||||||
| 
 |  | ||||||
|     early_config.add_cleanup(silence_logging_at_shutdown) |  | ||||||
| 
 |  | ||||||
|     # finally trigger conftest loading but while capturing (issue93) |     # finally trigger conftest loading but while capturing (issue93) | ||||||
|     capman.start_global_capturing() |     capman.start_global_capturing() | ||||||
|     outcome = yield |     outcome = yield | ||||||
|  | @ -463,6 +456,7 @@ CaptureResult = collections.namedtuple("CaptureResult", ["out", "err"]) | ||||||
| 
 | 
 | ||||||
| class MultiCapture(object): | class MultiCapture(object): | ||||||
|     out = err = in_ = None |     out = err = in_ = None | ||||||
|  |     _state = None | ||||||
| 
 | 
 | ||||||
|     def __init__(self, out=True, err=True, in_=True, Capture=None): |     def __init__(self, out=True, err=True, in_=True, Capture=None): | ||||||
|         if in_: |         if in_: | ||||||
|  | @ -473,9 +467,16 @@ class MultiCapture(object): | ||||||
|             self.err = Capture(2) |             self.err = Capture(2) | ||||||
| 
 | 
 | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return "<MultiCapture out=%r err=%r in_=%r>" % (self.out, self.err, self.in_) |         return "<MultiCapture out=%r err=%r in_=%r _state=%r _in_suspended=%r>" % ( | ||||||
|  |             self.out, | ||||||
|  |             self.err, | ||||||
|  |             self.in_, | ||||||
|  |             self._state, | ||||||
|  |             getattr(self, "_in_suspended", "<UNSET>"), | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|     def start_capturing(self): |     def start_capturing(self): | ||||||
|  |         self._state = "started" | ||||||
|         if self.in_: |         if self.in_: | ||||||
|             self.in_.start() |             self.in_.start() | ||||||
|         if self.out: |         if self.out: | ||||||
|  | @ -493,6 +494,7 @@ class MultiCapture(object): | ||||||
|         return out, err |         return out, err | ||||||
| 
 | 
 | ||||||
|     def suspend_capturing(self, in_=False): |     def suspend_capturing(self, in_=False): | ||||||
|  |         self._state = "suspended" | ||||||
|         if self.out: |         if self.out: | ||||||
|             self.out.suspend() |             self.out.suspend() | ||||||
|         if self.err: |         if self.err: | ||||||
|  | @ -502,6 +504,7 @@ class MultiCapture(object): | ||||||
|             self._in_suspended = True |             self._in_suspended = True | ||||||
| 
 | 
 | ||||||
|     def resume_capturing(self): |     def resume_capturing(self): | ||||||
|  |         self._state = "resumed" | ||||||
|         if self.out: |         if self.out: | ||||||
|             self.out.resume() |             self.out.resume() | ||||||
|         if self.err: |         if self.err: | ||||||
|  | @ -512,9 +515,9 @@ class MultiCapture(object): | ||||||
| 
 | 
 | ||||||
|     def stop_capturing(self): |     def stop_capturing(self): | ||||||
|         """ stop capturing and reset capturing streams """ |         """ stop capturing and reset capturing streams """ | ||||||
|         if hasattr(self, "_reset"): |         if self._state == "stopped": | ||||||
|             raise ValueError("was already stopped") |             raise ValueError("was already stopped") | ||||||
|         self._reset = True |         self._state = "stopped" | ||||||
|         if self.out: |         if self.out: | ||||||
|             self.out.done() |             self.out.done() | ||||||
|         if self.err: |         if self.err: | ||||||
|  | @ -542,6 +545,7 @@ class FDCaptureBinary(object): | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     EMPTY_BUFFER = b"" |     EMPTY_BUFFER = b"" | ||||||
|  |     _state = None | ||||||
| 
 | 
 | ||||||
|     def __init__(self, targetfd, tmpfile=None): |     def __init__(self, targetfd, tmpfile=None): | ||||||
|         self.targetfd = targetfd |         self.targetfd = targetfd | ||||||
|  | @ -568,9 +572,10 @@ class FDCaptureBinary(object): | ||||||
|             self.tmpfile_fd = tmpfile.fileno() |             self.tmpfile_fd = tmpfile.fileno() | ||||||
| 
 | 
 | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return "<FDCapture %s oldfd=%s>" % ( |         return "<FDCapture %s oldfd=%s _state=%r>" % ( | ||||||
|             self.targetfd, |             self.targetfd, | ||||||
|             getattr(self, "targetfd_save", None), |             getattr(self, "targetfd_save", None), | ||||||
|  |             self._state, | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|     def start(self): |     def start(self): | ||||||
|  | @ -581,6 +586,7 @@ class FDCaptureBinary(object): | ||||||
|             raise ValueError("saved filedescriptor not valid anymore") |             raise ValueError("saved filedescriptor not valid anymore") | ||||||
|         os.dup2(self.tmpfile_fd, self.targetfd) |         os.dup2(self.tmpfile_fd, self.targetfd) | ||||||
|         self.syscapture.start() |         self.syscapture.start() | ||||||
|  |         self._state = "started" | ||||||
| 
 | 
 | ||||||
|     def snap(self): |     def snap(self): | ||||||
|         self.tmpfile.seek(0) |         self.tmpfile.seek(0) | ||||||
|  | @ -597,14 +603,17 @@ class FDCaptureBinary(object): | ||||||
|         os.close(targetfd_save) |         os.close(targetfd_save) | ||||||
|         self.syscapture.done() |         self.syscapture.done() | ||||||
|         _attempt_to_close_capture_file(self.tmpfile) |         _attempt_to_close_capture_file(self.tmpfile) | ||||||
|  |         self._state = "done" | ||||||
| 
 | 
 | ||||||
|     def suspend(self): |     def suspend(self): | ||||||
|         self.syscapture.suspend() |         self.syscapture.suspend() | ||||||
|         os.dup2(self.targetfd_save, self.targetfd) |         os.dup2(self.targetfd_save, self.targetfd) | ||||||
|  |         self._state = "suspended" | ||||||
| 
 | 
 | ||||||
|     def resume(self): |     def resume(self): | ||||||
|         self.syscapture.resume() |         self.syscapture.resume() | ||||||
|         os.dup2(self.tmpfile_fd, self.targetfd) |         os.dup2(self.tmpfile_fd, self.targetfd) | ||||||
|  |         self._state = "resumed" | ||||||
| 
 | 
 | ||||||
|     def writeorg(self, data): |     def writeorg(self, data): | ||||||
|         """ write to original file descriptor. """ |         """ write to original file descriptor. """ | ||||||
|  | @ -632,6 +641,7 @@ class FDCapture(FDCaptureBinary): | ||||||
| class SysCapture(object): | class SysCapture(object): | ||||||
| 
 | 
 | ||||||
|     EMPTY_BUFFER = str() |     EMPTY_BUFFER = str() | ||||||
|  |     _state = None | ||||||
| 
 | 
 | ||||||
|     def __init__(self, fd, tmpfile=None): |     def __init__(self, fd, tmpfile=None): | ||||||
|         name = patchsysdict[fd] |         name = patchsysdict[fd] | ||||||
|  | @ -644,8 +654,17 @@ class SysCapture(object): | ||||||
|                 tmpfile = CaptureIO() |                 tmpfile = CaptureIO() | ||||||
|         self.tmpfile = tmpfile |         self.tmpfile = tmpfile | ||||||
| 
 | 
 | ||||||
|  |     def __repr__(self): | ||||||
|  |         return "<SysCapture %s _old=%r, tmpfile=%r _state=%r>" % ( | ||||||
|  |             self.name, | ||||||
|  |             self._old, | ||||||
|  |             self.tmpfile, | ||||||
|  |             self._state, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|     def start(self): |     def start(self): | ||||||
|         setattr(sys, self.name, self.tmpfile) |         setattr(sys, self.name, self.tmpfile) | ||||||
|  |         self._state = "started" | ||||||
| 
 | 
 | ||||||
|     def snap(self): |     def snap(self): | ||||||
|         res = self.tmpfile.getvalue() |         res = self.tmpfile.getvalue() | ||||||
|  | @ -657,12 +676,15 @@ class SysCapture(object): | ||||||
|         setattr(sys, self.name, self._old) |         setattr(sys, self.name, self._old) | ||||||
|         del self._old |         del self._old | ||||||
|         _attempt_to_close_capture_file(self.tmpfile) |         _attempt_to_close_capture_file(self.tmpfile) | ||||||
|  |         self._state = "done" | ||||||
| 
 | 
 | ||||||
|     def suspend(self): |     def suspend(self): | ||||||
|         setattr(sys, self.name, self._old) |         setattr(sys, self.name, self._old) | ||||||
|  |         self._state = "suspended" | ||||||
| 
 | 
 | ||||||
|     def resume(self): |     def resume(self): | ||||||
|         setattr(sys, self.name, self.tmpfile) |         setattr(sys, self.name, self.tmpfile) | ||||||
|  |         self._state = "resumed" | ||||||
| 
 | 
 | ||||||
|     def writeorg(self, data): |     def writeorg(self, data): | ||||||
|         self._old.write(data) |         self._old.write(data) | ||||||
|  |  | ||||||
|  | @ -32,7 +32,7 @@ from _pytest.compat import lru_cache | ||||||
| from _pytest.compat import safe_str | from _pytest.compat import safe_str | ||||||
| from _pytest.outcomes import fail | from _pytest.outcomes import fail | ||||||
| from _pytest.outcomes import Skipped | from _pytest.outcomes import Skipped | ||||||
| from _pytest.warning_types import PytestWarning | from _pytest.warning_types import PytestConfigWarning | ||||||
| 
 | 
 | ||||||
| hookimpl = HookimplMarker("pytest") | hookimpl = HookimplMarker("pytest") | ||||||
| hookspec = HookspecMarker("pytest") | hookspec = HookspecMarker("pytest") | ||||||
|  | @ -112,13 +112,18 @@ def directory_arg(path, optname): | ||||||
|     return path |     return path | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| default_plugins = ( | # Plugins that cannot be disabled via "-p no:X" currently. | ||||||
|  | essential_plugins = ( | ||||||
|     "mark", |     "mark", | ||||||
|     "main", |     "main", | ||||||
|     "terminal", |  | ||||||
|     "runner", |     "runner", | ||||||
|     "python", |     "python", | ||||||
|     "fixtures", |     "fixtures", | ||||||
|  |     "helpconfig",  # Provides -p. | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | default_plugins = essential_plugins + ( | ||||||
|  |     "terminal", | ||||||
|     "debugging", |     "debugging", | ||||||
|     "unittest", |     "unittest", | ||||||
|     "capture", |     "capture", | ||||||
|  | @ -127,7 +132,6 @@ default_plugins = ( | ||||||
|     "monkeypatch", |     "monkeypatch", | ||||||
|     "recwarn", |     "recwarn", | ||||||
|     "pastebin", |     "pastebin", | ||||||
|     "helpconfig", |  | ||||||
|     "nose", |     "nose", | ||||||
|     "assertion", |     "assertion", | ||||||
|     "junitxml", |     "junitxml", | ||||||
|  | @ -143,7 +147,6 @@ default_plugins = ( | ||||||
|     "reports", |     "reports", | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| builtin_plugins = set(default_plugins) | builtin_plugins = set(default_plugins) | ||||||
| builtin_plugins.add("pytester") | builtin_plugins.add("pytester") | ||||||
| 
 | 
 | ||||||
|  | @ -279,7 +282,6 @@ class PytestPluginManager(PluginManager): | ||||||
|             known_marks = {m.name for m in getattr(method, "pytestmark", [])} |             known_marks = {m.name for m in getattr(method, "pytestmark", [])} | ||||||
| 
 | 
 | ||||||
|             for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"): |             for name in ("tryfirst", "trylast", "optionalhook", "hookwrapper"): | ||||||
| 
 |  | ||||||
|                 opts.setdefault(name, hasattr(method, name) or name in known_marks) |                 opts.setdefault(name, hasattr(method, name) or name in known_marks) | ||||||
|         return opts |         return opts | ||||||
| 
 | 
 | ||||||
|  | @ -305,7 +307,7 @@ class PytestPluginManager(PluginManager): | ||||||
|     def register(self, plugin, name=None): |     def register(self, plugin, name=None): | ||||||
|         if name in ["pytest_catchlog", "pytest_capturelog"]: |         if name in ["pytest_catchlog", "pytest_capturelog"]: | ||||||
|             warnings.warn( |             warnings.warn( | ||||||
|                 PytestWarning( |                 PytestConfigWarning( | ||||||
|                     "{} plugin has been merged into the core, " |                     "{} plugin has been merged into the core, " | ||||||
|                     "please remove it from your requirements.".format( |                     "please remove it from your requirements.".format( | ||||||
|                         name.replace("_", "-") |                         name.replace("_", "-") | ||||||
|  | @ -496,6 +498,9 @@ class PytestPluginManager(PluginManager): | ||||||
|     def consider_pluginarg(self, arg): |     def consider_pluginarg(self, arg): | ||||||
|         if arg.startswith("no:"): |         if arg.startswith("no:"): | ||||||
|             name = arg[3:] |             name = arg[3:] | ||||||
|  |             if name in essential_plugins: | ||||||
|  |                 raise UsageError("plugin %s cannot be disabled" % name) | ||||||
|  | 
 | ||||||
|             # PR #4304 : remove stepwise if cacheprovider is blocked |             # PR #4304 : remove stepwise if cacheprovider is blocked | ||||||
|             if name == "cacheprovider": |             if name == "cacheprovider": | ||||||
|                 self.set_blocked("stepwise") |                 self.set_blocked("stepwise") | ||||||
|  | @ -569,7 +574,7 @@ class PytestPluginManager(PluginManager): | ||||||
|             from _pytest.warnings import _issue_warning_captured |             from _pytest.warnings import _issue_warning_captured | ||||||
| 
 | 
 | ||||||
|             _issue_warning_captured( |             _issue_warning_captured( | ||||||
|                 PytestWarning("skipped plugin %r: %s" % (modname, e.msg)), |                 PytestConfigWarning("skipped plugin %r: %s" % (modname, e.msg)), | ||||||
|                 self.hook, |                 self.hook, | ||||||
|                 stacklevel=1, |                 stacklevel=1, | ||||||
|             ) |             ) | ||||||
|  | @ -858,7 +863,7 @@ class Config(object): | ||||||
|                 from _pytest.warnings import _issue_warning_captured |                 from _pytest.warnings import _issue_warning_captured | ||||||
| 
 | 
 | ||||||
|                 _issue_warning_captured( |                 _issue_warning_captured( | ||||||
|                     PytestWarning( |                     PytestConfigWarning( | ||||||
|                         "could not load initial conftests: {}".format(e.path) |                         "could not load initial conftests: {}".format(e.path) | ||||||
|                     ), |                     ), | ||||||
|                     self.hook, |                     self.hook, | ||||||
|  |  | ||||||
|  | @ -143,18 +143,18 @@ class pytestPDB(object): | ||||||
|                     else: |                     else: | ||||||
|                         tw.sep(">", "PDB set_trace") |                         tw.sep(">", "PDB set_trace") | ||||||
| 
 | 
 | ||||||
|             class _PdbWrapper(cls._pdb_cls, object): |             class PytestPdbWrapper(cls._pdb_cls, object): | ||||||
|                 _pytest_capman = capman |                 _pytest_capman = capman | ||||||
|                 _continued = False |                 _continued = False | ||||||
| 
 | 
 | ||||||
|                 def do_debug(self, arg): |                 def do_debug(self, arg): | ||||||
|                     cls._recursive_debug += 1 |                     cls._recursive_debug += 1 | ||||||
|                     ret = super(_PdbWrapper, self).do_debug(arg) |                     ret = super(PytestPdbWrapper, self).do_debug(arg) | ||||||
|                     cls._recursive_debug -= 1 |                     cls._recursive_debug -= 1 | ||||||
|                     return ret |                     return ret | ||||||
| 
 | 
 | ||||||
|                 def do_continue(self, arg): |                 def do_continue(self, arg): | ||||||
|                     ret = super(_PdbWrapper, self).do_continue(arg) |                     ret = super(PytestPdbWrapper, self).do_continue(arg) | ||||||
|                     if cls._recursive_debug == 0: |                     if cls._recursive_debug == 0: | ||||||
|                         tw = _pytest.config.create_terminal_writer(cls._config) |                         tw = _pytest.config.create_terminal_writer(cls._config) | ||||||
|                         tw.line() |                         tw.line() | ||||||
|  | @ -181,24 +181,30 @@ class pytestPDB(object): | ||||||
| 
 | 
 | ||||||
|                 do_c = do_cont = do_continue |                 do_c = do_cont = do_continue | ||||||
| 
 | 
 | ||||||
|                 def set_quit(self): |                 def do_quit(self, arg): | ||||||
|                     """Raise Exit outcome when quit command is used in pdb. |                     """Raise Exit outcome when quit command is used in pdb. | ||||||
| 
 | 
 | ||||||
|                     This is a bit of a hack - it would be better if BdbQuit |                     This is a bit of a hack - it would be better if BdbQuit | ||||||
|                     could be handled, but this would require to wrap the |                     could be handled, but this would require to wrap the | ||||||
|                     whole pytest run, and adjust the report etc. |                     whole pytest run, and adjust the report etc. | ||||||
|                     """ |                     """ | ||||||
|                     super(_PdbWrapper, self).set_quit() |                     ret = super(PytestPdbWrapper, self).do_quit(arg) | ||||||
|  | 
 | ||||||
|                     if cls._recursive_debug == 0: |                     if cls._recursive_debug == 0: | ||||||
|                         outcomes.exit("Quitting debugger") |                         outcomes.exit("Quitting debugger") | ||||||
| 
 | 
 | ||||||
|  |                     return ret | ||||||
|  | 
 | ||||||
|  |                 do_q = do_quit | ||||||
|  |                 do_exit = do_quit | ||||||
|  | 
 | ||||||
|                 def setup(self, f, tb): |                 def setup(self, f, tb): | ||||||
|                     """Suspend on setup(). |                     """Suspend on setup(). | ||||||
| 
 | 
 | ||||||
|                     Needed after do_continue resumed, and entering another |                     Needed after do_continue resumed, and entering another | ||||||
|                     breakpoint again. |                     breakpoint again. | ||||||
|                     """ |                     """ | ||||||
|                     ret = super(_PdbWrapper, self).setup(f, tb) |                     ret = super(PytestPdbWrapper, self).setup(f, tb) | ||||||
|                     if not ret and self._continued: |                     if not ret and self._continued: | ||||||
|                         # pdb.setup() returns True if the command wants to exit |                         # pdb.setup() returns True if the command wants to exit | ||||||
|                         # from the interaction: do not suspend capturing then. |                         # from the interaction: do not suspend capturing then. | ||||||
|  | @ -206,7 +212,7 @@ class pytestPDB(object): | ||||||
|                             self._pytest_capman.suspend_global_capture(in_=True) |                             self._pytest_capman.suspend_global_capture(in_=True) | ||||||
|                     return ret |                     return ret | ||||||
| 
 | 
 | ||||||
|             _pdb = _PdbWrapper(**kwargs) |             _pdb = PytestPdbWrapper(**kwargs) | ||||||
|             cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb) |             cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb) | ||||||
|         else: |         else: | ||||||
|             _pdb = cls._pdb_cls(**kwargs) |             _pdb = cls._pdb_cls(**kwargs) | ||||||
|  | @ -246,7 +252,7 @@ def _test_pytest_function(pyfuncitem): | ||||||
|     _pdb = pytestPDB._init_pdb() |     _pdb = pytestPDB._init_pdb() | ||||||
|     testfunction = pyfuncitem.obj |     testfunction = pyfuncitem.obj | ||||||
|     pyfuncitem.obj = _pdb.runcall |     pyfuncitem.obj = _pdb.runcall | ||||||
|     if "func" in pyfuncitem._fixtureinfo.argnames:  # noqa |     if "func" in pyfuncitem._fixtureinfo.argnames:  # pragma: no branch | ||||||
|         raise ValueError("--trace can't be used with a fixture named func!") |         raise ValueError("--trace can't be used with a fixture named func!") | ||||||
|     pyfuncitem.funcargs["func"] = testfunction |     pyfuncitem.funcargs["func"] = testfunction | ||||||
|     new_list = list(pyfuncitem._fixtureinfo.argnames) |     new_list = list(pyfuncitem._fixtureinfo.argnames) | ||||||
|  |  | ||||||
|  | @ -1023,6 +1023,7 @@ def fixture(scope="function", params=None, autouse=False, ids=None, name=None): | ||||||
|     :arg params: an optional list of parameters which will cause multiple |     :arg params: an optional list of parameters which will cause multiple | ||||||
|                 invocations of the fixture function and all of the tests |                 invocations of the fixture function and all of the tests | ||||||
|                 using it. |                 using it. | ||||||
|  |                 The current parameter is available in ``request.param``. | ||||||
| 
 | 
 | ||||||
|     :arg autouse: if True, the fixture func is activated for all tests that |     :arg autouse: if True, the fixture func is activated for all tests that | ||||||
|                 can see it.  If False (the default) then an explicit |                 can see it.  If False (the default) then an explicit | ||||||
|  |  | ||||||
|  | @ -151,13 +151,14 @@ def showhelp(config): | ||||||
|     ) |     ) | ||||||
|     tw.line() |     tw.line() | ||||||
| 
 | 
 | ||||||
|  |     columns = tw.fullwidth  # costly call | ||||||
|     for name in config._parser._ininames: |     for name in config._parser._ininames: | ||||||
|         help, type, default = config._parser._inidict[name] |         help, type, default = config._parser._inidict[name] | ||||||
|         if type is None: |         if type is None: | ||||||
|             type = "string" |             type = "string" | ||||||
|         spec = "%s (%s)" % (name, type) |         spec = "%s (%s)" % (name, type) | ||||||
|         line = "  %-24s %s" % (spec, help) |         line = "  %-24s %s" % (spec, help) | ||||||
|         tw.line(line[: tw.fullwidth]) |         tw.line(line[:columns]) | ||||||
| 
 | 
 | ||||||
|     tw.line() |     tw.line() | ||||||
|     tw.line("environment variables:") |     tw.line("environment variables:") | ||||||
|  |  | ||||||
|  | @ -227,7 +227,7 @@ def pytest_collectreport(report): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def pytest_deselected(items): | def pytest_deselected(items): | ||||||
|     """ called for test items deselected by keyword. """ |     """ called for test items deselected, e.g. by keyword. """ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @hookspec(firstresult=True) | @hookspec(firstresult=True) | ||||||
|  |  | ||||||
|  | @ -252,7 +252,14 @@ class _NodeReporter(object): | ||||||
| 
 | 
 | ||||||
|     def append_skipped(self, report): |     def append_skipped(self, report): | ||||||
|         if hasattr(report, "wasxfail"): |         if hasattr(report, "wasxfail"): | ||||||
|             self._add_simple(Junit.skipped, "expected test failure", report.wasxfail) |             xfailreason = report.wasxfail | ||||||
|  |             if xfailreason.startswith("reason: "): | ||||||
|  |                 xfailreason = xfailreason[8:] | ||||||
|  |             self.append( | ||||||
|  |                 Junit.skipped( | ||||||
|  |                     "", type="pytest.xfail", message=bin_xml_escape(xfailreason) | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|         else: |         else: | ||||||
|             filename, lineno, skipreason = report.longrepr |             filename, lineno, skipreason = report.longrepr | ||||||
|             if skipreason.startswith("Skipped: "): |             if skipreason.startswith("Skipped: "): | ||||||
|  | @ -274,6 +281,21 @@ class _NodeReporter(object): | ||||||
|         self.to_xml = lambda: py.xml.raw(data) |         self.to_xml = lambda: py.xml.raw(data) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def _warn_incompatibility_with_xunit2(request, fixture_name): | ||||||
|  |     """Emits a PytestWarning about the given fixture being incompatible with newer xunit revisions""" | ||||||
|  |     from _pytest.warning_types import PytestWarning | ||||||
|  | 
 | ||||||
|  |     xml = getattr(request.config, "_xml", None) | ||||||
|  |     if xml is not None and xml.family not in ("xunit1", "legacy"): | ||||||
|  |         request.node.warn( | ||||||
|  |             PytestWarning( | ||||||
|  |                 "{fixture_name} is incompatible with junit_family '{family}' (use 'legacy' or 'xunit1')".format( | ||||||
|  |                     fixture_name=fixture_name, family=xml.family | ||||||
|  |                 ) | ||||||
|  |             ) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @pytest.fixture | @pytest.fixture | ||||||
| def record_property(request): | def record_property(request): | ||||||
|     """Add an extra properties the calling test. |     """Add an extra properties the calling test. | ||||||
|  | @ -287,6 +309,7 @@ def record_property(request): | ||||||
|         def test_function(record_property): |         def test_function(record_property): | ||||||
|             record_property("example_key", 1) |             record_property("example_key", 1) | ||||||
|     """ |     """ | ||||||
|  |     _warn_incompatibility_with_xunit2(request, "record_property") | ||||||
| 
 | 
 | ||||||
|     def append_property(name, value): |     def append_property(name, value): | ||||||
|         request.node.user_properties.append((name, value)) |         request.node.user_properties.append((name, value)) | ||||||
|  | @ -300,31 +323,67 @@ def record_xml_attribute(request): | ||||||
|     The fixture is callable with ``(name, value)``, with value being |     The fixture is callable with ``(name, value)``, with value being | ||||||
|     automatically xml-encoded |     automatically xml-encoded | ||||||
|     """ |     """ | ||||||
|     from _pytest.warning_types import PytestWarning |     from _pytest.warning_types import PytestExperimentalApiWarning | ||||||
| 
 | 
 | ||||||
|     request.node.warn(PytestWarning("record_xml_attribute is an experimental feature")) |     request.node.warn( | ||||||
|  |         PytestExperimentalApiWarning("record_xml_attribute is an experimental feature") | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     _warn_incompatibility_with_xunit2(request, "record_xml_attribute") | ||||||
| 
 | 
 | ||||||
|     # Declare noop |     # Declare noop | ||||||
|     def add_attr_noop(name, value): |     def add_attr_noop(name, value): | ||||||
|         pass |         pass | ||||||
| 
 | 
 | ||||||
|     attr_func = add_attr_noop |     attr_func = add_attr_noop | ||||||
|     xml = getattr(request.config, "_xml", None) |  | ||||||
| 
 | 
 | ||||||
|     if xml is not None and xml.family != "xunit1": |     xml = getattr(request.config, "_xml", None) | ||||||
|         request.node.warn( |     if xml is not None: | ||||||
|             PytestWarning( |  | ||||||
|                 "record_xml_attribute is incompatible with junit_family: " |  | ||||||
|                 "%s (use: legacy|xunit1)" % xml.family |  | ||||||
|             ) |  | ||||||
|         ) |  | ||||||
|     elif xml is not None: |  | ||||||
|         node_reporter = xml.node_reporter(request.node.nodeid) |         node_reporter = xml.node_reporter(request.node.nodeid) | ||||||
|         attr_func = node_reporter.add_attribute |         attr_func = node_reporter.add_attribute | ||||||
| 
 | 
 | ||||||
|     return attr_func |     return attr_func | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def _check_record_param_type(param, v): | ||||||
|  |     """Used by record_testsuite_property to check that the given parameter name is of the proper | ||||||
|  |     type""" | ||||||
|  |     __tracebackhide__ = True | ||||||
|  |     if not isinstance(v, six.string_types): | ||||||
|  |         msg = "{param} parameter needs to be a string, but {g} given" | ||||||
|  |         raise TypeError(msg.format(param=param, g=type(v).__name__)) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @pytest.fixture(scope="session") | ||||||
|  | def record_testsuite_property(request): | ||||||
|  |     """ | ||||||
|  |     Records a new ``<property>`` tag as child of the root ``<testsuite>``. This is suitable to | ||||||
|  |     writing global information regarding the entire test suite, and is compatible with ``xunit2`` JUnit family. | ||||||
|  | 
 | ||||||
|  |     This is a ``session``-scoped fixture which is called with ``(name, value)``. Example: | ||||||
|  | 
 | ||||||
|  |     .. code-block:: python | ||||||
|  | 
 | ||||||
|  |         def test_foo(record_testsuite_property): | ||||||
|  |             record_testsuite_property("ARCH", "PPC") | ||||||
|  |             record_testsuite_property("STORAGE_TYPE", "CEPH") | ||||||
|  | 
 | ||||||
|  |     ``name`` must be a string, ``value`` will be converted to a string and properly xml-escaped. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     __tracebackhide__ = True | ||||||
|  | 
 | ||||||
|  |     def record_func(name, value): | ||||||
|  |         """noop function in case --junitxml was not passed in the command-line""" | ||||||
|  |         __tracebackhide__ = True | ||||||
|  |         _check_record_param_type("name", name) | ||||||
|  | 
 | ||||||
|  |     xml = getattr(request.config, "_xml", None) | ||||||
|  |     if xml is not None: | ||||||
|  |         record_func = xml.add_global_property  # noqa | ||||||
|  |     return record_func | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def pytest_addoption(parser): | def pytest_addoption(parser): | ||||||
|     group = parser.getgroup("terminal reporting") |     group = parser.getgroup("terminal reporting") | ||||||
|     group.addoption( |     group.addoption( | ||||||
|  | @ -424,6 +483,7 @@ class LogXML(object): | ||||||
|         self.node_reporters = {}  # nodeid -> _NodeReporter |         self.node_reporters = {}  # nodeid -> _NodeReporter | ||||||
|         self.node_reporters_ordered = [] |         self.node_reporters_ordered = [] | ||||||
|         self.global_properties = [] |         self.global_properties = [] | ||||||
|  | 
 | ||||||
|         # List of reports that failed on call but teardown is pending. |         # List of reports that failed on call but teardown is pending. | ||||||
|         self.open_reports = [] |         self.open_reports = [] | ||||||
|         self.cnt_double_fail_tests = 0 |         self.cnt_double_fail_tests = 0 | ||||||
|  | @ -612,7 +672,9 @@ class LogXML(object): | ||||||
|         terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile)) |         terminalreporter.write_sep("-", "generated xml file: %s" % (self.logfile)) | ||||||
| 
 | 
 | ||||||
|     def add_global_property(self, name, value): |     def add_global_property(self, name, value): | ||||||
|         self.global_properties.append((str(name), bin_xml_escape(value))) |         __tracebackhide__ = True | ||||||
|  |         _check_record_param_type("name", name) | ||||||
|  |         self.global_properties.append((name, bin_xml_escape(value))) | ||||||
| 
 | 
 | ||||||
|     def _get_global_properties_node(self): |     def _get_global_properties_node(self): | ||||||
|         """Return a Junit node containing custom properties, if any. |         """Return a Junit node containing custom properties, if any. | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ from _pytest.compat import dummy_context_manager | ||||||
| from _pytest.config import create_terminal_writer | from _pytest.config import create_terminal_writer | ||||||
| from _pytest.pathlib import Path | from _pytest.pathlib import Path | ||||||
| 
 | 
 | ||||||
| DEFAULT_LOG_FORMAT = "%(filename)-25s %(lineno)4d %(levelname)-8s %(message)s" | DEFAULT_LOG_FORMAT = "%(levelname)-8s %(name)s:%(filename)s:%(lineno)d %(message)s" | ||||||
| DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S" | DEFAULT_LOG_DATE_FORMAT = "%H:%M:%S" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -47,11 +47,6 @@ def pytest_addoption(parser): | ||||||
|         type="args", |         type="args", | ||||||
|         default=[], |         default=[], | ||||||
|     ) |     ) | ||||||
|     # parser.addini("dirpatterns", |  | ||||||
|     #    "patterns specifying possible locations of test files", |  | ||||||
|     #    type="linelist", default=["**/test_*.txt", |  | ||||||
|     #            "**/test_*.py", "**/*_test.py"] |  | ||||||
|     # ) |  | ||||||
|     group = parser.getgroup("general", "running and selection options") |     group = parser.getgroup("general", "running and selection options") | ||||||
|     group._addoption( |     group._addoption( | ||||||
|         "-x", |         "-x", | ||||||
|  | @ -71,9 +66,10 @@ def pytest_addoption(parser): | ||||||
|         help="exit after first num failures or errors.", |         help="exit after first num failures or errors.", | ||||||
|     ) |     ) | ||||||
|     group._addoption( |     group._addoption( | ||||||
|  |         "--strict-markers", | ||||||
|         "--strict", |         "--strict", | ||||||
|         action="store_true", |         action="store_true", | ||||||
|         help="marks not registered in configuration file raise errors.", |         help="markers not registered in the `markers` section of the configuration file raise errors.", | ||||||
|     ) |     ) | ||||||
|     group._addoption( |     group._addoption( | ||||||
|         "-c", |         "-c", | ||||||
|  | @ -208,16 +204,20 @@ def wrap_session(config, doit): | ||||||
|             initstate = 2 |             initstate = 2 | ||||||
|             session.exitstatus = doit(config, session) or 0 |             session.exitstatus = doit(config, session) or 0 | ||||||
|         except UsageError: |         except UsageError: | ||||||
|  |             session.exitstatus = EXIT_USAGEERROR | ||||||
|             raise |             raise | ||||||
|         except Failed: |         except Failed: | ||||||
|             session.exitstatus = EXIT_TESTSFAILED |             session.exitstatus = EXIT_TESTSFAILED | ||||||
|         except (KeyboardInterrupt, exit.Exception): |         except (KeyboardInterrupt, exit.Exception): | ||||||
|             excinfo = _pytest._code.ExceptionInfo.from_current() |             excinfo = _pytest._code.ExceptionInfo.from_current() | ||||||
|             exitstatus = EXIT_INTERRUPTED |             exitstatus = EXIT_INTERRUPTED | ||||||
|             if initstate <= 2 and isinstance(excinfo.value, exit.Exception): |             if isinstance(excinfo.value, exit.Exception): | ||||||
|                 sys.stderr.write("{}: {}\n".format(excinfo.typename, excinfo.value.msg)) |  | ||||||
|                 if excinfo.value.returncode is not None: |                 if excinfo.value.returncode is not None: | ||||||
|                     exitstatus = excinfo.value.returncode |                     exitstatus = excinfo.value.returncode | ||||||
|  |                 if initstate < 2: | ||||||
|  |                     sys.stderr.write( | ||||||
|  |                         "{}: {}\n".format(excinfo.typename, excinfo.value.msg) | ||||||
|  |                     ) | ||||||
|             config.hook.pytest_keyboard_interrupt(excinfo=excinfo) |             config.hook.pytest_keyboard_interrupt(excinfo=excinfo) | ||||||
|             session.exitstatus = exitstatus |             session.exitstatus = exitstatus | ||||||
|         except:  # noqa |         except:  # noqa | ||||||
|  | @ -431,7 +431,7 @@ class Session(nodes.FSCollector): | ||||||
|         self.shouldfail = False |         self.shouldfail = False | ||||||
|         self.trace = config.trace.root.get("collection") |         self.trace = config.trace.root.get("collection") | ||||||
|         self._norecursepatterns = config.getini("norecursedirs") |         self._norecursepatterns = config.getini("norecursedirs") | ||||||
|         self.startdir = py.path.local() |         self.startdir = config.invocation_dir | ||||||
|         self._initialpaths = frozenset() |         self._initialpaths = frozenset() | ||||||
|         # Keep track of any collected nodes in here, so we don't duplicate fixtures |         # Keep track of any collected nodes in here, so we don't duplicate fixtures | ||||||
|         self._node_cache = {} |         self._node_cache = {} | ||||||
|  |  | ||||||
|  | @ -100,6 +100,9 @@ pytest_cmdline_main.tryfirst = True | ||||||
| 
 | 
 | ||||||
| def deselect_by_keyword(items, config): | def deselect_by_keyword(items, config): | ||||||
|     keywordexpr = config.option.keyword.lstrip() |     keywordexpr = config.option.keyword.lstrip() | ||||||
|  |     if not keywordexpr: | ||||||
|  |         return | ||||||
|  | 
 | ||||||
|     if keywordexpr.startswith("-"): |     if keywordexpr.startswith("-"): | ||||||
|         keywordexpr = "not " + keywordexpr[1:] |         keywordexpr = "not " + keywordexpr[1:] | ||||||
|     selectuntil = False |     selectuntil = False | ||||||
|  | @ -147,8 +150,7 @@ def pytest_collection_modifyitems(items, config): | ||||||
| 
 | 
 | ||||||
| def pytest_configure(config): | def pytest_configure(config): | ||||||
|     config._old_mark_config = MARK_GEN._config |     config._old_mark_config = MARK_GEN._config | ||||||
|     if config.option.strict: |     MARK_GEN._config = config | ||||||
|         MARK_GEN._config = config |  | ||||||
| 
 | 
 | ||||||
|     empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION) |     empty_parameterset = config.getini(EMPTY_PARAMETERSET_OPTION) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -12,6 +12,7 @@ from ..compat import MappingMixin | ||||||
| from ..compat import NOTSET | from ..compat import NOTSET | ||||||
| from _pytest.deprecated import PYTEST_PARAM_UNKNOWN_KWARGS | from _pytest.deprecated import PYTEST_PARAM_UNKNOWN_KWARGS | ||||||
| from _pytest.outcomes import fail | from _pytest.outcomes import fail | ||||||
|  | from _pytest.warning_types import PytestUnknownMarkWarning | ||||||
| 
 | 
 | ||||||
| EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" | EMPTY_PARAMETERSET_OPTION = "empty_parameter_set_mark" | ||||||
| 
 | 
 | ||||||
|  | @ -135,7 +136,7 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")): | ||||||
|                     ) |                     ) | ||||||
|         else: |         else: | ||||||
|             # empty parameter set (likely computed at runtime): create a single |             # empty parameter set (likely computed at runtime): create a single | ||||||
|             # parameter set with NOSET values, with the "empty parameter set" mark applied to it |             # parameter set with NOTSET values, with the "empty parameter set" mark applied to it | ||||||
|             mark = get_empty_parameterset_mark(config, argnames, func) |             mark = get_empty_parameterset_mark(config, argnames, func) | ||||||
|             parameters.append( |             parameters.append( | ||||||
|                 ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None) |                 ParameterSet(values=(NOTSET,) * len(argnames), marks=[mark], id=None) | ||||||
|  | @ -158,7 +159,7 @@ class Mark(object): | ||||||
|         :type other: Mark |         :type other: Mark | ||||||
|         :rtype: Mark |         :rtype: Mark | ||||||
| 
 | 
 | ||||||
|         combines by appending aargs and merging the mappings |         combines by appending args and merging the mappings | ||||||
|         """ |         """ | ||||||
|         assert self.name == other.name |         assert self.name == other.name | ||||||
|         return Mark( |         return Mark( | ||||||
|  | @ -289,28 +290,41 @@ class MarkGenerator(object): | ||||||
|     on the ``test_function`` object. """ |     on the ``test_function`` object. """ | ||||||
| 
 | 
 | ||||||
|     _config = None |     _config = None | ||||||
|  |     _markers = set() | ||||||
| 
 | 
 | ||||||
|     def __getattr__(self, name): |     def __getattr__(self, name): | ||||||
|         if name[0] == "_": |         if name[0] == "_": | ||||||
|             raise AttributeError("Marker name must NOT start with underscore") |             raise AttributeError("Marker name must NOT start with underscore") | ||||||
|         if self._config is not None: |  | ||||||
|             self._check(name) |  | ||||||
|         return MarkDecorator(Mark(name, (), {})) |  | ||||||
| 
 | 
 | ||||||
|     def _check(self, name): |         if self._config is not None: | ||||||
|         try: |             # We store a set of markers as a performance optimisation - if a mark | ||||||
|             if name in self._markers: |             # name is in the set we definitely know it, but a mark may be known and | ||||||
|                 return |             # not in the set.  We therefore start by updating the set! | ||||||
|         except AttributeError: |             if name not in self._markers: | ||||||
|             pass |                 for line in self._config.getini("markers"): | ||||||
|         self._markers = values = set() |                     # example lines: "skipif(condition): skip the given test if..." | ||||||
|         for line in self._config.getini("markers"): |                     # or "hypothesis: tests which use Hypothesis", so to get the | ||||||
|             marker = line.split(":", 1)[0] |                     # marker name we split on both `:` and `(`. | ||||||
|             marker = marker.rstrip() |                     marker = line.split(":")[0].split("(")[0].strip() | ||||||
|             x = marker.split("(", 1)[0] |                     self._markers.add(marker) | ||||||
|             values.add(x) | 
 | ||||||
|         if name not in self._markers: |             # If the name is not in the set of known marks after updating, | ||||||
|             fail("{!r} not a registered marker".format(name), pytrace=False) |             # then it really is time to issue a warning or an error. | ||||||
|  |             if name not in self._markers: | ||||||
|  |                 if self._config.option.strict_markers: | ||||||
|  |                     fail( | ||||||
|  |                         "{!r} not found in `markers` configuration option".format(name), | ||||||
|  |                         pytrace=False, | ||||||
|  |                     ) | ||||||
|  |                 else: | ||||||
|  |                     warnings.warn( | ||||||
|  |                         "Unknown pytest.mark.%s - is this a typo?  You can register " | ||||||
|  |                         "custom marks to avoid this warning - for details, see " | ||||||
|  |                         "https://docs.pytest.org/en/latest/mark.html" % name, | ||||||
|  |                         PytestUnknownMarkWarning, | ||||||
|  |                     ) | ||||||
|  | 
 | ||||||
|  |         return MarkDecorator(Mark(name, (), {})) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| MARK_GEN = MarkGenerator() | MARK_GEN = MarkGenerator() | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ import sys | ||||||
| 
 | 
 | ||||||
| import six | import six | ||||||
| 
 | 
 | ||||||
|  | import pytest | ||||||
| from _pytest import python | from _pytest import python | ||||||
| from _pytest import runner | from _pytest import runner | ||||||
| from _pytest import unittest | from _pytest import unittest | ||||||
|  | @ -26,7 +27,7 @@ def pytest_runtest_makereport(item, call): | ||||||
|     if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()): |     if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()): | ||||||
|         # let's substitute the excinfo with a pytest.skip one |         # let's substitute the excinfo with a pytest.skip one | ||||||
|         call2 = runner.CallInfo.from_call( |         call2 = runner.CallInfo.from_call( | ||||||
|             lambda: runner.skip(six.text_type(call.excinfo.value)), call.when |             lambda: pytest.skip(six.text_type(call.excinfo.value)), call.when | ||||||
|         ) |         ) | ||||||
|         call.excinfo = call2.excinfo |         call.excinfo = call2.excinfo | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -68,10 +68,19 @@ def pytest_configure(config): | ||||||
|         if checker.matching_platform(): |         if checker.matching_platform(): | ||||||
|             config.pluginmanager.register(checker) |             config.pluginmanager.register(checker) | ||||||
| 
 | 
 | ||||||
|  |     config.addinivalue_line( | ||||||
|  |         "markers", | ||||||
|  |         "pytester_example_path(*path_segments): join the given path " | ||||||
|  |         "segments to `pytester_example_dir` for this test.", | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def raise_on_kwargs(kwargs): | def raise_on_kwargs(kwargs): | ||||||
|     if kwargs: |     __tracebackhide__ = True | ||||||
|         raise TypeError("Unexpected arguments: {}".format(", ".join(sorted(kwargs)))) |     if kwargs:  # pragma: no branch | ||||||
|  |         raise TypeError( | ||||||
|  |             "Unexpected keyword arguments: {}".format(", ".join(sorted(kwargs))) | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class LsofFdLeakChecker(object): | class LsofFdLeakChecker(object): | ||||||
|  | @ -303,7 +312,8 @@ class HookRecorder(object): | ||||||
|                     passed.append(rep) |                     passed.append(rep) | ||||||
|             elif rep.skipped: |             elif rep.skipped: | ||||||
|                 skipped.append(rep) |                 skipped.append(rep) | ||||||
|             elif rep.failed: |             else: | ||||||
|  |                 assert rep.failed, "Unexpected outcome: {!r}".format(rep) | ||||||
|                 failed.append(rep) |                 failed.append(rep) | ||||||
|         return passed, skipped, failed |         return passed, skipped, failed | ||||||
| 
 | 
 | ||||||
|  | @ -476,6 +486,8 @@ class Testdir(object): | ||||||
| 
 | 
 | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|  |     CLOSE_STDIN = object | ||||||
|  | 
 | ||||||
|     class TimeoutExpired(Exception): |     class TimeoutExpired(Exception): | ||||||
|         pass |         pass | ||||||
| 
 | 
 | ||||||
|  | @ -787,12 +799,15 @@ class Testdir(object): | ||||||
| 
 | 
 | ||||||
|         :param args: command line arguments to pass to :py:func:`pytest.main` |         :param args: command line arguments to pass to :py:func:`pytest.main` | ||||||
| 
 | 
 | ||||||
|         :param plugin: (keyword-only) extra plugin instances the |         :param plugins: (keyword-only) extra plugin instances the | ||||||
|            ``pytest.main()`` instance should use |            ``pytest.main()`` instance should use | ||||||
| 
 | 
 | ||||||
|         :return: a :py:class:`HookRecorder` instance |         :return: a :py:class:`HookRecorder` instance | ||||||
| 
 |  | ||||||
|         """ |         """ | ||||||
|  |         plugins = kwargs.pop("plugins", []) | ||||||
|  |         no_reraise_ctrlc = kwargs.pop("no_reraise_ctrlc", None) | ||||||
|  |         raise_on_kwargs(kwargs) | ||||||
|  | 
 | ||||||
|         finalizers = [] |         finalizers = [] | ||||||
|         try: |         try: | ||||||
|             # Do not load user config (during runs only). |             # Do not load user config (during runs only). | ||||||
|  | @ -832,7 +847,6 @@ class Testdir(object): | ||||||
|                 def pytest_configure(x, config): |                 def pytest_configure(x, config): | ||||||
|                     rec.append(self.make_hook_recorder(config.pluginmanager)) |                     rec.append(self.make_hook_recorder(config.pluginmanager)) | ||||||
| 
 | 
 | ||||||
|             plugins = kwargs.get("plugins") or [] |  | ||||||
|             plugins.append(Collect()) |             plugins.append(Collect()) | ||||||
|             ret = pytest.main(list(args), plugins=plugins) |             ret = pytest.main(list(args), plugins=plugins) | ||||||
|             if len(rec) == 1: |             if len(rec) == 1: | ||||||
|  | @ -846,7 +860,7 @@ class Testdir(object): | ||||||
| 
 | 
 | ||||||
|             # typically we reraise keyboard interrupts from the child run |             # typically we reraise keyboard interrupts from the child run | ||||||
|             # because it's our user requesting interruption of the testing |             # because it's our user requesting interruption of the testing | ||||||
|             if ret == EXIT_INTERRUPTED and not kwargs.get("no_reraise_ctrlc"): |             if ret == EXIT_INTERRUPTED and not no_reraise_ctrlc: | ||||||
|                 calls = reprec.getcalls("pytest_keyboard_interrupt") |                 calls = reprec.getcalls("pytest_keyboard_interrupt") | ||||||
|                 if calls and calls[-1].excinfo.type == KeyboardInterrupt: |                 if calls and calls[-1].excinfo.type == KeyboardInterrupt: | ||||||
|                     raise KeyboardInterrupt() |                     raise KeyboardInterrupt() | ||||||
|  | @ -858,9 +872,10 @@ class Testdir(object): | ||||||
|     def runpytest_inprocess(self, *args, **kwargs): |     def runpytest_inprocess(self, *args, **kwargs): | ||||||
|         """Return result of running pytest in-process, providing a similar |         """Return result of running pytest in-process, providing a similar | ||||||
|         interface to what self.runpytest() provides. |         interface to what self.runpytest() provides. | ||||||
| 
 |  | ||||||
|         """ |         """ | ||||||
|         if kwargs.get("syspathinsert"): |         syspathinsert = kwargs.pop("syspathinsert", False) | ||||||
|  | 
 | ||||||
|  |         if syspathinsert: | ||||||
|             self.syspathinsert() |             self.syspathinsert() | ||||||
|         now = time.time() |         now = time.time() | ||||||
|         capture = MultiCapture(Capture=SysCapture) |         capture = MultiCapture(Capture=SysCapture) | ||||||
|  | @ -1018,7 +1033,14 @@ class Testdir(object): | ||||||
|             if colitem.name == name: |             if colitem.name == name: | ||||||
|                 return colitem |                 return colitem | ||||||
| 
 | 
 | ||||||
|     def popen(self, cmdargs, stdout, stderr, **kw): |     def popen( | ||||||
|  |         self, | ||||||
|  |         cmdargs, | ||||||
|  |         stdout=subprocess.PIPE, | ||||||
|  |         stderr=subprocess.PIPE, | ||||||
|  |         stdin=CLOSE_STDIN, | ||||||
|  |         **kw | ||||||
|  |     ): | ||||||
|         """Invoke subprocess.Popen. |         """Invoke subprocess.Popen. | ||||||
| 
 | 
 | ||||||
|         This calls subprocess.Popen making sure the current working directory |         This calls subprocess.Popen making sure the current working directory | ||||||
|  | @ -1036,10 +1058,18 @@ class Testdir(object): | ||||||
|         env["USERPROFILE"] = env["HOME"] |         env["USERPROFILE"] = env["HOME"] | ||||||
|         kw["env"] = env |         kw["env"] = env | ||||||
| 
 | 
 | ||||||
|         popen = subprocess.Popen( |         if stdin is Testdir.CLOSE_STDIN: | ||||||
|             cmdargs, stdin=subprocess.PIPE, stdout=stdout, stderr=stderr, **kw |             kw["stdin"] = subprocess.PIPE | ||||||
|         ) |         elif isinstance(stdin, bytes): | ||||||
|         popen.stdin.close() |             kw["stdin"] = subprocess.PIPE | ||||||
|  |         else: | ||||||
|  |             kw["stdin"] = stdin | ||||||
|  | 
 | ||||||
|  |         popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) | ||||||
|  |         if stdin is Testdir.CLOSE_STDIN: | ||||||
|  |             popen.stdin.close() | ||||||
|  |         elif isinstance(stdin, bytes): | ||||||
|  |             popen.stdin.write(stdin) | ||||||
| 
 | 
 | ||||||
|         return popen |         return popen | ||||||
| 
 | 
 | ||||||
|  | @ -1051,6 +1081,10 @@ class Testdir(object): | ||||||
|         :param args: the sequence of arguments to pass to `subprocess.Popen()` |         :param args: the sequence of arguments to pass to `subprocess.Popen()` | ||||||
|         :param timeout: the period in seconds after which to timeout and raise |         :param timeout: the period in seconds after which to timeout and raise | ||||||
|             :py:class:`Testdir.TimeoutExpired` |             :py:class:`Testdir.TimeoutExpired` | ||||||
|  |         :param stdin: optional standard input.  Bytes are being send, closing | ||||||
|  |             the pipe, otherwise it is passed through to ``popen``. | ||||||
|  |             Defaults to ``CLOSE_STDIN``, which translates to using a pipe | ||||||
|  |             (``subprocess.PIPE``) that gets closed. | ||||||
| 
 | 
 | ||||||
|         Returns a :py:class:`RunResult`. |         Returns a :py:class:`RunResult`. | ||||||
| 
 | 
 | ||||||
|  | @ -1058,6 +1092,7 @@ class Testdir(object): | ||||||
|         __tracebackhide__ = True |         __tracebackhide__ = True | ||||||
| 
 | 
 | ||||||
|         timeout = kwargs.pop("timeout", None) |         timeout = kwargs.pop("timeout", None) | ||||||
|  |         stdin = kwargs.pop("stdin", Testdir.CLOSE_STDIN) | ||||||
|         raise_on_kwargs(kwargs) |         raise_on_kwargs(kwargs) | ||||||
| 
 | 
 | ||||||
|         cmdargs = [ |         cmdargs = [ | ||||||
|  | @ -1072,8 +1107,14 @@ class Testdir(object): | ||||||
|         try: |         try: | ||||||
|             now = time.time() |             now = time.time() | ||||||
|             popen = self.popen( |             popen = self.popen( | ||||||
|                 cmdargs, stdout=f1, stderr=f2, close_fds=(sys.platform != "win32") |                 cmdargs, | ||||||
|  |                 stdin=stdin, | ||||||
|  |                 stdout=f1, | ||||||
|  |                 stderr=f2, | ||||||
|  |                 close_fds=(sys.platform != "win32"), | ||||||
|             ) |             ) | ||||||
|  |             if isinstance(stdin, bytes): | ||||||
|  |                 popen.stdin.close() | ||||||
| 
 | 
 | ||||||
|             def handle_timeout(): |             def handle_timeout(): | ||||||
|                 __tracebackhide__ = True |                 __tracebackhide__ = True | ||||||
|  | @ -1159,9 +1200,10 @@ class Testdir(object): | ||||||
|             :py:class:`Testdir.TimeoutExpired` |             :py:class:`Testdir.TimeoutExpired` | ||||||
| 
 | 
 | ||||||
|         Returns a :py:class:`RunResult`. |         Returns a :py:class:`RunResult`. | ||||||
| 
 |  | ||||||
|         """ |         """ | ||||||
|         __tracebackhide__ = True |         __tracebackhide__ = True | ||||||
|  |         timeout = kwargs.pop("timeout", None) | ||||||
|  |         raise_on_kwargs(kwargs) | ||||||
| 
 | 
 | ||||||
|         p = py.path.local.make_numbered_dir( |         p = py.path.local.make_numbered_dir( | ||||||
|             prefix="runpytest-", keep=None, rootdir=self.tmpdir |             prefix="runpytest-", keep=None, rootdir=self.tmpdir | ||||||
|  | @ -1171,7 +1213,7 @@ class Testdir(object): | ||||||
|         if plugins: |         if plugins: | ||||||
|             args = ("-p", plugins[0]) + args |             args = ("-p", plugins[0]) + args | ||||||
|         args = self._getpytestargs() + args |         args = self._getpytestargs() + args | ||||||
|         return self.run(*args, timeout=kwargs.get("timeout")) |         return self.run(*args, timeout=timeout) | ||||||
| 
 | 
 | ||||||
|     def spawn_pytest(self, string, expect_timeout=10.0): |     def spawn_pytest(self, string, expect_timeout=10.0): | ||||||
|         """Run pytest using pexpect. |         """Run pytest using pexpect. | ||||||
|  |  | ||||||
|  | @ -45,7 +45,8 @@ from _pytest.mark.structures import normalize_mark_list | ||||||
| from _pytest.outcomes import fail | from _pytest.outcomes import fail | ||||||
| from _pytest.outcomes import skip | from _pytest.outcomes import skip | ||||||
| from _pytest.pathlib import parts | from _pytest.pathlib import parts | ||||||
| from _pytest.warning_types import PytestWarning | from _pytest.warning_types import PytestCollectionWarning | ||||||
|  | from _pytest.warning_types import PytestUnhandledCoroutineWarning | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def pyobj_property(name): | def pyobj_property(name): | ||||||
|  | @ -171,7 +172,7 @@ def pytest_pyfunc_call(pyfuncitem): | ||||||
|         msg += "  - pytest-asyncio\n" |         msg += "  - pytest-asyncio\n" | ||||||
|         msg += "  - pytest-trio\n" |         msg += "  - pytest-trio\n" | ||||||
|         msg += "  - pytest-tornasync" |         msg += "  - pytest-tornasync" | ||||||
|         warnings.warn(PytestWarning(msg.format(pyfuncitem.nodeid))) |         warnings.warn(PytestUnhandledCoroutineWarning(msg.format(pyfuncitem.nodeid))) | ||||||
|         skip(msg="coroutine function and no async plugin installed (see warnings)") |         skip(msg="coroutine function and no async plugin installed (see warnings)") | ||||||
|     funcargs = pyfuncitem.funcargs |     funcargs = pyfuncitem.funcargs | ||||||
|     testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} |     testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} | ||||||
|  | @ -221,7 +222,7 @@ def pytest_pycollect_makeitem(collector, name, obj): | ||||||
|         if not (isfunction(obj) or isfunction(get_real_func(obj))): |         if not (isfunction(obj) or isfunction(get_real_func(obj))): | ||||||
|             filename, lineno = getfslineno(obj) |             filename, lineno = getfslineno(obj) | ||||||
|             warnings.warn_explicit( |             warnings.warn_explicit( | ||||||
|                 message=PytestWarning( |                 message=PytestCollectionWarning( | ||||||
|                     "cannot collect %r because it is not a function." % name |                     "cannot collect %r because it is not a function." % name | ||||||
|                 ), |                 ), | ||||||
|                 category=None, |                 category=None, | ||||||
|  | @ -233,7 +234,7 @@ def pytest_pycollect_makeitem(collector, name, obj): | ||||||
|                 res = Function(name, parent=collector) |                 res = Function(name, parent=collector) | ||||||
|                 reason = deprecated.YIELD_TESTS.format(name=name) |                 reason = deprecated.YIELD_TESTS.format(name=name) | ||||||
|                 res.add_marker(MARK_GEN.xfail(run=False, reason=reason)) |                 res.add_marker(MARK_GEN.xfail(run=False, reason=reason)) | ||||||
|                 res.warn(PytestWarning(reason)) |                 res.warn(PytestCollectionWarning(reason)) | ||||||
|             else: |             else: | ||||||
|                 res = list(collector._genfunctions(name, obj)) |                 res = list(collector._genfunctions(name, obj)) | ||||||
|             outcome.force_result(res) |             outcome.force_result(res) | ||||||
|  | @ -721,7 +722,7 @@ class Class(PyCollector): | ||||||
|             return [] |             return [] | ||||||
|         if hasinit(self.obj): |         if hasinit(self.obj): | ||||||
|             self.warn( |             self.warn( | ||||||
|                 PytestWarning( |                 PytestCollectionWarning( | ||||||
|                     "cannot collect test class %r because it has a " |                     "cannot collect test class %r because it has a " | ||||||
|                     "__init__ constructor" % self.obj.__name__ |                     "__init__ constructor" % self.obj.__name__ | ||||||
|                 ) |                 ) | ||||||
|  | @ -729,7 +730,7 @@ class Class(PyCollector): | ||||||
|             return [] |             return [] | ||||||
|         elif hasnew(self.obj): |         elif hasnew(self.obj): | ||||||
|             self.warn( |             self.warn( | ||||||
|                 PytestWarning( |                 PytestCollectionWarning( | ||||||
|                     "cannot collect test class %r because it has a " |                     "cannot collect test class %r because it has a " | ||||||
|                     "__new__ constructor" % self.obj.__name__ |                     "__new__ constructor" % self.obj.__name__ | ||||||
|                 ) |                 ) | ||||||
|  | @ -1341,17 +1342,19 @@ def _showfixtures_main(config, session): | ||||||
|                 currentmodule = module |                 currentmodule = module | ||||||
|         if verbose <= 0 and argname[0] == "_": |         if verbose <= 0 and argname[0] == "_": | ||||||
|             continue |             continue | ||||||
|  |         tw.write(argname, green=True) | ||||||
|  |         if fixturedef.scope != "function": | ||||||
|  |             tw.write(" [%s scope]" % fixturedef.scope, cyan=True) | ||||||
|         if verbose > 0: |         if verbose > 0: | ||||||
|             funcargspec = "%s -- %s" % (argname, bestrel) |             tw.write(" -- %s" % bestrel, yellow=True) | ||||||
|         else: |         tw.write("\n") | ||||||
|             funcargspec = argname |  | ||||||
|         tw.line(funcargspec, green=True) |  | ||||||
|         loc = getlocation(fixturedef.func, curdir) |         loc = getlocation(fixturedef.func, curdir) | ||||||
|         doc = fixturedef.func.__doc__ or "" |         doc = fixturedef.func.__doc__ or "" | ||||||
|         if doc: |         if doc: | ||||||
|             write_docstring(tw, doc) |             write_docstring(tw, doc) | ||||||
|         else: |         else: | ||||||
|             tw.line("    %s: no docstring available" % (loc,), red=True) |             tw.line("    %s: no docstring available" % (loc,), red=True) | ||||||
|  |         tw.line() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def write_docstring(tw, doc, indent="    "): | def write_docstring(tw, doc, indent="    "): | ||||||
|  |  | ||||||
|  | @ -148,6 +148,12 @@ class BaseReport(object): | ||||||
|             fspath, lineno, domain = self.location |             fspath, lineno, domain = self.location | ||||||
|             return domain |             return domain | ||||||
| 
 | 
 | ||||||
|  |     def _get_verbose_word(self, config): | ||||||
|  |         _category, _short, verbose = config.hook.pytest_report_teststatus( | ||||||
|  |             report=self, config=config | ||||||
|  |         ) | ||||||
|  |         return verbose | ||||||
|  | 
 | ||||||
|     def _to_json(self): |     def _to_json(self): | ||||||
|         """ |         """ | ||||||
|         This was originally the serialize_report() function from xdist (ca03269). |         This was originally the serialize_report() function from xdist (ca03269). | ||||||
|  | @ -328,7 +334,8 @@ class TestReport(BaseReport): | ||||||
|         self.__dict__.update(extra) |         self.__dict__.update(extra) | ||||||
| 
 | 
 | ||||||
|     def __repr__(self): |     def __repr__(self): | ||||||
|         return "<TestReport %r when=%r outcome=%r>" % ( |         return "<%s %r when=%r outcome=%r>" % ( | ||||||
|  |             self.__class__.__name__, | ||||||
|             self.nodeid, |             self.nodeid, | ||||||
|             self.when, |             self.when, | ||||||
|             self.outcome, |             self.outcome, | ||||||
|  |  | ||||||
|  | @ -16,7 +16,6 @@ from .reports import CollectReport | ||||||
| from .reports import TestReport | from .reports import TestReport | ||||||
| from _pytest._code.code import ExceptionInfo | from _pytest._code.code import ExceptionInfo | ||||||
| from _pytest.outcomes import Exit | from _pytest.outcomes import Exit | ||||||
| from _pytest.outcomes import skip |  | ||||||
| from _pytest.outcomes import Skipped | from _pytest.outcomes import Skipped | ||||||
| from _pytest.outcomes import TEST_OUTCOME | from _pytest.outcomes import TEST_OUTCOME | ||||||
| 
 | 
 | ||||||
|  | @ -183,7 +182,7 @@ def call_and_report(item, when, log=True, **kwds): | ||||||
| def check_interactive_exception(call, report): | def check_interactive_exception(call, report): | ||||||
|     return call.excinfo and not ( |     return call.excinfo and not ( | ||||||
|         hasattr(report, "wasxfail") |         hasattr(report, "wasxfail") | ||||||
|         or call.excinfo.errisinstance(skip.Exception) |         or call.excinfo.errisinstance(Skipped) | ||||||
|         or call.excinfo.errisinstance(bdb.BdbQuit) |         or call.excinfo.errisinstance(bdb.BdbQuit) | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | # coding=utf8 | ||||||
| """ support for skip/xfail functions and markers. """ | """ support for skip/xfail functions and markers. """ | ||||||
| from __future__ import absolute_import | from __future__ import absolute_import | ||||||
| from __future__ import division | from __future__ import division | ||||||
|  | @ -183,128 +184,3 @@ def pytest_report_teststatus(report): | ||||||
|             return "xfailed", "x", "XFAIL" |             return "xfailed", "x", "XFAIL" | ||||||
|         elif report.passed: |         elif report.passed: | ||||||
|             return "xpassed", "X", "XPASS" |             return "xpassed", "X", "XPASS" | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| # called by the terminalreporter instance/plugin |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def pytest_terminal_summary(terminalreporter): |  | ||||||
|     tr = terminalreporter |  | ||||||
|     if not tr.reportchars: |  | ||||||
|         return |  | ||||||
| 
 |  | ||||||
|     lines = [] |  | ||||||
|     for char in tr.reportchars: |  | ||||||
|         action = REPORTCHAR_ACTIONS.get(char, lambda tr, lines: None) |  | ||||||
|         action(terminalreporter, lines) |  | ||||||
| 
 |  | ||||||
|     if lines: |  | ||||||
|         tr._tw.sep("=", "short test summary info") |  | ||||||
|         for line in lines: |  | ||||||
|             tr._tw.line(line) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def show_simple(terminalreporter, lines, stat): |  | ||||||
|     failed = terminalreporter.stats.get(stat) |  | ||||||
|     if failed: |  | ||||||
|         config = terminalreporter.config |  | ||||||
|         for rep in failed: |  | ||||||
|             verbose_word = _get_report_str(config, rep) |  | ||||||
|             pos = _get_pos(config, rep) |  | ||||||
|             lines.append("%s %s" % (verbose_word, pos)) |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 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"), |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | # encoding: utf-8 | ||||||
| """ terminal reporting of the full testing process. | """ terminal reporting of the full testing process. | ||||||
| 
 | 
 | ||||||
| This is a good source for looking at the various reporting hooks. | This is a good source for looking at the various reporting hooks. | ||||||
|  | @ -11,6 +12,7 @@ import collections | ||||||
| import platform | import platform | ||||||
| import sys | import sys | ||||||
| import time | import time | ||||||
|  | from functools import partial | ||||||
| 
 | 
 | ||||||
| import attr | import attr | ||||||
| import pluggy | import pluggy | ||||||
|  | @ -81,11 +83,11 @@ def pytest_addoption(parser): | ||||||
|         dest="reportchars", |         dest="reportchars", | ||||||
|         default="", |         default="", | ||||||
|         metavar="chars", |         metavar="chars", | ||||||
|         help="show extra test summary info as specified by chars (f)ailed, " |         help="show extra test summary info as specified by chars: (f)ailed, " | ||||||
|         "(E)error, (s)skipped, (x)failed, (X)passed, " |         "(E)rror, (s)kipped, (x)failed, (X)passed, " | ||||||
|         "(p)passed, (P)passed with output, (a)all except pP. " |         "(p)assed, (P)assed with output, (a)ll except passed (p/P), or (A)ll. " | ||||||
|         "Warnings are displayed at all times except when " |         "Warnings are displayed at all times except when " | ||||||
|         "--disable-warnings is set", |         "--disable-warnings is set.", | ||||||
|     ) |     ) | ||||||
|     group._addoption( |     group._addoption( | ||||||
|         "--disable-warnings", |         "--disable-warnings", | ||||||
|  | @ -140,7 +142,7 @@ def pytest_addoption(parser): | ||||||
| 
 | 
 | ||||||
|     parser.addini( |     parser.addini( | ||||||
|         "console_output_style", |         "console_output_style", | ||||||
|         help="console output: classic or with additional progress information (classic|progress).", |         help='console output: "classic", or with additional progress information ("progress" (percentage) | "count").', | ||||||
|         default="progress", |         default="progress", | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|  | @ -164,15 +166,18 @@ def getreportopt(config): | ||||||
|         reportchars += "w" |         reportchars += "w" | ||||||
|     elif config.option.disable_warnings and "w" in reportchars: |     elif config.option.disable_warnings and "w" in reportchars: | ||||||
|         reportchars = reportchars.replace("w", "") |         reportchars = reportchars.replace("w", "") | ||||||
|     if reportchars: |     for char in reportchars: | ||||||
|         for char in reportchars: |         if char == "a": | ||||||
|             if char not in reportopts and char != "a": |             reportopts = "sxXwEf" | ||||||
|                 reportopts += char |         elif char == "A": | ||||||
|             elif char == "a": |             reportopts = "sxXwEfpP" | ||||||
|                 reportopts = "sxXwEf" |             break | ||||||
|  |         elif char not in reportopts: | ||||||
|  |             reportopts += char | ||||||
|     return reportopts |     return reportopts | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @pytest.hookimpl(trylast=True)  # after _pytest.runner | ||||||
| def pytest_report_teststatus(report): | def pytest_report_teststatus(report): | ||||||
|     if report.passed: |     if report.passed: | ||||||
|         letter = "." |         letter = "." | ||||||
|  | @ -230,7 +235,7 @@ class TerminalReporter(object): | ||||||
|         self._showfspath = None |         self._showfspath = None | ||||||
| 
 | 
 | ||||||
|         self.stats = {} |         self.stats = {} | ||||||
|         self.startdir = py.path.local() |         self.startdir = config.invocation_dir | ||||||
|         if file is None: |         if file is None: | ||||||
|             file = sys.stdout |             file = sys.stdout | ||||||
|         self._tw = _pytest.config.create_terminal_writer(config, file) |         self._tw = _pytest.config.create_terminal_writer(config, file) | ||||||
|  | @ -253,7 +258,10 @@ class TerminalReporter(object): | ||||||
|         # do not show progress if we are showing fixture setup/teardown |         # do not show progress if we are showing fixture setup/teardown | ||||||
|         if self.config.getoption("setupshow", False): |         if self.config.getoption("setupshow", False): | ||||||
|             return False |             return False | ||||||
|         return self.config.getini("console_output_style") in ("progress", "count") |         cfg = self.config.getini("console_output_style") | ||||||
|  |         if cfg in ("progress", "count"): | ||||||
|  |             return cfg | ||||||
|  |         return False | ||||||
| 
 | 
 | ||||||
|     @property |     @property | ||||||
|     def verbosity(self): |     def verbosity(self): | ||||||
|  | @ -437,18 +445,18 @@ class TerminalReporter(object): | ||||||
|                 self.currentfspath = -2 |                 self.currentfspath = -2 | ||||||
| 
 | 
 | ||||||
|     def pytest_runtest_logfinish(self, nodeid): |     def pytest_runtest_logfinish(self, nodeid): | ||||||
|         if self.config.getini("console_output_style") == "count": |  | ||||||
|             num_tests = self._session.testscollected |  | ||||||
|             progress_length = len(" [{}/{}]".format(str(num_tests), str(num_tests))) |  | ||||||
|         else: |  | ||||||
|             progress_length = len(" [100%]") |  | ||||||
| 
 |  | ||||||
|         if self.verbosity <= 0 and self._show_progress_info: |         if self.verbosity <= 0 and self._show_progress_info: | ||||||
|  |             if self._show_progress_info == "count": | ||||||
|  |                 num_tests = self._session.testscollected | ||||||
|  |                 progress_length = len(" [{}/{}]".format(str(num_tests), str(num_tests))) | ||||||
|  |             else: | ||||||
|  |                 progress_length = len(" [100%]") | ||||||
|  | 
 | ||||||
|             self._progress_nodeids_reported.add(nodeid) |             self._progress_nodeids_reported.add(nodeid) | ||||||
|             last_item = ( |             is_last_item = ( | ||||||
|                 len(self._progress_nodeids_reported) == self._session.testscollected |                 len(self._progress_nodeids_reported) == self._session.testscollected | ||||||
|             ) |             ) | ||||||
|             if last_item: |             if is_last_item: | ||||||
|                 self._write_progress_information_filling_space() |                 self._write_progress_information_filling_space() | ||||||
|             else: |             else: | ||||||
|                 w = self._width_of_current_line |                 w = self._width_of_current_line | ||||||
|  | @ -459,7 +467,7 @@ class TerminalReporter(object): | ||||||
| 
 | 
 | ||||||
|     def _get_progress_information_message(self): |     def _get_progress_information_message(self): | ||||||
|         collected = self._session.testscollected |         collected = self._session.testscollected | ||||||
|         if self.config.getini("console_output_style") == "count": |         if self._show_progress_info == "count": | ||||||
|             if collected: |             if collected: | ||||||
|                 progress = self._progress_nodeids_reported |                 progress = self._progress_nodeids_reported | ||||||
|                 counter_format = "{{:{}d}}".format(len(str(collected))) |                 counter_format = "{{:{}d}}".format(len(str(collected))) | ||||||
|  | @ -545,10 +553,6 @@ class TerminalReporter(object): | ||||||
|         else: |         else: | ||||||
|             self.write_line(line) |             self.write_line(line) | ||||||
| 
 | 
 | ||||||
|     @pytest.hookimpl(trylast=True) |  | ||||||
|     def pytest_collection_modifyitems(self): |  | ||||||
|         self.report_collect(True) |  | ||||||
| 
 |  | ||||||
|     @pytest.hookimpl(trylast=True) |     @pytest.hookimpl(trylast=True) | ||||||
|     def pytest_sessionstart(self, session): |     def pytest_sessionstart(self, session): | ||||||
|         self._session = session |         self._session = session | ||||||
|  | @ -601,6 +605,8 @@ class TerminalReporter(object): | ||||||
|         return result |         return result | ||||||
| 
 | 
 | ||||||
|     def pytest_collection_finish(self, session): |     def pytest_collection_finish(self, session): | ||||||
|  |         self.report_collect(True) | ||||||
|  | 
 | ||||||
|         if self.config.getoption("collectonly"): |         if self.config.getoption("collectonly"): | ||||||
|             self._printcollecteditems(session.items) |             self._printcollecteditems(session.items) | ||||||
| 
 | 
 | ||||||
|  | @ -676,8 +682,9 @@ class TerminalReporter(object): | ||||||
|         self.summary_errors() |         self.summary_errors() | ||||||
|         self.summary_failures() |         self.summary_failures() | ||||||
|         self.summary_warnings() |         self.summary_warnings() | ||||||
|         yield |  | ||||||
|         self.summary_passes() |         self.summary_passes() | ||||||
|  |         yield | ||||||
|  |         self.short_test_summary() | ||||||
|         # Display any extra warnings from teardown here (if any). |         # Display any extra warnings from teardown here (if any). | ||||||
|         self.summary_warnings() |         self.summary_warnings() | ||||||
| 
 | 
 | ||||||
|  | @ -725,10 +732,10 @@ class TerminalReporter(object): | ||||||
|         return res + " " |         return res + " " | ||||||
| 
 | 
 | ||||||
|     def _getfailureheadline(self, rep): |     def _getfailureheadline(self, rep): | ||||||
|         if rep.head_line: |         head_line = rep.head_line | ||||||
|             return rep.head_line |         if head_line: | ||||||
|         else: |             return head_line | ||||||
|             return "test session"  # XXX? |         return "test session"  # XXX? | ||||||
| 
 | 
 | ||||||
|     def _getcrashline(self, rep): |     def _getcrashline(self, rep): | ||||||
|         try: |         try: | ||||||
|  | @ -797,7 +804,7 @@ class TerminalReporter(object): | ||||||
|                 for rep in reports: |                 for rep in reports: | ||||||
|                     if rep.sections: |                     if rep.sections: | ||||||
|                         msg = self._getfailureheadline(rep) |                         msg = self._getfailureheadline(rep) | ||||||
|                         self.write_sep("_", msg) |                         self.write_sep("_", msg, green=True, bold=True) | ||||||
|                         self._outrep_summary(rep) |                         self._outrep_summary(rep) | ||||||
| 
 | 
 | ||||||
|     def print_teardown_sections(self, rep): |     def print_teardown_sections(self, rep): | ||||||
|  | @ -819,17 +826,22 @@ class TerminalReporter(object): | ||||||
|             if not reports: |             if not reports: | ||||||
|                 return |                 return | ||||||
|             self.write_sep("=", "FAILURES") |             self.write_sep("=", "FAILURES") | ||||||
|             for rep in reports: |             if self.config.option.tbstyle == "line": | ||||||
|                 if self.config.option.tbstyle == "line": |                 for rep in reports: | ||||||
|                     line = self._getcrashline(rep) |                     line = self._getcrashline(rep) | ||||||
|                     self.write_line(line) |                     self.write_line(line) | ||||||
|                 else: |             else: | ||||||
|  |                 teardown_sections = {} | ||||||
|  |                 for report in self.getreports(""): | ||||||
|  |                     if report.when == "teardown": | ||||||
|  |                         teardown_sections.setdefault(report.nodeid, []).append(report) | ||||||
|  | 
 | ||||||
|  |                 for rep in reports: | ||||||
|                     msg = self._getfailureheadline(rep) |                     msg = self._getfailureheadline(rep) | ||||||
|                     self.write_sep("_", msg, red=True, bold=True) |                     self.write_sep("_", msg, red=True, bold=True) | ||||||
|                     self._outrep_summary(rep) |                     self._outrep_summary(rep) | ||||||
|                     for report in self.getreports(""): |                     for report in teardown_sections.get(rep.nodeid, []): | ||||||
|                         if report.nodeid == rep.nodeid and report.when == "teardown": |                         self.print_teardown_sections(report) | ||||||
|                             self.print_teardown_sections(report) |  | ||||||
| 
 | 
 | ||||||
|     def summary_errors(self): |     def summary_errors(self): | ||||||
|         if self.config.option.tbstyle != "no": |         if self.config.option.tbstyle != "no": | ||||||
|  | @ -841,10 +853,8 @@ class TerminalReporter(object): | ||||||
|                 msg = self._getfailureheadline(rep) |                 msg = self._getfailureheadline(rep) | ||||||
|                 if rep.when == "collect": |                 if rep.when == "collect": | ||||||
|                     msg = "ERROR collecting " + msg |                     msg = "ERROR collecting " + msg | ||||||
|                 elif rep.when == "setup": |                 else: | ||||||
|                     msg = "ERROR at setup of " + msg |                     msg = "ERROR at %s of %s" % (rep.when, msg) | ||||||
|                 elif rep.when == "teardown": |  | ||||||
|                     msg = "ERROR at teardown of " + msg |  | ||||||
|                 self.write_sep("_", msg, red=True, bold=True) |                 self.write_sep("_", msg, red=True, bold=True) | ||||||
|                 self._outrep_summary(rep) |                 self._outrep_summary(rep) | ||||||
| 
 | 
 | ||||||
|  | @ -872,6 +882,149 @@ class TerminalReporter(object): | ||||||
|         if self.verbosity == -1: |         if self.verbosity == -1: | ||||||
|             self.write_line(msg, **markup) |             self.write_line(msg, **markup) | ||||||
| 
 | 
 | ||||||
|  |     def short_test_summary(self): | ||||||
|  |         if not self.reportchars: | ||||||
|  |             return | ||||||
|  | 
 | ||||||
|  |         def show_simple(stat, lines): | ||||||
|  |             failed = self.stats.get(stat, []) | ||||||
|  |             if not failed: | ||||||
|  |                 return | ||||||
|  |             termwidth = self.writer.fullwidth | ||||||
|  |             config = self.config | ||||||
|  |             for rep in failed: | ||||||
|  |                 line = _get_line_with_reprcrash_message(config, rep, termwidth) | ||||||
|  |                 lines.append(line) | ||||||
|  | 
 | ||||||
|  |         def show_xfailed(lines): | ||||||
|  |             xfailed = self.stats.get("xfailed", []) | ||||||
|  |             for rep in xfailed: | ||||||
|  |                 verbose_word = rep._get_verbose_word(self.config) | ||||||
|  |                 pos = _get_pos(self.config, rep) | ||||||
|  |                 lines.append("%s %s" % (verbose_word, pos)) | ||||||
|  |                 reason = rep.wasxfail | ||||||
|  |                 if reason: | ||||||
|  |                     lines.append("  " + str(reason)) | ||||||
|  | 
 | ||||||
|  |         def show_xpassed(lines): | ||||||
|  |             xpassed = self.stats.get("xpassed", []) | ||||||
|  |             for rep in xpassed: | ||||||
|  |                 verbose_word = rep._get_verbose_word(self.config) | ||||||
|  |                 pos = _get_pos(self.config, rep) | ||||||
|  |                 reason = rep.wasxfail | ||||||
|  |                 lines.append("%s %s %s" % (verbose_word, pos, reason)) | ||||||
|  | 
 | ||||||
|  |         def show_skipped(lines): | ||||||
|  |             skipped = self.stats.get("skipped", []) | ||||||
|  |             fskips = _folded_skips(skipped) if skipped else [] | ||||||
|  |             if not fskips: | ||||||
|  |                 return | ||||||
|  |             verbose_word = skipped[0]._get_verbose_word(self.config) | ||||||
|  |             for num, fspath, lineno, reason in fskips: | ||||||
|  |                 if reason.startswith("Skipped: "): | ||||||
|  |                     reason = reason[9:] | ||||||
|  |                 if lineno is not None: | ||||||
|  |                     lines.append( | ||||||
|  |                         "%s [%d] %s:%d: %s" | ||||||
|  |                         % (verbose_word, num, fspath, lineno + 1, reason) | ||||||
|  |                     ) | ||||||
|  |                 else: | ||||||
|  |                     lines.append("%s [%d] %s: %s" % (verbose_word, num, fspath, reason)) | ||||||
|  | 
 | ||||||
|  |         REPORTCHAR_ACTIONS = { | ||||||
|  |             "x": show_xfailed, | ||||||
|  |             "X": show_xpassed, | ||||||
|  |             "f": partial(show_simple, "failed"), | ||||||
|  |             "F": partial(show_simple, "failed"), | ||||||
|  |             "s": show_skipped, | ||||||
|  |             "S": show_skipped, | ||||||
|  |             "p": partial(show_simple, "passed"), | ||||||
|  |             "E": partial(show_simple, "error"), | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         lines = [] | ||||||
|  |         for char in self.reportchars: | ||||||
|  |             action = REPORTCHAR_ACTIONS.get(char) | ||||||
|  |             if action:  # skipping e.g. "P" (passed with output) here. | ||||||
|  |                 action(lines) | ||||||
|  | 
 | ||||||
|  |         if lines: | ||||||
|  |             self.write_sep("=", "short test summary info") | ||||||
|  |             for line in lines: | ||||||
|  |                 self.write_line(line) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def _get_pos(config, rep): | ||||||
|  |     nodeid = config.cwd_relative_nodeid(rep.nodeid) | ||||||
|  |     return nodeid | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def _get_line_with_reprcrash_message(config, rep, termwidth): | ||||||
|  |     """Get summary line for a report, trying to add reprcrash message.""" | ||||||
|  |     from wcwidth import wcswidth | ||||||
|  | 
 | ||||||
|  |     verbose_word = rep._get_verbose_word(config) | ||||||
|  |     pos = _get_pos(config, rep) | ||||||
|  | 
 | ||||||
|  |     line = "%s %s" % (verbose_word, pos) | ||||||
|  |     len_line = wcswidth(line) | ||||||
|  |     ellipsis, len_ellipsis = "...", 3 | ||||||
|  |     if len_line > termwidth - len_ellipsis: | ||||||
|  |         # No space for an additional message. | ||||||
|  |         return line | ||||||
|  | 
 | ||||||
|  |     try: | ||||||
|  |         msg = rep.longrepr.reprcrash.message | ||||||
|  |     except AttributeError: | ||||||
|  |         pass | ||||||
|  |     else: | ||||||
|  |         # Only use the first line. | ||||||
|  |         i = msg.find("\n") | ||||||
|  |         if i != -1: | ||||||
|  |             msg = msg[:i] | ||||||
|  |         len_msg = wcswidth(msg) | ||||||
|  | 
 | ||||||
|  |         sep, len_sep = " - ", 3 | ||||||
|  |         max_len_msg = termwidth - len_line - len_sep | ||||||
|  |         if max_len_msg >= len_ellipsis: | ||||||
|  |             if len_msg > max_len_msg: | ||||||
|  |                 max_len_msg -= len_ellipsis | ||||||
|  |                 msg = msg[:max_len_msg] | ||||||
|  |                 while wcswidth(msg) > max_len_msg: | ||||||
|  |                     msg = msg[:-1] | ||||||
|  |                 if six.PY2: | ||||||
|  |                     # on python 2 systems with narrow unicode compilation, trying to | ||||||
|  |                     # get a single character out of a multi-byte unicode character such as | ||||||
|  |                     # u'😄' will result in a High Surrogate (U+D83D) character, which is | ||||||
|  |                     # rendered as u'<27>'; in this case we just strip that character out as it | ||||||
|  |                     # serves no purpose being rendered | ||||||
|  |                     msg = msg.rstrip(u"\uD83D") | ||||||
|  |                 msg += ellipsis | ||||||
|  |             line += sep + msg | ||||||
|  |     return line | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def _folded_skips(skipped): | ||||||
|  |     d = {} | ||||||
|  |     for event in skipped: | ||||||
|  |         key = event.longrepr | ||||||
|  |         assert len(key) == 3, (event, key) | ||||||
|  |         keywords = getattr(event, "keywords", {}) | ||||||
|  |         # folding reports with global pytestmark variable | ||||||
|  |         # this is workaround, because for now we cannot identify the scope of a skip marker | ||||||
|  |         # TODO: revisit after marks scope would be fixed | ||||||
|  |         if ( | ||||||
|  |             event.when == "setup" | ||||||
|  |             and "skip" in keywords | ||||||
|  |             and "pytestmark" not in keywords | ||||||
|  |         ): | ||||||
|  |             key = (key[0], None, key[2]) | ||||||
|  |         d.setdefault(key, []).append(event) | ||||||
|  |     values = [] | ||||||
|  |     for key, events in d.items(): | ||||||
|  |         values.append((len(events),) + key) | ||||||
|  |     return values | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def build_summary_stats_line(stats): | def build_summary_stats_line(stats): | ||||||
|     known_types = ( |     known_types = ( | ||||||
|  |  | ||||||
|  | @ -9,6 +9,38 @@ class PytestWarning(UserWarning): | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class PytestAssertRewriteWarning(PytestWarning): | ||||||
|  |     """ | ||||||
|  |     Bases: :class:`PytestWarning`. | ||||||
|  | 
 | ||||||
|  |     Warning emitted by the pytest assert rewrite module. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PytestCacheWarning(PytestWarning): | ||||||
|  |     """ | ||||||
|  |     Bases: :class:`PytestWarning`. | ||||||
|  | 
 | ||||||
|  |     Warning emitted by the cache plugin in various situations. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PytestConfigWarning(PytestWarning): | ||||||
|  |     """ | ||||||
|  |     Bases: :class:`PytestWarning`. | ||||||
|  | 
 | ||||||
|  |     Warning emitted for configuration issues. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PytestCollectionWarning(PytestWarning): | ||||||
|  |     """ | ||||||
|  |     Bases: :class:`PytestWarning`. | ||||||
|  | 
 | ||||||
|  |     Warning emitted when pytest is not able to collect a file or symbol in a module. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class PytestDeprecationWarning(PytestWarning, DeprecationWarning): | class PytestDeprecationWarning(PytestWarning, DeprecationWarning): | ||||||
|     """ |     """ | ||||||
|     Bases: :class:`pytest.PytestWarning`, :class:`DeprecationWarning`. |     Bases: :class:`pytest.PytestWarning`, :class:`DeprecationWarning`. | ||||||
|  | @ -17,14 +49,6 @@ class PytestDeprecationWarning(PytestWarning, DeprecationWarning): | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class RemovedInPytest4Warning(PytestDeprecationWarning): |  | ||||||
|     """ |  | ||||||
|     Bases: :class:`pytest.PytestDeprecationWarning`. |  | ||||||
| 
 |  | ||||||
|     Warning class for features scheduled to be removed in pytest 4.0. |  | ||||||
|     """ |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| class PytestExperimentalApiWarning(PytestWarning, FutureWarning): | class PytestExperimentalApiWarning(PytestWarning, FutureWarning): | ||||||
|     """ |     """ | ||||||
|     Bases: :class:`pytest.PytestWarning`, :class:`FutureWarning`. |     Bases: :class:`pytest.PytestWarning`, :class:`FutureWarning`. | ||||||
|  | @ -42,6 +66,33 @@ class PytestExperimentalApiWarning(PytestWarning, FutureWarning): | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | class PytestUnhandledCoroutineWarning(PytestWarning): | ||||||
|  |     """ | ||||||
|  |     Bases: :class:`PytestWarning`. | ||||||
|  | 
 | ||||||
|  |     Warning emitted when pytest encounters a test function which is a coroutine, | ||||||
|  |     but it was not handled by any async-aware plugin. Coroutine test functions | ||||||
|  |     are not natively supported. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class PytestUnknownMarkWarning(PytestWarning): | ||||||
|  |     """ | ||||||
|  |     Bases: :class:`PytestWarning`. | ||||||
|  | 
 | ||||||
|  |     Warning emitted on use of unknown markers. | ||||||
|  |     See https://docs.pytest.org/en/latest/mark.html for details. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class RemovedInPytest4Warning(PytestDeprecationWarning): | ||||||
|  |     """ | ||||||
|  |     Bases: :class:`pytest.PytestDeprecationWarning`. | ||||||
|  | 
 | ||||||
|  |     Warning class for features scheduled to be removed in pytest 4.0. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @attr.s | @attr.s | ||||||
| class UnformattedWarning(object): | class UnformattedWarning(object): | ||||||
|     """Used to hold warnings that need to format their message at runtime, as opposed to a direct message. |     """Used to hold warnings that need to format their message at runtime, as opposed to a direct message. | ||||||
|  |  | ||||||
|  | @ -35,8 +35,14 @@ from _pytest.python_api import approx | ||||||
| from _pytest.python_api import raises | from _pytest.python_api import raises | ||||||
| from _pytest.recwarn import deprecated_call | from _pytest.recwarn import deprecated_call | ||||||
| from _pytest.recwarn import warns | from _pytest.recwarn import warns | ||||||
|  | from _pytest.warning_types import PytestAssertRewriteWarning | ||||||
|  | from _pytest.warning_types import PytestCacheWarning | ||||||
|  | from _pytest.warning_types import PytestCollectionWarning | ||||||
|  | from _pytest.warning_types import PytestConfigWarning | ||||||
| from _pytest.warning_types import PytestDeprecationWarning | from _pytest.warning_types import PytestDeprecationWarning | ||||||
| from _pytest.warning_types import PytestExperimentalApiWarning | from _pytest.warning_types import PytestExperimentalApiWarning | ||||||
|  | from _pytest.warning_types import PytestUnhandledCoroutineWarning | ||||||
|  | from _pytest.warning_types import PytestUnknownMarkWarning | ||||||
| from _pytest.warning_types import PytestWarning | from _pytest.warning_types import PytestWarning | ||||||
| from _pytest.warning_types import RemovedInPytest4Warning | from _pytest.warning_types import RemovedInPytest4Warning | ||||||
| 
 | 
 | ||||||
|  | @ -66,8 +72,14 @@ __all__ = [ | ||||||
|     "Module", |     "Module", | ||||||
|     "Package", |     "Package", | ||||||
|     "param", |     "param", | ||||||
|  |     "PytestAssertRewriteWarning", | ||||||
|  |     "PytestCacheWarning", | ||||||
|  |     "PytestCollectionWarning", | ||||||
|  |     "PytestConfigWarning", | ||||||
|     "PytestDeprecationWarning", |     "PytestDeprecationWarning", | ||||||
|     "PytestExperimentalApiWarning", |     "PytestExperimentalApiWarning", | ||||||
|  |     "PytestUnhandledCoroutineWarning", | ||||||
|  |     "PytestUnknownMarkWarning", | ||||||
|     "PytestWarning", |     "PytestWarning", | ||||||
|     "raises", |     "raises", | ||||||
|     "register_assert_rewrite", |     "register_assert_rewrite", | ||||||
|  |  | ||||||
|  | @ -428,9 +428,20 @@ class TestGeneralUsage(object): | ||||||
|             assert result.ret == 4  # usage error only if item not found |             assert result.ret == 4  # usage error only if item not found | ||||||
| 
 | 
 | ||||||
|     def test_report_all_failed_collections_initargs(self, testdir): |     def test_report_all_failed_collections_initargs(self, testdir): | ||||||
|  |         testdir.makeconftest( | ||||||
|  |             """ | ||||||
|  |             from _pytest.main import EXIT_USAGEERROR | ||||||
|  | 
 | ||||||
|  |             def pytest_sessionfinish(exitstatus): | ||||||
|  |                 assert exitstatus == EXIT_USAGEERROR | ||||||
|  |                 print("pytest_sessionfinish_called") | ||||||
|  |             """ | ||||||
|  |         ) | ||||||
|         testdir.makepyfile(test_a="def", test_b="def") |         testdir.makepyfile(test_a="def", test_b="def") | ||||||
|         result = testdir.runpytest("test_a.py::a", "test_b.py::b") |         result = testdir.runpytest("test_a.py::a", "test_b.py::b") | ||||||
|         result.stderr.fnmatch_lines(["*ERROR*test_a.py::a*", "*ERROR*test_b.py::b*"]) |         result.stderr.fnmatch_lines(["*ERROR*test_a.py::a*", "*ERROR*test_b.py::b*"]) | ||||||
|  |         result.stdout.fnmatch_lines(["pytest_sessionfinish_called"]) | ||||||
|  |         assert result.ret == EXIT_USAGEERROR | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.usefixtures("recwarn") |     @pytest.mark.usefixtures("recwarn") | ||||||
|     def test_namespace_import_doesnt_confuse_import_hook(self, testdir): |     def test_namespace_import_doesnt_confuse_import_hook(self, testdir): | ||||||
|  | @ -865,7 +876,9 @@ class TestInvocationVariants(object): | ||||||
|                 _fail, _sep, testid = line.partition(" ") |                 _fail, _sep, testid = line.partition(" ") | ||||||
|                 break |                 break | ||||||
|         result = testdir.runpytest(testid, "-rf") |         result = testdir.runpytest(testid, "-rf") | ||||||
|         result.stdout.fnmatch_lines([line, "*1 failed*"]) |         result.stdout.fnmatch_lines( | ||||||
|  |             ["FAILED test_doctest_id.txt::test_doctest_id.txt", "*1 failed*"] | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|     def test_core_backward_compatibility(self): |     def test_core_backward_compatibility(self): | ||||||
|         """Test backward compatibility for get_plugin_manager function. See #787.""" |         """Test backward compatibility for get_plugin_manager function. See #787.""" | ||||||
|  |  | ||||||
|  | @ -11,7 +11,6 @@ import textwrap | ||||||
| import py | import py | ||||||
| import six | import six | ||||||
| from six.moves import queue | from six.moves import queue | ||||||
| from test_source import astonly |  | ||||||
| 
 | 
 | ||||||
| import _pytest | import _pytest | ||||||
| import pytest | import pytest | ||||||
|  | @ -147,7 +146,6 @@ class TestTraceback_f_g_h(object): | ||||||
|         assert s.startswith("def f():") |         assert s.startswith("def f():") | ||||||
|         assert s.endswith("raise ValueError") |         assert s.endswith("raise ValueError") | ||||||
| 
 | 
 | ||||||
|     @astonly |  | ||||||
|     @failsonjython |     @failsonjython | ||||||
|     def test_traceback_entry_getsource_in_construct(self): |     def test_traceback_entry_getsource_in_construct(self): | ||||||
|         source = _pytest._code.Source( |         source = _pytest._code.Source( | ||||||
|  |  | ||||||
|  | @ -16,7 +16,6 @@ import _pytest._code | ||||||
| import pytest | import pytest | ||||||
| from _pytest._code import Source | from _pytest._code import Source | ||||||
| 
 | 
 | ||||||
| astonly = pytest.mark.nothing |  | ||||||
| failsonjython = pytest.mark.xfail("sys.platform.startswith('java')") | failsonjython = pytest.mark.xfail("sys.platform.startswith('java')") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -227,7 +226,6 @@ class TestSourceParsingAndCompiling(object): | ||||||
|         s = source.getstatement(1) |         s = source.getstatement(1) | ||||||
|         assert s == str(source) |         assert s == str(source) | ||||||
| 
 | 
 | ||||||
|     @astonly |  | ||||||
|     def test_getstatementrange_within_constructs(self): |     def test_getstatementrange_within_constructs(self): | ||||||
|         source = Source( |         source = Source( | ||||||
|             """\ |             """\ | ||||||
|  | @ -630,7 +628,6 @@ x = 3 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestTry(object): | class TestTry(object): | ||||||
|     pytestmark = astonly |  | ||||||
|     source = """\ |     source = """\ | ||||||
| try: | try: | ||||||
|     raise ValueError |     raise ValueError | ||||||
|  | @ -675,7 +672,6 @@ finally: | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestIf(object): | class TestIf(object): | ||||||
|     pytestmark = astonly |  | ||||||
|     source = """\ |     source = """\ | ||||||
| if 1: | if 1: | ||||||
|     y = 3 |     y = 3 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,36 @@ | ||||||
|  | import pytest | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @pytest.hookimpl(hookwrapper=True, tryfirst=True) | ||||||
|  | def pytest_collection_modifyitems(config, items): | ||||||
|  |     """Prefer faster tests. | ||||||
|  | 
 | ||||||
|  |     Use a hookwrapper to do this in the beginning, so e.g. --ff still works | ||||||
|  |     correctly. | ||||||
|  |     """ | ||||||
|  |     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 | ||||||
|  | 
 | ||||||
|  |     yield | ||||||
|  | @ -248,7 +248,7 @@ def test_log_cli_enabled_disabled(testdir, enabled): | ||||||
|             [ |             [ | ||||||
|                 "test_log_cli_enabled_disabled.py::test_log_cli ", |                 "test_log_cli_enabled_disabled.py::test_log_cli ", | ||||||
|                 "*-- live log call --*", |                 "*-- live log call --*", | ||||||
|                 "test_log_cli_enabled_disabled.py* CRITICAL critical message logged by test", |                 "CRITICAL *test_log_cli_enabled_disabled.py* critical message logged by test", | ||||||
|                 "PASSED*", |                 "PASSED*", | ||||||
|             ] |             ] | ||||||
|         ) |         ) | ||||||
|  | @ -282,7 +282,7 @@ def test_log_cli_default_level(testdir): | ||||||
|     result.stdout.fnmatch_lines( |     result.stdout.fnmatch_lines( | ||||||
|         [ |         [ | ||||||
|             "test_log_cli_default_level.py::test_log_cli ", |             "test_log_cli_default_level.py::test_log_cli ", | ||||||
|             "test_log_cli_default_level.py*WARNING message will be shown*", |             "WARNING*test_log_cli_default_level.py* message will be shown*", | ||||||
|         ] |         ] | ||||||
|     ) |     ) | ||||||
|     assert "INFO message won't be shown" not in result.stdout.str() |     assert "INFO message won't be shown" not in result.stdout.str() | ||||||
|  | @ -523,7 +523,7 @@ def test_sections_single_new_line_after_test_outcome(testdir, request): | ||||||
|     ) |     ) | ||||||
|     assert ( |     assert ( | ||||||
|         re.search( |         re.search( | ||||||
|             r"(.+)live log teardown(.+)\n(.+)WARNING(.+)\n(.+)WARNING(.+)", |             r"(.+)live log teardown(.+)\nWARNING(.+)\nWARNING(.+)", | ||||||
|             result.stdout.str(), |             result.stdout.str(), | ||||||
|             re.MULTILINE, |             re.MULTILINE, | ||||||
|         ) |         ) | ||||||
|  | @ -531,7 +531,7 @@ def test_sections_single_new_line_after_test_outcome(testdir, request): | ||||||
|     ) |     ) | ||||||
|     assert ( |     assert ( | ||||||
|         re.search( |         re.search( | ||||||
|             r"(.+)live log finish(.+)\n(.+)WARNING(.+)\n(.+)WARNING(.+)", |             r"(.+)live log finish(.+)\nWARNING(.+)\nWARNING(.+)", | ||||||
|             result.stdout.str(), |             result.stdout.str(), | ||||||
|             re.MULTILINE, |             re.MULTILINE, | ||||||
|         ) |         ) | ||||||
|  | @ -565,7 +565,7 @@ def test_log_cli_level(testdir): | ||||||
|     # fnmatch_lines does an assertion internally |     # fnmatch_lines does an assertion internally | ||||||
|     result.stdout.fnmatch_lines( |     result.stdout.fnmatch_lines( | ||||||
|         [ |         [ | ||||||
|             "test_log_cli_level.py*This log message will be shown", |             "*test_log_cli_level.py*This log message will be shown", | ||||||
|             "PASSED",  # 'PASSED' on its own line because the log message prints a new line |             "PASSED",  # 'PASSED' on its own line because the log message prints a new line | ||||||
|         ] |         ] | ||||||
|     ) |     ) | ||||||
|  | @ -579,7 +579,7 @@ def test_log_cli_level(testdir): | ||||||
|     # fnmatch_lines does an assertion internally |     # fnmatch_lines does an assertion internally | ||||||
|     result.stdout.fnmatch_lines( |     result.stdout.fnmatch_lines( | ||||||
|         [ |         [ | ||||||
|             "test_log_cli_level.py* This log message will be shown", |             "*test_log_cli_level.py* This log message will be shown", | ||||||
|             "PASSED",  # 'PASSED' on its own line because the log message prints a new line |             "PASSED",  # 'PASSED' on its own line because the log message prints a new line | ||||||
|         ] |         ] | ||||||
|     ) |     ) | ||||||
|  | @ -615,7 +615,7 @@ def test_log_cli_ini_level(testdir): | ||||||
|     # fnmatch_lines does an assertion internally |     # fnmatch_lines does an assertion internally | ||||||
|     result.stdout.fnmatch_lines( |     result.stdout.fnmatch_lines( | ||||||
|         [ |         [ | ||||||
|             "test_log_cli_ini_level.py* This log message will be shown", |             "*test_log_cli_ini_level.py* This log message will be shown", | ||||||
|             "PASSED",  # 'PASSED' on its own line because the log message prints a new line |             "PASSED",  # 'PASSED' on its own line because the log message prints a new line | ||||||
|         ] |         ] | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -1925,10 +1925,10 @@ class TestAutouseManagement(object): | ||||||
|         reprec = testdir.inline_run() |         reprec = testdir.inline_run() | ||||||
|         reprec.assertoutcome(passed=1) |         reprec.assertoutcome(passed=1) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue226 |  | ||||||
|     @pytest.mark.parametrize("param1", ["", "params=[1]"], ids=["p00", "p01"]) |     @pytest.mark.parametrize("param1", ["", "params=[1]"], ids=["p00", "p01"]) | ||||||
|     @pytest.mark.parametrize("param2", ["", "params=[1]"], ids=["p10", "p11"]) |     @pytest.mark.parametrize("param2", ["", "params=[1]"], ids=["p10", "p11"]) | ||||||
|     def test_ordering_dependencies_torndown_first(self, testdir, param1, param2): |     def test_ordering_dependencies_torndown_first(self, testdir, param1, param2): | ||||||
|  |         """#226""" | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|             import pytest |             import pytest | ||||||
|  | @ -2707,9 +2707,9 @@ class TestFixtureMarker(object): | ||||||
|         reprec = testdir.inline_run("-v") |         reprec = testdir.inline_run("-v") | ||||||
|         reprec.assertoutcome(passed=5) |         reprec.assertoutcome(passed=5) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue246 |  | ||||||
|     @pytest.mark.parametrize("scope", ["session", "function", "module"]) |     @pytest.mark.parametrize("scope", ["session", "function", "module"]) | ||||||
|     def test_finalizer_order_on_parametrization(self, scope, testdir): |     def test_finalizer_order_on_parametrization(self, scope, testdir): | ||||||
|  |         """#246""" | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|             import pytest |             import pytest | ||||||
|  | @ -2744,8 +2744,8 @@ class TestFixtureMarker(object): | ||||||
|         reprec = testdir.inline_run("-lvs") |         reprec = testdir.inline_run("-lvs") | ||||||
|         reprec.assertoutcome(passed=3) |         reprec.assertoutcome(passed=3) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue396 |  | ||||||
|     def test_class_scope_parametrization_ordering(self, testdir): |     def test_class_scope_parametrization_ordering(self, testdir): | ||||||
|  |         """#396""" | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|             import pytest |             import pytest | ||||||
|  | @ -2865,8 +2865,8 @@ class TestFixtureMarker(object): | ||||||
|         res = testdir.runpytest("-v") |         res = testdir.runpytest("-v") | ||||||
|         res.stdout.fnmatch_lines(["*test_foo*alpha*", "*test_foo*beta*"]) |         res.stdout.fnmatch_lines(["*test_foo*alpha*", "*test_foo*beta*"]) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue920 |  | ||||||
|     def test_deterministic_fixture_collection(self, testdir, monkeypatch): |     def test_deterministic_fixture_collection(self, testdir, monkeypatch): | ||||||
|  |         """#920""" | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|             import pytest |             import pytest | ||||||
|  | @ -3037,11 +3037,25 @@ class TestShowFixtures(object): | ||||||
| 
 | 
 | ||||||
|     def test_show_fixtures(self, testdir): |     def test_show_fixtures(self, testdir): | ||||||
|         result = testdir.runpytest("--fixtures") |         result = testdir.runpytest("--fixtures") | ||||||
|         result.stdout.fnmatch_lines(["*tmpdir*", "*temporary directory*"]) |         result.stdout.fnmatch_lines( | ||||||
|  |             [ | ||||||
|  |                 "tmpdir_factory [[]session scope[]]", | ||||||
|  |                 "*for the test session*", | ||||||
|  |                 "tmpdir", | ||||||
|  |                 "*temporary directory*", | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|     def test_show_fixtures_verbose(self, testdir): |     def test_show_fixtures_verbose(self, testdir): | ||||||
|         result = testdir.runpytest("--fixtures", "-v") |         result = testdir.runpytest("--fixtures", "-v") | ||||||
|         result.stdout.fnmatch_lines(["*tmpdir*--*tmpdir.py*", "*temporary directory*"]) |         result.stdout.fnmatch_lines( | ||||||
|  |             [ | ||||||
|  |                 "tmpdir_factory [[]session scope[]] -- *tmpdir.py*", | ||||||
|  |                 "*for the test session*", | ||||||
|  |                 "tmpdir -- *tmpdir.py*", | ||||||
|  |                 "*temporary directory*", | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|     def test_show_fixtures_testmodule(self, testdir): |     def test_show_fixtures_testmodule(self, testdir): | ||||||
|         p = testdir.makepyfile( |         p = testdir.makepyfile( | ||||||
|  | @ -3635,7 +3649,6 @@ class TestScopeOrdering(object): | ||||||
|     """Class of tests that ensure fixtures are ordered based on their scopes (#2405)""" |     """Class of tests that ensure fixtures are ordered based on their scopes (#2405)""" | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.parametrize("variant", ["mark", "autouse"]) |     @pytest.mark.parametrize("variant", ["mark", "autouse"]) | ||||||
|     @pytest.mark.issue(github="#2405") |  | ||||||
|     def test_func_closure_module_auto(self, testdir, variant, monkeypatch): |     def test_func_closure_module_auto(self, testdir, variant, monkeypatch): | ||||||
|         """Semantically identical to the example posted in #2405 when ``use_mark=True``""" |         """Semantically identical to the example posted in #2405 when ``use_mark=True``""" | ||||||
|         monkeypatch.setenv("FIXTURE_ACTIVATION_VARIANT", variant) |         monkeypatch.setenv("FIXTURE_ACTIVATION_VARIANT", variant) | ||||||
|  |  | ||||||
|  | @ -393,8 +393,9 @@ class TestNoselikeTestAttribute(object): | ||||||
|         assert not call.items |         assert not call.items | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.issue351 |  | ||||||
| class TestParameterize(object): | class TestParameterize(object): | ||||||
|  |     """#351""" | ||||||
|  | 
 | ||||||
|     def test_idfn_marker(self, testdir): |     def test_idfn_marker(self, testdir): | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|  |  | ||||||
|  | @ -159,8 +159,9 @@ class TestMetafunc(object): | ||||||
|                 ("x", "y"), [("abc", "def"), ("ghi", "jkl")], ids=["one"] |                 ("x", "y"), [("abc", "def"), ("ghi", "jkl")], ids=["one"] | ||||||
|             ) |             ) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue510 |  | ||||||
|     def test_parametrize_empty_list(self): |     def test_parametrize_empty_list(self): | ||||||
|  |         """#510""" | ||||||
|  | 
 | ||||||
|         def func(y): |         def func(y): | ||||||
|             pass |             pass | ||||||
| 
 | 
 | ||||||
|  | @ -262,8 +263,8 @@ class TestMetafunc(object): | ||||||
|         for val, expected in values: |         for val, expected in values: | ||||||
|             assert _idval(val, "a", 6, None, item=None, config=None) == expected |             assert _idval(val, "a", 6, None, item=None, config=None) == expected | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue250 |  | ||||||
|     def test_idmaker_autoname(self): |     def test_idmaker_autoname(self): | ||||||
|  |         """#250""" | ||||||
|         from _pytest.python import idmaker |         from _pytest.python import idmaker | ||||||
| 
 | 
 | ||||||
|         result = idmaker( |         result = idmaker( | ||||||
|  | @ -356,8 +357,8 @@ class TestMetafunc(object): | ||||||
|         result = idmaker(("a", "b"), [pytest.param(e.one, e.two)]) |         result = idmaker(("a", "b"), [pytest.param(e.one, e.two)]) | ||||||
|         assert result == ["Foo.one-Foo.two"] |         assert result == ["Foo.one-Foo.two"] | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue351 |  | ||||||
|     def test_idmaker_idfn(self): |     def test_idmaker_idfn(self): | ||||||
|  |         """#351""" | ||||||
|         from _pytest.python import idmaker |         from _pytest.python import idmaker | ||||||
| 
 | 
 | ||||||
|         def ids(val): |         def ids(val): | ||||||
|  | @ -375,8 +376,8 @@ class TestMetafunc(object): | ||||||
|         ) |         ) | ||||||
|         assert result == ["10.0-IndexError()", "20-KeyError()", "three-b2"] |         assert result == ["10.0-IndexError()", "20-KeyError()", "three-b2"] | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue351 |  | ||||||
|     def test_idmaker_idfn_unique_names(self): |     def test_idmaker_idfn_unique_names(self): | ||||||
|  |         """#351""" | ||||||
|         from _pytest.python import idmaker |         from _pytest.python import idmaker | ||||||
| 
 | 
 | ||||||
|         def ids(val): |         def ids(val): | ||||||
|  | @ -459,8 +460,9 @@ class TestMetafunc(object): | ||||||
|         ) |         ) | ||||||
|         assert result == ["a0", "a1", "b0", "c", "b1"] |         assert result == ["a0", "a1", "b0", "c", "b1"] | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue714 |  | ||||||
|     def test_parametrize_indirect(self): |     def test_parametrize_indirect(self): | ||||||
|  |         """#714""" | ||||||
|  | 
 | ||||||
|         def func(x, y): |         def func(x, y): | ||||||
|             pass |             pass | ||||||
| 
 | 
 | ||||||
|  | @ -473,8 +475,9 @@ class TestMetafunc(object): | ||||||
|         assert metafunc._calls[0].params == dict(x=1, y=2) |         assert metafunc._calls[0].params == dict(x=1, y=2) | ||||||
|         assert metafunc._calls[1].params == dict(x=1, y=3) |         assert metafunc._calls[1].params == dict(x=1, y=3) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue714 |  | ||||||
|     def test_parametrize_indirect_list(self): |     def test_parametrize_indirect_list(self): | ||||||
|  |         """#714""" | ||||||
|  | 
 | ||||||
|         def func(x, y): |         def func(x, y): | ||||||
|             pass |             pass | ||||||
| 
 | 
 | ||||||
|  | @ -483,8 +486,9 @@ class TestMetafunc(object): | ||||||
|         assert metafunc._calls[0].funcargs == dict(y="b") |         assert metafunc._calls[0].funcargs == dict(y="b") | ||||||
|         assert metafunc._calls[0].params == dict(x="a") |         assert metafunc._calls[0].params == dict(x="a") | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue714 |  | ||||||
|     def test_parametrize_indirect_list_all(self): |     def test_parametrize_indirect_list_all(self): | ||||||
|  |         """#714""" | ||||||
|  | 
 | ||||||
|         def func(x, y): |         def func(x, y): | ||||||
|             pass |             pass | ||||||
| 
 | 
 | ||||||
|  | @ -493,8 +497,9 @@ class TestMetafunc(object): | ||||||
|         assert metafunc._calls[0].funcargs == {} |         assert metafunc._calls[0].funcargs == {} | ||||||
|         assert metafunc._calls[0].params == dict(x="a", y="b") |         assert metafunc._calls[0].params == dict(x="a", y="b") | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue714 |  | ||||||
|     def test_parametrize_indirect_list_empty(self): |     def test_parametrize_indirect_list_empty(self): | ||||||
|  |         """#714""" | ||||||
|  | 
 | ||||||
|         def func(x, y): |         def func(x, y): | ||||||
|             pass |             pass | ||||||
| 
 | 
 | ||||||
|  | @ -503,9 +508,9 @@ class TestMetafunc(object): | ||||||
|         assert metafunc._calls[0].funcargs == dict(x="a", y="b") |         assert metafunc._calls[0].funcargs == dict(x="a", y="b") | ||||||
|         assert metafunc._calls[0].params == {} |         assert metafunc._calls[0].params == {} | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue714 |  | ||||||
|     def test_parametrize_indirect_list_functional(self, testdir): |     def test_parametrize_indirect_list_functional(self, testdir): | ||||||
|         """ |         """ | ||||||
|  |         #714 | ||||||
|         Test parametrization with 'indirect' parameter applied on |         Test parametrization with 'indirect' parameter applied on | ||||||
|         particular arguments. As y is is direct, its value should |         particular arguments. As y is is direct, its value should | ||||||
|         be used directly rather than being passed to the fixture |         be used directly rather than being passed to the fixture | ||||||
|  | @ -532,8 +537,9 @@ class TestMetafunc(object): | ||||||
|         result = testdir.runpytest("-v") |         result = testdir.runpytest("-v") | ||||||
|         result.stdout.fnmatch_lines(["*test_simple*a-b*", "*1 passed*"]) |         result.stdout.fnmatch_lines(["*test_simple*a-b*", "*1 passed*"]) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue714 |  | ||||||
|     def test_parametrize_indirect_list_error(self, testdir): |     def test_parametrize_indirect_list_error(self, testdir): | ||||||
|  |         """#714""" | ||||||
|  | 
 | ||||||
|         def func(x, y): |         def func(x, y): | ||||||
|             pass |             pass | ||||||
| 
 | 
 | ||||||
|  | @ -541,12 +547,13 @@ class TestMetafunc(object): | ||||||
|         with pytest.raises(pytest.fail.Exception): |         with pytest.raises(pytest.fail.Exception): | ||||||
|             metafunc.parametrize("x, y", [("a", "b")], indirect=["x", "z"]) |             metafunc.parametrize("x, y", [("a", "b")], indirect=["x", "z"]) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue714 |  | ||||||
|     def test_parametrize_uses_no_fixture_error_indirect_false(self, testdir): |     def test_parametrize_uses_no_fixture_error_indirect_false(self, testdir): | ||||||
|         """The 'uses no fixture' error tells the user at collection time |         """The 'uses no fixture' error tells the user at collection time | ||||||
|         that the parametrize data they've set up doesn't correspond to the |         that the parametrize data they've set up doesn't correspond to the | ||||||
|         fixtures in their test function, rather than silently ignoring this |         fixtures in their test function, rather than silently ignoring this | ||||||
|         and letting the test potentially pass. |         and letting the test potentially pass. | ||||||
|  | 
 | ||||||
|  |         #714 | ||||||
|         """ |         """ | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|  | @ -560,8 +567,8 @@ class TestMetafunc(object): | ||||||
|         result = testdir.runpytest("--collect-only") |         result = testdir.runpytest("--collect-only") | ||||||
|         result.stdout.fnmatch_lines(["*uses no argument 'y'*"]) |         result.stdout.fnmatch_lines(["*uses no argument 'y'*"]) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue714 |  | ||||||
|     def test_parametrize_uses_no_fixture_error_indirect_true(self, testdir): |     def test_parametrize_uses_no_fixture_error_indirect_true(self, testdir): | ||||||
|  |         """#714""" | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|             import pytest |             import pytest | ||||||
|  | @ -580,8 +587,8 @@ class TestMetafunc(object): | ||||||
|         result = testdir.runpytest("--collect-only") |         result = testdir.runpytest("--collect-only") | ||||||
|         result.stdout.fnmatch_lines(["*uses no fixture 'y'*"]) |         result.stdout.fnmatch_lines(["*uses no fixture 'y'*"]) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue714 |  | ||||||
|     def test_parametrize_indirect_uses_no_fixture_error_indirect_string(self, testdir): |     def test_parametrize_indirect_uses_no_fixture_error_indirect_string(self, testdir): | ||||||
|  |         """#714""" | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|             import pytest |             import pytest | ||||||
|  | @ -597,8 +604,8 @@ class TestMetafunc(object): | ||||||
|         result = testdir.runpytest("--collect-only") |         result = testdir.runpytest("--collect-only") | ||||||
|         result.stdout.fnmatch_lines(["*uses no fixture 'y'*"]) |         result.stdout.fnmatch_lines(["*uses no fixture 'y'*"]) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue714 |  | ||||||
|     def test_parametrize_indirect_uses_no_fixture_error_indirect_list(self, testdir): |     def test_parametrize_indirect_uses_no_fixture_error_indirect_list(self, testdir): | ||||||
|  |         """#714""" | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|             import pytest |             import pytest | ||||||
|  | @ -614,8 +621,8 @@ class TestMetafunc(object): | ||||||
|         result = testdir.runpytest("--collect-only") |         result = testdir.runpytest("--collect-only") | ||||||
|         result.stdout.fnmatch_lines(["*uses no fixture 'y'*"]) |         result.stdout.fnmatch_lines(["*uses no fixture 'y'*"]) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue714 |  | ||||||
|     def test_parametrize_argument_not_in_indirect_list(self, testdir): |     def test_parametrize_argument_not_in_indirect_list(self, testdir): | ||||||
|  |         """#714""" | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|             import pytest |             import pytest | ||||||
|  | @ -1201,9 +1208,9 @@ class TestMetafuncFunctional(object): | ||||||
|         reprec = testdir.runpytest() |         reprec = testdir.runpytest() | ||||||
|         reprec.assert_outcomes(passed=4) |         reprec.assert_outcomes(passed=4) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue463 |  | ||||||
|     @pytest.mark.parametrize("attr", ["parametrise", "parameterize", "parameterise"]) |     @pytest.mark.parametrize("attr", ["parametrise", "parameterize", "parameterise"]) | ||||||
|     def test_parametrize_misspelling(self, testdir, attr): |     def test_parametrize_misspelling(self, testdir, attr): | ||||||
|  |         """#463""" | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|             import pytest |             import pytest | ||||||
|  | @ -1386,8 +1393,9 @@ class TestMetafuncFunctionalAuto(object): | ||||||
|         assert output.count("preparing foo-3") == 1 |         assert output.count("preparing foo-3") == 1 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.issue308 |  | ||||||
| class TestMarkersWithParametrization(object): | class TestMarkersWithParametrization(object): | ||||||
|  |     """#308""" | ||||||
|  | 
 | ||||||
|     def test_simple_mark(self, testdir): |     def test_simple_mark(self, testdir): | ||||||
|         s = """ |         s = """ | ||||||
|             import pytest |             import pytest | ||||||
|  | @ -1575,8 +1583,8 @@ class TestMarkersWithParametrization(object): | ||||||
|         reprec = testdir.inline_run(SHOW_PYTEST_WARNINGS_ARG) |         reprec = testdir.inline_run(SHOW_PYTEST_WARNINGS_ARG) | ||||||
|         reprec.assertoutcome(passed=2, skipped=2) |         reprec.assertoutcome(passed=2, skipped=2) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue290 |  | ||||||
|     def test_parametrize_ID_generation_string_int_works(self, testdir): |     def test_parametrize_ID_generation_string_int_works(self, testdir): | ||||||
|  |         """#290""" | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|             import pytest |             import pytest | ||||||
|  |  | ||||||
|  | @ -446,6 +446,50 @@ class TestAssert_reprcompare(object): | ||||||
|         assert "Omitting" not in lines[1] |         assert "Omitting" not in lines[1] | ||||||
|         assert lines[2] == "{'b': 1}" |         assert lines[2] == "{'b': 1}" | ||||||
| 
 | 
 | ||||||
|  |     def test_dict_different_items(self): | ||||||
|  |         lines = callequal({"a": 0}, {"b": 1, "c": 2}, verbose=2) | ||||||
|  |         assert lines == [ | ||||||
|  |             "{'a': 0} == {'b': 1, 'c': 2}", | ||||||
|  |             "Left contains 1 more item:", | ||||||
|  |             "{'a': 0}", | ||||||
|  |             "Right contains 2 more items:", | ||||||
|  |             "{'b': 1, 'c': 2}", | ||||||
|  |             "Full diff:", | ||||||
|  |             "- {'a': 0}", | ||||||
|  |             "+ {'b': 1, 'c': 2}", | ||||||
|  |         ] | ||||||
|  |         lines = callequal({"b": 1, "c": 2}, {"a": 0}, verbose=2) | ||||||
|  |         assert lines == [ | ||||||
|  |             "{'b': 1, 'c': 2} == {'a': 0}", | ||||||
|  |             "Left contains 2 more items:", | ||||||
|  |             "{'b': 1, 'c': 2}", | ||||||
|  |             "Right contains 1 more item:", | ||||||
|  |             "{'a': 0}", | ||||||
|  |             "Full diff:", | ||||||
|  |             "- {'b': 1, 'c': 2}", | ||||||
|  |             "+ {'a': 0}", | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|  |     def test_sequence_different_items(self): | ||||||
|  |         lines = callequal((1, 2), (3, 4, 5), verbose=2) | ||||||
|  |         assert lines == [ | ||||||
|  |             "(1, 2) == (3, 4, 5)", | ||||||
|  |             "At index 0 diff: 1 != 3", | ||||||
|  |             "Right contains one more item: 5", | ||||||
|  |             "Full diff:", | ||||||
|  |             "- (1, 2)", | ||||||
|  |             "+ (3, 4, 5)", | ||||||
|  |         ] | ||||||
|  |         lines = callequal((1, 2, 3), (4,), verbose=2) | ||||||
|  |         assert lines == [ | ||||||
|  |             "(1, 2, 3) == (4,)", | ||||||
|  |             "At index 0 diff: 1 != 4", | ||||||
|  |             "Left contains 2 more items, first extra item: 2", | ||||||
|  |             "Full diff:", | ||||||
|  |             "- (1, 2, 3)", | ||||||
|  |             "+ (4,)", | ||||||
|  |         ] | ||||||
|  | 
 | ||||||
|     def test_set(self): |     def test_set(self): | ||||||
|         expl = callequal({0, 1}, {0, 2}) |         expl = callequal({0, 1}, {0, 2}) | ||||||
|         assert len(expl) > 1 |         assert len(expl) > 1 | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ import textwrap | ||||||
| import py | import py | ||||||
| 
 | 
 | ||||||
| import pytest | import pytest | ||||||
|  | from _pytest.main import EXIT_NOTESTSCOLLECTED | ||||||
| 
 | 
 | ||||||
| pytest_plugins = ("pytester",) | pytest_plugins = ("pytester",) | ||||||
| 
 | 
 | ||||||
|  | @ -195,6 +196,7 @@ def test_cache_show(testdir): | ||||||
|         """ |         """ | ||||||
|         def pytest_configure(config): |         def pytest_configure(config): | ||||||
|             config.cache.set("my/name", [1,2,3]) |             config.cache.set("my/name", [1,2,3]) | ||||||
|  |             config.cache.set("my/hello", "world") | ||||||
|             config.cache.set("other/some", {1:2}) |             config.cache.set("other/some", {1:2}) | ||||||
|             dp = config.cache.makedir("mydb") |             dp = config.cache.makedir("mydb") | ||||||
|             dp.ensure("hello") |             dp.ensure("hello") | ||||||
|  | @ -203,20 +205,39 @@ def test_cache_show(testdir): | ||||||
|     ) |     ) | ||||||
|     result = testdir.runpytest() |     result = testdir.runpytest() | ||||||
|     assert result.ret == 5  # no tests executed |     assert result.ret == 5  # no tests executed | ||||||
|  | 
 | ||||||
|     result = testdir.runpytest("--cache-show") |     result = testdir.runpytest("--cache-show") | ||||||
|     result.stdout.fnmatch_lines_random( |     result.stdout.fnmatch_lines( | ||||||
|         [ |         [ | ||||||
|             "*cachedir:*", |             "*cachedir:*", | ||||||
|             "-*cache values*-", |             "*- cache values for '[*]' -*", | ||||||
|             "*my/name contains:", |             "cache/nodeids contains:", | ||||||
|  |             "my/name contains:", | ||||||
|             "  [1, 2, 3]", |             "  [1, 2, 3]", | ||||||
|             "*other/some contains*", |             "other/some contains:", | ||||||
|             "  {*1*: 2}", |             "  {*'1': 2}", | ||||||
|             "-*cache directories*-", |             "*- cache directories for '[*]' -*", | ||||||
|             "*mydb/hello*length 0*", |             "*mydb/hello*length 0*", | ||||||
|             "*mydb/world*length 0*", |             "*mydb/world*length 0*", | ||||||
|         ] |         ] | ||||||
|     ) |     ) | ||||||
|  |     assert result.ret == 0 | ||||||
|  | 
 | ||||||
|  |     result = testdir.runpytest("--cache-show", "*/hello") | ||||||
|  |     result.stdout.fnmatch_lines( | ||||||
|  |         [ | ||||||
|  |             "*cachedir:*", | ||||||
|  |             "*- cache values for '[*]/hello' -*", | ||||||
|  |             "my/hello contains:", | ||||||
|  |             "  *'world'", | ||||||
|  |             "*- cache directories for '[*]/hello' -*", | ||||||
|  |             "d/mydb/hello*length 0*", | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
|  |     stdout = result.stdout.str() | ||||||
|  |     assert "other/some" not in stdout | ||||||
|  |     assert "d/mydb/world" not in stdout | ||||||
|  |     assert result.ret == 0 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class TestLastFailed(object): | class TestLastFailed(object): | ||||||
|  | @ -251,7 +272,13 @@ class TestLastFailed(object): | ||||||
|         result = testdir.runpytest("--lf") |         result = testdir.runpytest("--lf") | ||||||
|         result.stdout.fnmatch_lines(["*2 passed*1 desel*"]) |         result.stdout.fnmatch_lines(["*2 passed*1 desel*"]) | ||||||
|         result = testdir.runpytest("--lf") |         result = testdir.runpytest("--lf") | ||||||
|         result.stdout.fnmatch_lines(["*1 failed*2 passed*"]) |         result.stdout.fnmatch_lines( | ||||||
|  |             [ | ||||||
|  |                 "collected 3 items", | ||||||
|  |                 "run-last-failure: no previously failed tests, not deselecting items.", | ||||||
|  |                 "*1 failed*2 passed*", | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|         result = testdir.runpytest("--lf", "--cache-clear") |         result = testdir.runpytest("--lf", "--cache-clear") | ||||||
|         result.stdout.fnmatch_lines(["*1 failed*2 passed*"]) |         result.stdout.fnmatch_lines(["*1 failed*2 passed*"]) | ||||||
| 
 | 
 | ||||||
|  | @ -418,14 +445,20 @@ class TestLastFailed(object): | ||||||
|         result = testdir.runpytest("--lf") |         result = testdir.runpytest("--lf") | ||||||
|         result.stdout.fnmatch_lines( |         result.stdout.fnmatch_lines( | ||||||
|             [ |             [ | ||||||
|                 "collected 4 items / 2 deselected / 2 selected", |                 "collected 2 items", | ||||||
|                 "run-last-failure: rerun previous 2 failures", |                 "run-last-failure: rerun previous 2 failures (skipped 1 file)", | ||||||
|                 "*2 failed, 2 deselected in*", |                 "*2 failed in*", | ||||||
|             ] |             ] | ||||||
|         ) |         ) | ||||||
| 
 | 
 | ||||||
|         result = testdir.runpytest(test_a, "--lf") |         result = testdir.runpytest(test_a, "--lf") | ||||||
|         result.stdout.fnmatch_lines(["collected 2 items", "*2 passed in*"]) |         result.stdout.fnmatch_lines( | ||||||
|  |             [ | ||||||
|  |                 "collected 2 items", | ||||||
|  |                 "run-last-failure: 2 known failures not in selected tests", | ||||||
|  |                 "*2 passed in*", | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
| 
 | 
 | ||||||
|         result = testdir.runpytest(test_b, "--lf") |         result = testdir.runpytest(test_b, "--lf") | ||||||
|         result.stdout.fnmatch_lines( |         result.stdout.fnmatch_lines( | ||||||
|  | @ -685,7 +718,7 @@ class TestLastFailed(object): | ||||||
|         assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"] |         assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"] | ||||||
| 
 | 
 | ||||||
|         result = testdir.runpytest("--last-failed") |         result = testdir.runpytest("--last-failed") | ||||||
|         result.stdout.fnmatch_lines(["*1 failed, 3 deselected*"]) |         result.stdout.fnmatch_lines(["*1 failed, 1 deselected*"]) | ||||||
|         assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"] |         assert self.get_cached_last_failed(testdir) == ["test_foo.py::test_foo_4"] | ||||||
| 
 | 
 | ||||||
|         # 3. fix test_foo_4, run only test_foo.py |         # 3. fix test_foo_4, run only test_foo.py | ||||||
|  | @ -721,7 +754,14 @@ class TestLastFailed(object): | ||||||
|         result = testdir.runpytest("--lf", "--lfnf", "all") |         result = testdir.runpytest("--lf", "--lfnf", "all") | ||||||
|         result.stdout.fnmatch_lines(["*2 passed*"]) |         result.stdout.fnmatch_lines(["*2 passed*"]) | ||||||
|         result = testdir.runpytest("--lf", "--lfnf", "none") |         result = testdir.runpytest("--lf", "--lfnf", "none") | ||||||
|         result.stdout.fnmatch_lines(["*2 desel*"]) |         result.stdout.fnmatch_lines( | ||||||
|  |             [ | ||||||
|  |                 "collected 2 items / 2 deselected", | ||||||
|  |                 "run-last-failure: no previously failed tests, deselecting all items.", | ||||||
|  |                 "* 2 deselected in *", | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |         assert result.ret == EXIT_NOTESTSCOLLECTED | ||||||
| 
 | 
 | ||||||
|     def test_lastfailed_no_failures_behavior_empty_cache(self, testdir): |     def test_lastfailed_no_failures_behavior_empty_cache(self, testdir): | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|  | @ -739,6 +779,58 @@ class TestLastFailed(object): | ||||||
|         result = testdir.runpytest("--lf", "--cache-clear", "--lfnf", "none") |         result = testdir.runpytest("--lf", "--cache-clear", "--lfnf", "none") | ||||||
|         result.stdout.fnmatch_lines(["*2 desel*"]) |         result.stdout.fnmatch_lines(["*2 desel*"]) | ||||||
| 
 | 
 | ||||||
|  |     def test_lastfailed_skip_collection(self, testdir): | ||||||
|  |         """ | ||||||
|  |         Test --lf behavior regarding skipping collection of files that are not marked as | ||||||
|  |         failed in the cache (#5172). | ||||||
|  |         """ | ||||||
|  |         testdir.makepyfile( | ||||||
|  |             **{ | ||||||
|  |                 "pkg1/test_1.py": """ | ||||||
|  |                 import pytest | ||||||
|  | 
 | ||||||
|  |                 @pytest.mark.parametrize('i', range(3)) | ||||||
|  |                 def test_1(i): pass | ||||||
|  |             """, | ||||||
|  |                 "pkg2/test_2.py": """ | ||||||
|  |                 import pytest | ||||||
|  | 
 | ||||||
|  |                 @pytest.mark.parametrize('i', range(5)) | ||||||
|  |                 def test_1(i): | ||||||
|  |                     assert i not in (1, 3) | ||||||
|  |             """, | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |         # first run: collects 8 items (test_1: 3, test_2: 5) | ||||||
|  |         result = testdir.runpytest() | ||||||
|  |         result.stdout.fnmatch_lines(["collected 8 items", "*2 failed*6 passed*"]) | ||||||
|  |         # second run: collects only 5 items from test_2, because all tests from test_1 have passed | ||||||
|  |         result = testdir.runpytest("--lf") | ||||||
|  |         result.stdout.fnmatch_lines( | ||||||
|  |             [ | ||||||
|  |                 "collected 5 items / 3 deselected / 2 selected", | ||||||
|  |                 "run-last-failure: rerun previous 2 failures (skipped 1 file)", | ||||||
|  |                 "*2 failed*3 deselected*", | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # add another file and check if message is correct when skipping more than 1 file | ||||||
|  |         testdir.makepyfile( | ||||||
|  |             **{ | ||||||
|  |                 "pkg1/test_3.py": """ | ||||||
|  |                 def test_3(): pass | ||||||
|  |             """ | ||||||
|  |             } | ||||||
|  |         ) | ||||||
|  |         result = testdir.runpytest("--lf") | ||||||
|  |         result.stdout.fnmatch_lines( | ||||||
|  |             [ | ||||||
|  |                 "collected 5 items / 3 deselected / 2 selected", | ||||||
|  |                 "run-last-failure: rerun previous 2 failures (skipped 2 files)", | ||||||
|  |                 "*2 failed*3 deselected*", | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| class TestNewFirst(object): | class TestNewFirst(object): | ||||||
|     def test_newfirst_usecase(self, testdir): |     def test_newfirst_usecase(self, testdir): | ||||||
|  |  | ||||||
|  | @ -605,8 +605,8 @@ class TestCaptureFixture(object): | ||||||
|         result.stdout.fnmatch_lines(["*KeyboardInterrupt*"]) |         result.stdout.fnmatch_lines(["*KeyboardInterrupt*"]) | ||||||
|         assert result.ret == 2 |         assert result.ret == 2 | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue14 |  | ||||||
|     def test_capture_and_logging(self, testdir): |     def test_capture_and_logging(self, testdir): | ||||||
|  |         """#14""" | ||||||
|         p = testdir.makepyfile( |         p = testdir.makepyfile( | ||||||
|             """\ |             """\ | ||||||
|             import logging |             import logging | ||||||
|  | @ -819,15 +819,15 @@ def test_error_during_readouterr(testdir): | ||||||
|     testdir.makepyfile( |     testdir.makepyfile( | ||||||
|         pytest_xyz=""" |         pytest_xyz=""" | ||||||
|         from _pytest.capture import FDCapture |         from _pytest.capture import FDCapture | ||||||
|  | 
 | ||||||
|         def bad_snap(self): |         def bad_snap(self): | ||||||
|             raise Exception('boom') |             raise Exception('boom') | ||||||
|  | 
 | ||||||
|         assert FDCapture.snap |         assert FDCapture.snap | ||||||
|         FDCapture.snap = bad_snap |         FDCapture.snap = bad_snap | ||||||
|     """ |     """ | ||||||
|     ) |     ) | ||||||
|     result = testdir.runpytest_subprocess( |     result = testdir.runpytest_subprocess("-p", "pytest_xyz", "--version") | ||||||
|         "-p", "pytest_xyz", "--version", syspathinsert=True |  | ||||||
|     ) |  | ||||||
|     result.stderr.fnmatch_lines( |     result.stderr.fnmatch_lines( | ||||||
|         ["*in bad_snap", "    raise Exception('boom')", "Exception: boom"] |         ["*in bad_snap", "    raise Exception('boom')", "Exception: boom"] | ||||||
|     ) |     ) | ||||||
|  | @ -1243,25 +1243,24 @@ class TestStdCaptureFDinvalidFD(object): | ||||||
|             from _pytest import capture |             from _pytest import capture | ||||||
| 
 | 
 | ||||||
|             def StdCaptureFD(out=True, err=True, in_=True): |             def StdCaptureFD(out=True, err=True, in_=True): | ||||||
|                 return capture.MultiCapture(out, err, in_, |                 return capture.MultiCapture(out, err, in_, Capture=capture.FDCapture) | ||||||
|                                             Capture=capture.FDCapture) |  | ||||||
| 
 | 
 | ||||||
|             def test_stdout(): |             def test_stdout(): | ||||||
|                 os.close(1) |                 os.close(1) | ||||||
|                 cap = StdCaptureFD(out=True, err=False, in_=False) |                 cap = StdCaptureFD(out=True, err=False, in_=False) | ||||||
|                 assert repr(cap.out) == "<FDCapture 1 oldfd=None>" |                 assert repr(cap.out) == "<FDCapture 1 oldfd=None _state=None>" | ||||||
|                 cap.stop_capturing() |                 cap.stop_capturing() | ||||||
| 
 | 
 | ||||||
|             def test_stderr(): |             def test_stderr(): | ||||||
|                 os.close(2) |                 os.close(2) | ||||||
|                 cap = StdCaptureFD(out=False, err=True, in_=False) |                 cap = StdCaptureFD(out=False, err=True, in_=False) | ||||||
|                 assert repr(cap.err) == "<FDCapture 2 oldfd=None>" |                 assert repr(cap.err) == "<FDCapture 2 oldfd=None _state=None>" | ||||||
|                 cap.stop_capturing() |                 cap.stop_capturing() | ||||||
| 
 | 
 | ||||||
|             def test_stdin(): |             def test_stdin(): | ||||||
|                 os.close(0) |                 os.close(0) | ||||||
|                 cap = StdCaptureFD(out=False, err=False, in_=True) |                 cap = StdCaptureFD(out=False, err=False, in_=True) | ||||||
|                 assert repr(cap.in_) == "<FDCapture 0 oldfd=None>" |                 assert repr(cap.in_) == "<FDCapture 0 oldfd=None _state=None>" | ||||||
|                 cap.stop_capturing() |                 cap.stop_capturing() | ||||||
|         """ |         """ | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|  | @ -1212,20 +1212,12 @@ def test_config_does_not_load_blocked_plugin_from_args(testdir): | ||||||
|     [ |     [ | ||||||
|         x |         x | ||||||
|         for x in _pytest.config.default_plugins |         for x in _pytest.config.default_plugins | ||||||
|         if x |         if x not in _pytest.config.essential_plugins | ||||||
|         not in [ |  | ||||||
|             "fixtures", |  | ||||||
|             "helpconfig",  # Provides -p. |  | ||||||
|             "main", |  | ||||||
|             "mark", |  | ||||||
|             "python", |  | ||||||
|             "runner", |  | ||||||
|             "terminal",  # works in OK case (no output), but not with failures. |  | ||||||
|         ] |  | ||||||
|     ], |     ], | ||||||
| ) | ) | ||||||
| def test_config_blocked_default_plugins(testdir, plugin): | def test_config_blocked_default_plugins(testdir, plugin): | ||||||
|     if plugin == "debugging": |     if plugin == "debugging": | ||||||
|  |         # Fixed in xdist master (after 1.27.0). | ||||||
|         # https://github.com/pytest-dev/pytest-xdist/pull/422 |         # https://github.com/pytest-dev/pytest-xdist/pull/422 | ||||||
|         try: |         try: | ||||||
|             import xdist  # noqa: F401 |             import xdist  # noqa: F401 | ||||||
|  | @ -1237,9 +1229,13 @@ def test_config_blocked_default_plugins(testdir, plugin): | ||||||
|     p = testdir.makepyfile("def test(): pass") |     p = testdir.makepyfile("def test(): pass") | ||||||
|     result = testdir.runpytest(str(p), "-pno:%s" % plugin) |     result = testdir.runpytest(str(p), "-pno:%s" % plugin) | ||||||
|     assert result.ret == EXIT_OK |     assert result.ret == EXIT_OK | ||||||
|     result.stdout.fnmatch_lines(["* 1 passed in *"]) |     if plugin != "terminal": | ||||||
|  |         result.stdout.fnmatch_lines(["* 1 passed in *"]) | ||||||
| 
 | 
 | ||||||
|     p = testdir.makepyfile("def test(): assert 0") |     p = testdir.makepyfile("def test(): assert 0") | ||||||
|     result = testdir.runpytest(str(p), "-pno:%s" % plugin) |     result = testdir.runpytest(str(p), "-pno:%s" % plugin) | ||||||
|     assert result.ret == EXIT_TESTSFAILED |     assert result.ret == EXIT_TESTSFAILED | ||||||
|     result.stdout.fnmatch_lines(["* 1 failed in *"]) |     if plugin != "terminal": | ||||||
|  |         result.stdout.fnmatch_lines(["* 1 failed in *"]) | ||||||
|  |     else: | ||||||
|  |         assert result.stdout.lines == [""] | ||||||
|  |  | ||||||
|  | @ -491,10 +491,10 @@ class TestConftestVisibility(object): | ||||||
|             ("snc", ".", 1), |             ("snc", ".", 1), | ||||||
|         ], |         ], | ||||||
|     ) |     ) | ||||||
|     @pytest.mark.issue616 |  | ||||||
|     def test_parsefactories_relative_node_ids( |     def test_parsefactories_relative_node_ids( | ||||||
|         self, testdir, chdir, testarg, expect_ntests_passed |         self, testdir, chdir, testarg, expect_ntests_passed | ||||||
|     ): |     ): | ||||||
|  |         """#616""" | ||||||
|         dirs = self._setup_tree(testdir) |         dirs = self._setup_tree(testdir) | ||||||
|         print("pytest run in cwd: %s" % (dirs[chdir].relto(testdir.tmpdir))) |         print("pytest run in cwd: %s" % (dirs[chdir].relto(testdir.tmpdir))) | ||||||
|         print("pytestarg        : %s" % (testarg)) |         print("pytestarg        : %s" % (testarg)) | ||||||
|  |  | ||||||
|  | @ -485,9 +485,27 @@ class TestPython(object): | ||||||
|         tnode = node.find_first_by_tag("testcase") |         tnode = node.find_first_by_tag("testcase") | ||||||
|         tnode.assert_attr(classname="test_xfailure_function", name="test_xfail") |         tnode.assert_attr(classname="test_xfailure_function", name="test_xfail") | ||||||
|         fnode = tnode.find_first_by_tag("skipped") |         fnode = tnode.find_first_by_tag("skipped") | ||||||
|         fnode.assert_attr(message="expected test failure") |         fnode.assert_attr(type="pytest.xfail", message="42") | ||||||
|         # assert "ValueError" in fnode.toxml() |         # assert "ValueError" in fnode.toxml() | ||||||
| 
 | 
 | ||||||
|  |     def test_xfailure_marker(self, testdir): | ||||||
|  |         testdir.makepyfile( | ||||||
|  |             """ | ||||||
|  |             import pytest | ||||||
|  |             @pytest.mark.xfail(reason="42") | ||||||
|  |             def test_xfail(): | ||||||
|  |                 assert False | ||||||
|  |         """ | ||||||
|  |         ) | ||||||
|  |         result, dom = runandparse(testdir) | ||||||
|  |         assert not result.ret | ||||||
|  |         node = dom.find_first_by_tag("testsuite") | ||||||
|  |         node.assert_attr(skipped=1, tests=1) | ||||||
|  |         tnode = node.find_first_by_tag("testcase") | ||||||
|  |         tnode.assert_attr(classname="test_xfailure_marker", name="test_xfail") | ||||||
|  |         fnode = tnode.find_first_by_tag("skipped") | ||||||
|  |         fnode.assert_attr(type="pytest.xfail", message="42") | ||||||
|  | 
 | ||||||
|     def test_xfail_captures_output_once(self, testdir): |     def test_xfail_captures_output_once(self, testdir): | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|  | @ -975,6 +993,20 @@ def test_record_property_same_name(testdir): | ||||||
|     pnodes[1].assert_attr(name="foo", value="baz") |     pnodes[1].assert_attr(name="foo", value="baz") | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @pytest.mark.parametrize("fixture_name", ["record_property", "record_xml_attribute"]) | ||||||
|  | def test_record_fixtures_without_junitxml(testdir, fixture_name): | ||||||
|  |     testdir.makepyfile( | ||||||
|  |         """ | ||||||
|  |         def test_record({fixture_name}): | ||||||
|  |             {fixture_name}("foo", "bar") | ||||||
|  |     """.format( | ||||||
|  |             fixture_name=fixture_name | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  |     result = testdir.runpytest() | ||||||
|  |     assert result.ret == 0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @pytest.mark.filterwarnings("default") | @pytest.mark.filterwarnings("default") | ||||||
| def test_record_attribute(testdir): | def test_record_attribute(testdir): | ||||||
|     testdir.makeini( |     testdir.makeini( | ||||||
|  | @ -1005,8 +1037,9 @@ def test_record_attribute(testdir): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.filterwarnings("default") | @pytest.mark.filterwarnings("default") | ||||||
| def test_record_attribute_xunit2(testdir): | @pytest.mark.parametrize("fixture_name", ["record_xml_attribute", "record_property"]) | ||||||
|     """Ensure record_xml_attribute drops values when outside of legacy family | def test_record_fixtures_xunit2(testdir, fixture_name): | ||||||
|  |     """Ensure record_xml_attribute and record_property drop values when outside of legacy family | ||||||
|     """ |     """ | ||||||
|     testdir.makeini( |     testdir.makeini( | ||||||
|         """ |         """ | ||||||
|  | @ -1019,21 +1052,28 @@ def test_record_attribute_xunit2(testdir): | ||||||
|         import pytest |         import pytest | ||||||
| 
 | 
 | ||||||
|         @pytest.fixture |         @pytest.fixture | ||||||
|         def other(record_xml_attribute): |         def other({fixture_name}): | ||||||
|             record_xml_attribute("bar", 1) |             {fixture_name}("bar", 1) | ||||||
|         def test_record(record_xml_attribute, other): |         def test_record({fixture_name}, other): | ||||||
|             record_xml_attribute("foo", "<1"); |             {fixture_name}("foo", "<1"); | ||||||
|     """ |     """.format( | ||||||
|  |             fixture_name=fixture_name | ||||||
|  |         ) | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     result, dom = runandparse(testdir, "-rw") |     result, dom = runandparse(testdir, "-rw") | ||||||
|     result.stdout.fnmatch_lines( |     expected_lines = [] | ||||||
|         [ |     if fixture_name == "record_xml_attribute": | ||||||
|             "*test_record_attribute_xunit2.py:6:*record_xml_attribute is an experimental feature", |         expected_lines.append( | ||||||
|             "*test_record_attribute_xunit2.py:6:*record_xml_attribute is incompatible with " |             "*test_record_fixtures_xunit2.py:6:*record_xml_attribute is an experimental feature" | ||||||
|             "junit_family: xunit2 (use: legacy|xunit1)", |         ) | ||||||
|         ] |     expected_lines = [ | ||||||
|     ) |         "*test_record_fixtures_xunit2.py:6:*{fixture_name} is incompatible " | ||||||
|  |         "with junit_family 'xunit2' (use 'legacy' or 'xunit1')".format( | ||||||
|  |             fixture_name=fixture_name | ||||||
|  |         ) | ||||||
|  |     ] | ||||||
|  |     result.stdout.fnmatch_lines(expected_lines) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_random_report_log_xdist(testdir, monkeypatch): | def test_random_report_log_xdist(testdir, monkeypatch): | ||||||
|  | @ -1203,6 +1243,53 @@ def test_url_property(testdir): | ||||||
|     ), "The URL did not get written to the xml" |     ), "The URL did not get written to the xml" | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | def test_record_testsuite_property(testdir): | ||||||
|  |     testdir.makepyfile( | ||||||
|  |         """ | ||||||
|  |         def test_func1(record_testsuite_property): | ||||||
|  |             record_testsuite_property("stats", "all good") | ||||||
|  | 
 | ||||||
|  |         def test_func2(record_testsuite_property): | ||||||
|  |             record_testsuite_property("stats", 10) | ||||||
|  |     """ | ||||||
|  |     ) | ||||||
|  |     result, dom = runandparse(testdir) | ||||||
|  |     assert result.ret == 0 | ||||||
|  |     node = dom.find_first_by_tag("testsuite") | ||||||
|  |     properties_node = node.find_first_by_tag("properties") | ||||||
|  |     p1_node = properties_node.find_nth_by_tag("property", 0) | ||||||
|  |     p2_node = properties_node.find_nth_by_tag("property", 1) | ||||||
|  |     p1_node.assert_attr(name="stats", value="all good") | ||||||
|  |     p2_node.assert_attr(name="stats", value="10") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_record_testsuite_property_junit_disabled(testdir): | ||||||
|  |     testdir.makepyfile( | ||||||
|  |         """ | ||||||
|  |         def test_func1(record_testsuite_property): | ||||||
|  |             record_testsuite_property("stats", "all good") | ||||||
|  |     """ | ||||||
|  |     ) | ||||||
|  |     result = testdir.runpytest() | ||||||
|  |     assert result.ret == 0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @pytest.mark.parametrize("junit", [True, False]) | ||||||
|  | def test_record_testsuite_property_type_checking(testdir, junit): | ||||||
|  |     testdir.makepyfile( | ||||||
|  |         """ | ||||||
|  |         def test_func1(record_testsuite_property): | ||||||
|  |             record_testsuite_property(1, 2) | ||||||
|  |     """ | ||||||
|  |     ) | ||||||
|  |     args = ("--junitxml=tests.xml",) if junit else () | ||||||
|  |     result = testdir.runpytest(*args) | ||||||
|  |     assert result.ret == 1 | ||||||
|  |     result.stdout.fnmatch_lines( | ||||||
|  |         ["*TypeError: name parameter needs to be a string, but int given"] | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| @pytest.mark.parametrize("suite_name", ["my_suite", ""]) | @pytest.mark.parametrize("suite_name", ["my_suite", ""]) | ||||||
| def test_set_suite_name(testdir, suite_name): | def test_set_suite_name(testdir, suite_name): | ||||||
|     if suite_name: |     if suite_name: | ||||||
|  |  | ||||||
|  | @ -44,11 +44,11 @@ class TestMark(object): | ||||||
|         class SomeClass(object): |         class SomeClass(object): | ||||||
|             pass |             pass | ||||||
| 
 | 
 | ||||||
|         assert pytest.mark.fun(some_function) is some_function |         assert pytest.mark.foo(some_function) is some_function | ||||||
|         assert pytest.mark.fun.with_args(some_function) is not some_function |         assert pytest.mark.foo.with_args(some_function) is not some_function | ||||||
| 
 | 
 | ||||||
|         assert pytest.mark.fun(SomeClass) is SomeClass |         assert pytest.mark.foo(SomeClass) is SomeClass | ||||||
|         assert pytest.mark.fun.with_args(SomeClass) is not SomeClass |         assert pytest.mark.foo.with_args(SomeClass) is not SomeClass | ||||||
| 
 | 
 | ||||||
|     def test_pytest_mark_name_starts_with_underscore(self): |     def test_pytest_mark_name_starts_with_underscore(self): | ||||||
|         mark = Mark() |         mark = Mark() | ||||||
|  | @ -130,7 +130,7 @@ def test_ini_markers_whitespace(testdir): | ||||||
|             assert True |             assert True | ||||||
|     """ |     """ | ||||||
|     ) |     ) | ||||||
|     rec = testdir.inline_run("--strict", "-m", "a1") |     rec = testdir.inline_run("--strict-markers", "-m", "a1") | ||||||
|     rec.assertoutcome(passed=1) |     rec.assertoutcome(passed=1) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -150,7 +150,7 @@ def test_marker_without_description(testdir): | ||||||
|     ) |     ) | ||||||
|     ftdir = testdir.mkdir("ft1_dummy") |     ftdir = testdir.mkdir("ft1_dummy") | ||||||
|     testdir.tmpdir.join("conftest.py").move(ftdir.join("conftest.py")) |     testdir.tmpdir.join("conftest.py").move(ftdir.join("conftest.py")) | ||||||
|     rec = testdir.runpytest("--strict") |     rec = testdir.runpytest("--strict-markers") | ||||||
|     rec.assert_outcomes() |     rec.assert_outcomes() | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -194,7 +194,8 @@ def test_mark_on_pseudo_function(testdir): | ||||||
|     reprec.assertoutcome(passed=1) |     reprec.assertoutcome(passed=1) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_strict_prohibits_unregistered_markers(testdir): | @pytest.mark.parametrize("option_name", ["--strict-markers", "--strict"]) | ||||||
|  | def test_strict_prohibits_unregistered_markers(testdir, option_name): | ||||||
|     testdir.makepyfile( |     testdir.makepyfile( | ||||||
|         """ |         """ | ||||||
|         import pytest |         import pytest | ||||||
|  | @ -203,9 +204,11 @@ def test_strict_prohibits_unregistered_markers(testdir): | ||||||
|             pass |             pass | ||||||
|     """ |     """ | ||||||
|     ) |     ) | ||||||
|     result = testdir.runpytest("--strict") |     result = testdir.runpytest(option_name) | ||||||
|     assert result.ret != 0 |     assert result.ret != 0 | ||||||
|     result.stdout.fnmatch_lines(["'unregisteredmark' not a registered marker"]) |     result.stdout.fnmatch_lines( | ||||||
|  |         ["'unregisteredmark' not found in `markers` configuration option"] | ||||||
|  |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
|  | @ -449,8 +452,8 @@ class TestFunctional(object): | ||||||
|         items, rec = testdir.inline_genitems(p) |         items, rec = testdir.inline_genitems(p) | ||||||
|         self.assert_markers(items, test_foo=("a", "b"), test_bar=("a",)) |         self.assert_markers(items, test_foo=("a", "b"), test_bar=("a",)) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue568 |  | ||||||
|     def test_mark_should_not_pass_to_siebling_class(self, testdir): |     def test_mark_should_not_pass_to_siebling_class(self, testdir): | ||||||
|  |         """#568""" | ||||||
|         p = testdir.makepyfile( |         p = testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|             import pytest |             import pytest | ||||||
|  | @ -652,9 +655,9 @@ class TestFunctional(object): | ||||||
|             markers = {m.name for m in items[name].iter_markers()} |             markers = {m.name for m in items[name].iter_markers()} | ||||||
|             assert markers == set(expected_markers) |             assert markers == set(expected_markers) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue1540 |  | ||||||
|     @pytest.mark.filterwarnings("ignore") |     @pytest.mark.filterwarnings("ignore") | ||||||
|     def test_mark_from_parameters(self, testdir): |     def test_mark_from_parameters(self, testdir): | ||||||
|  |         """#1540""" | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             """ |             """ | ||||||
|             import pytest |             import pytest | ||||||
|  | @ -933,16 +936,16 @@ def test_mark_expressions_no_smear(testdir): | ||||||
| 
 | 
 | ||||||
| def test_addmarker_order(): | def test_addmarker_order(): | ||||||
|     node = Node("Test", config=mock.Mock(), session=mock.Mock(), nodeid="Test") |     node = Node("Test", config=mock.Mock(), session=mock.Mock(), nodeid="Test") | ||||||
|     node.add_marker("a") |     node.add_marker("foo") | ||||||
|     node.add_marker("b") |     node.add_marker("bar") | ||||||
|     node.add_marker("c", append=False) |     node.add_marker("baz", append=False) | ||||||
|     extracted = [x.name for x in node.iter_markers()] |     extracted = [x.name for x in node.iter_markers()] | ||||||
|     assert extracted == ["c", "a", "b"] |     assert extracted == ["baz", "foo", "bar"] | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.issue("https://github.com/pytest-dev/pytest/issues/3605") |  | ||||||
| @pytest.mark.filterwarnings("ignore") | @pytest.mark.filterwarnings("ignore") | ||||||
| def test_markers_from_parametrize(testdir): | def test_markers_from_parametrize(testdir): | ||||||
|  |     """#3605""" | ||||||
|     testdir.makepyfile( |     testdir.makepyfile( | ||||||
|         """ |         """ | ||||||
|         from __future__ import print_function |         from __future__ import print_function | ||||||
|  |  | ||||||
|  | @ -6,6 +6,8 @@ import py | ||||||
| import _pytest | import _pytest | ||||||
| import pytest | import pytest | ||||||
| 
 | 
 | ||||||
|  | pytestmark = pytest.mark.slow | ||||||
|  | 
 | ||||||
| MODSET = [ | MODSET = [ | ||||||
|     x |     x | ||||||
|     for x in py.path.local(_pytest.__file__).dirpath().visit("*.py") |     for x in py.path.local(_pytest.__file__).dirpath().visit("*.py") | ||||||
|  |  | ||||||
|  | @ -6,6 +6,8 @@ import os | ||||||
| import platform | import platform | ||||||
| import sys | import sys | ||||||
| 
 | 
 | ||||||
|  | import six | ||||||
|  | 
 | ||||||
| import _pytest._code | import _pytest._code | ||||||
| import pytest | import pytest | ||||||
| from _pytest.debugging import _validate_usepdb_cls | from _pytest.debugging import _validate_usepdb_cls | ||||||
|  | @ -395,7 +397,7 @@ class TestPDB(object): | ||||||
|         child = testdir.spawn_pytest(str(p1)) |         child = testdir.spawn_pytest(str(p1)) | ||||||
|         child.expect("test_1") |         child.expect("test_1") | ||||||
|         child.expect("Pdb") |         child.expect("Pdb") | ||||||
|         child.sendeof() |         child.sendline("q") | ||||||
|         rest = child.read().decode("utf8") |         rest = child.read().decode("utf8") | ||||||
|         assert "no tests ran" in rest |         assert "no tests ran" in rest | ||||||
|         assert "reading from stdin while output" not in rest |         assert "reading from stdin while output" not in rest | ||||||
|  | @ -957,7 +959,7 @@ class TestDebuggingBreakpoints(object): | ||||||
|         child = testdir.spawn_pytest(str(p1)) |         child = testdir.spawn_pytest(str(p1)) | ||||||
|         child.expect("test_1") |         child.expect("test_1") | ||||||
|         child.expect("Pdb") |         child.expect("Pdb") | ||||||
|         child.sendeof() |         child.sendline("quit") | ||||||
|         rest = child.read().decode("utf8") |         rest = child.read().decode("utf8") | ||||||
|         assert "Quitting debugger" in rest |         assert "Quitting debugger" in rest | ||||||
|         assert "reading from stdin while output" not in rest |         assert "reading from stdin while output" not in rest | ||||||
|  | @ -1013,7 +1015,8 @@ class TestTraceOption: | ||||||
|         rest = child.read().decode("utf8") |         rest = child.read().decode("utf8") | ||||||
|         assert "2 passed in" in rest |         assert "2 passed in" in rest | ||||||
|         assert "reading from stdin while output" not in rest |         assert "reading from stdin while output" not in rest | ||||||
|         assert "Exit: Quitting debugger" in child.before.decode("utf8") |         # Only printed once - not on stderr. | ||||||
|  |         assert "Exit: Quitting debugger" not in child.before.decode("utf8") | ||||||
|         TestPDB.flush(child) |         TestPDB.flush(child) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -1133,7 +1136,11 @@ def test_pdbcls_via_local_module(testdir): | ||||||
|         class Wrapped: |         class Wrapped: | ||||||
|             class MyPdb: |             class MyPdb: | ||||||
|                 def set_trace(self, *args): |                 def set_trace(self, *args): | ||||||
|                     print("mypdb_called", args) |                     print("settrace_called", args) | ||||||
|  | 
 | ||||||
|  |                 def runcall(self, *args, **kwds): | ||||||
|  |                     print("runcall_called", args, kwds) | ||||||
|  |                     assert "func" in kwds | ||||||
|         """, |         """, | ||||||
|     ) |     ) | ||||||
|     result = testdir.runpytest( |     result = testdir.runpytest( | ||||||
|  | @ -1150,4 +1157,37 @@ def test_pdbcls_via_local_module(testdir): | ||||||
|         str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", syspathinsert=True |         str(p1), "--pdbcls=mypdb:Wrapped.MyPdb", syspathinsert=True | ||||||
|     ) |     ) | ||||||
|     assert result.ret == 0 |     assert result.ret == 0 | ||||||
|     result.stdout.fnmatch_lines(["*mypdb_called*", "* 1 passed in *"]) |     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 *"]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_raises_bdbquit_with_eoferror(testdir): | ||||||
|  |     """It is not guaranteed that DontReadFromInput's read is called.""" | ||||||
|  |     if six.PY2: | ||||||
|  |         builtin_module = "__builtin__" | ||||||
|  |         input_func = "raw_input" | ||||||
|  |     else: | ||||||
|  |         builtin_module = "builtins" | ||||||
|  |         input_func = "input" | ||||||
|  |     p1 = testdir.makepyfile( | ||||||
|  |         """ | ||||||
|  |         def input_without_read(*args, **kwargs): | ||||||
|  |             raise EOFError() | ||||||
|  | 
 | ||||||
|  |         def test(monkeypatch): | ||||||
|  |             import {builtin_module} | ||||||
|  |             monkeypatch.setattr({builtin_module}, {input_func!r}, input_without_read) | ||||||
|  |             __import__('pdb').set_trace() | ||||||
|  |         """.format( | ||||||
|  |             builtin_module=builtin_module, input_func=input_func | ||||||
|  |         ) | ||||||
|  |     ) | ||||||
|  |     result = testdir.runpytest(str(p1)) | ||||||
|  |     result.stdout.fnmatch_lines(["E *BdbQuit", "*= 1 failed in*"]) | ||||||
|  |     assert result.ret == 1 | ||||||
|  |  | ||||||
|  | @ -9,6 +9,7 @@ import types | ||||||
| 
 | 
 | ||||||
| import pytest | import pytest | ||||||
| from _pytest.config import PytestPluginManager | from _pytest.config import PytestPluginManager | ||||||
|  | from _pytest.config.exceptions import UsageError | ||||||
| from _pytest.main import EXIT_NOTESTSCOLLECTED | from _pytest.main import EXIT_NOTESTSCOLLECTED | ||||||
| from _pytest.main import Session | from _pytest.main import Session | ||||||
| 
 | 
 | ||||||
|  | @ -314,6 +315,9 @@ class TestPytestPluginManagerBootstrapming(object): | ||||||
|         # Handles -p without following arg (when used without argparse). |         # Handles -p without following arg (when used without argparse). | ||||||
|         pytestpm.consider_preparse(["-p"]) |         pytestpm.consider_preparse(["-p"]) | ||||||
| 
 | 
 | ||||||
|  |         with pytest.raises(UsageError, match="^plugin main cannot be disabled$"): | ||||||
|  |             pytestpm.consider_preparse(["-p", "no:main"]) | ||||||
|  | 
 | ||||||
|     def test_plugin_prevent_register(self, pytestpm): |     def test_plugin_prevent_register(self, pytestpm): | ||||||
|         pytestpm.consider_preparse(["xyz", "-p", "no:abc"]) |         pytestpm.consider_preparse(["xyz", "-p", "no:abc"]) | ||||||
|         l1 = pytestpm.get_plugins() |         l1 = pytestpm.get_plugins() | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ from __future__ import division | ||||||
| from __future__ import print_function | from __future__ import print_function | ||||||
| 
 | 
 | ||||||
| import os | import os | ||||||
|  | import subprocess | ||||||
| import sys | import sys | ||||||
| import time | import time | ||||||
| 
 | 
 | ||||||
|  | @ -482,3 +483,79 @@ def test_pytester_addopts(request, monkeypatch): | ||||||
|         testdir.finalize() |         testdir.finalize() | ||||||
| 
 | 
 | ||||||
|     assert os.environ["PYTEST_ADDOPTS"] == "--orig-unused" |     assert os.environ["PYTEST_ADDOPTS"] == "--orig-unused" | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_run_stdin(testdir): | ||||||
|  |     with pytest.raises(testdir.TimeoutExpired): | ||||||
|  |         testdir.run( | ||||||
|  |             sys.executable, | ||||||
|  |             "-c", | ||||||
|  |             "import sys, time; time.sleep(1); print(sys.stdin.read())", | ||||||
|  |             stdin=subprocess.PIPE, | ||||||
|  |             timeout=0.1, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     with pytest.raises(testdir.TimeoutExpired): | ||||||
|  |         result = testdir.run( | ||||||
|  |             sys.executable, | ||||||
|  |             "-c", | ||||||
|  |             "import sys, time; time.sleep(1); print(sys.stdin.read())", | ||||||
|  |             stdin=b"input\n2ndline", | ||||||
|  |             timeout=0.1, | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     result = testdir.run( | ||||||
|  |         sys.executable, | ||||||
|  |         "-c", | ||||||
|  |         "import sys; print(sys.stdin.read())", | ||||||
|  |         stdin=b"input\n2ndline", | ||||||
|  |     ) | ||||||
|  |     assert result.stdout.lines == ["input", "2ndline"] | ||||||
|  |     assert result.stderr.str() == "" | ||||||
|  |     assert result.ret == 0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_popen_stdin_pipe(testdir): | ||||||
|  |     proc = testdir.popen( | ||||||
|  |         [sys.executable, "-c", "import sys; print(sys.stdin.read())"], | ||||||
|  |         stdout=subprocess.PIPE, | ||||||
|  |         stderr=subprocess.PIPE, | ||||||
|  |         stdin=subprocess.PIPE, | ||||||
|  |     ) | ||||||
|  |     stdin = b"input\n2ndline" | ||||||
|  |     stdout, stderr = proc.communicate(input=stdin) | ||||||
|  |     assert stdout.decode("utf8").splitlines() == ["input", "2ndline"] | ||||||
|  |     assert stderr == b"" | ||||||
|  |     assert proc.returncode == 0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_popen_stdin_bytes(testdir): | ||||||
|  |     proc = testdir.popen( | ||||||
|  |         [sys.executable, "-c", "import sys; print(sys.stdin.read())"], | ||||||
|  |         stdout=subprocess.PIPE, | ||||||
|  |         stderr=subprocess.PIPE, | ||||||
|  |         stdin=b"input\n2ndline", | ||||||
|  |     ) | ||||||
|  |     stdout, stderr = proc.communicate() | ||||||
|  |     assert stdout.decode("utf8").splitlines() == ["input", "2ndline"] | ||||||
|  |     assert stderr == b"" | ||||||
|  |     assert proc.returncode == 0 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_popen_default_stdin_stderr_and_stdin_None(testdir): | ||||||
|  |     # stdout, stderr default to pipes, | ||||||
|  |     # stdin can be None to not close the pipe, avoiding | ||||||
|  |     # "ValueError: flush of closed file" with `communicate()`. | ||||||
|  |     p1 = testdir.makepyfile( | ||||||
|  |         """ | ||||||
|  |         import sys | ||||||
|  |         print(sys.stdin.read())  # empty | ||||||
|  |         print('stdout') | ||||||
|  |         sys.stderr.write('stderr') | ||||||
|  |         """ | ||||||
|  |     ) | ||||||
|  |     proc = testdir.popen([sys.executable, str(p1)], stdin=None) | ||||||
|  |     stdout, stderr = proc.communicate(b"ignored") | ||||||
|  |     assert stdout.splitlines() == [b"", b"stdout"] | ||||||
|  |     assert stderr.splitlines() == [b"stderr"] | ||||||
|  |     assert proc.returncode == 0 | ||||||
|  |  | ||||||
|  | @ -47,8 +47,8 @@ class TestWarningsRecorderChecker(object): | ||||||
|             assert values is rec.list |             assert values is rec.list | ||||||
|             pytest.raises(AssertionError, rec.pop) |             pytest.raises(AssertionError, rec.pop) | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue(4243) |  | ||||||
|     def test_warn_stacklevel(self): |     def test_warn_stacklevel(self): | ||||||
|  |         """#4243""" | ||||||
|         rec = WarningsRecorder() |         rec = WarningsRecorder() | ||||||
|         with rec: |         with rec: | ||||||
|             warnings.warn("test", DeprecationWarning, 2) |             warnings.warn("test", DeprecationWarning, 2) | ||||||
|  |  | ||||||
|  | @ -580,8 +580,31 @@ def test_pytest_exit_returncode(testdir): | ||||||
|     """ |     """ | ||||||
|     ) |     ) | ||||||
|     result = testdir.runpytest() |     result = testdir.runpytest() | ||||||
|  |     result.stdout.fnmatch_lines(["*! *Exit: some exit msg !*"]) | ||||||
|  |     # Assert no output on stderr, except for unreliable ResourceWarnings. | ||||||
|  |     # (https://github.com/pytest-dev/pytest/issues/5088) | ||||||
|  |     assert [ | ||||||
|  |         x | ||||||
|  |         for x in result.stderr.lines | ||||||
|  |         if not x.startswith("Exception ignored in:") | ||||||
|  |         and not x.startswith("ResourceWarning") | ||||||
|  |     ] == [""] | ||||||
|     assert result.ret == 99 |     assert result.ret == 99 | ||||||
| 
 | 
 | ||||||
|  |     # It prints to stderr also in case of exit during pytest_sessionstart. | ||||||
|  |     testdir.makeconftest( | ||||||
|  |         """ | ||||||
|  |         import pytest | ||||||
|  | 
 | ||||||
|  |         def pytest_sessionstart(): | ||||||
|  |             pytest.exit("during_sessionstart", 98) | ||||||
|  |         """ | ||||||
|  |     ) | ||||||
|  |     result = testdir.runpytest() | ||||||
|  |     result.stdout.fnmatch_lines(["*! *Exit: during_sessionstart !*"]) | ||||||
|  |     assert result.stderr.lines == ["Exit: during_sessionstart", ""] | ||||||
|  |     assert result.ret == 98 | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def test_pytest_fail_notrace_runtest(testdir): | def test_pytest_fail_notrace_runtest(testdir): | ||||||
|     """Test pytest.fail(..., pytrace=False) does not show tracebacks during test run.""" |     """Test pytest.fail(..., pytrace=False) does not show tracebacks during test run.""" | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | # coding=utf8 | ||||||
| from __future__ import absolute_import | from __future__ import absolute_import | ||||||
| from __future__ import division | from __future__ import division | ||||||
| from __future__ import print_function | from __future__ import print_function | ||||||
|  | @ -6,7 +7,6 @@ import sys | ||||||
| 
 | 
 | ||||||
| import pytest | import pytest | ||||||
| from _pytest.runner import runtestprotocol | from _pytest.runner import runtestprotocol | ||||||
| from _pytest.skipping import folded_skips |  | ||||||
| from _pytest.skipping import MarkEvaluator | from _pytest.skipping import MarkEvaluator | ||||||
| from _pytest.skipping import pytest_runtest_setup | from _pytest.skipping import pytest_runtest_setup | ||||||
| 
 | 
 | ||||||
|  | @ -749,40 +749,6 @@ def test_skipif_class(testdir): | ||||||
|     result.stdout.fnmatch_lines(["*2 skipped*"]) |     result.stdout.fnmatch_lines(["*2 skipped*"]) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_skip_reasons_folding(): |  | ||||||
|     path = "xyz" |  | ||||||
|     lineno = 3 |  | ||||||
|     message = "justso" |  | ||||||
|     longrepr = (path, lineno, message) |  | ||||||
| 
 |  | ||||||
|     class X(object): |  | ||||||
|         pass |  | ||||||
| 
 |  | ||||||
|     ev1 = X() |  | ||||||
|     ev1.when = "execute" |  | ||||||
|     ev1.skipped = True |  | ||||||
|     ev1.longrepr = longrepr |  | ||||||
| 
 |  | ||||||
|     ev2 = X() |  | ||||||
|     ev2.when = "execute" |  | ||||||
|     ev2.longrepr = longrepr |  | ||||||
|     ev2.skipped = True |  | ||||||
| 
 |  | ||||||
|     # ev3 might be a collection report |  | ||||||
|     ev3 = X() |  | ||||||
|     ev3.when = "collect" |  | ||||||
|     ev3.longrepr = longrepr |  | ||||||
|     ev3.skipped = True |  | ||||||
| 
 |  | ||||||
|     values = folded_skips([ev1, ev2, ev3]) |  | ||||||
|     assert len(values) == 1 |  | ||||||
|     num, fspath, lineno, reason = values[0] |  | ||||||
|     assert num == 3 |  | ||||||
|     assert fspath == path |  | ||||||
|     assert lineno == lineno |  | ||||||
|     assert reason == message |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| def test_skipped_reasons_functional(testdir): | def test_skipped_reasons_functional(testdir): | ||||||
|     testdir.makepyfile( |     testdir.makepyfile( | ||||||
|         test_one=""" |         test_one=""" | ||||||
|  | @ -1208,6 +1174,6 @@ def test_summary_list_after_errors(testdir): | ||||||
|         [ |         [ | ||||||
|             "=* FAILURES *=", |             "=* FAILURES *=", | ||||||
|             "*= short test summary info =*", |             "*= short test summary info =*", | ||||||
|             "FAILED test_summary_list_after_errors.py::test_fail", |             "FAILED test_summary_list_after_errors.py::test_fail - assert 0", | ||||||
|         ] |         ] | ||||||
|     ) |     ) | ||||||
|  |  | ||||||
|  | @ -76,7 +76,7 @@ def broken_testdir(testdir): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_run_without_stepwise(stepwise_testdir): | def test_run_without_stepwise(stepwise_testdir): | ||||||
|     result = stepwise_testdir.runpytest("-v", "--strict", "--fail") |     result = stepwise_testdir.runpytest("-v", "--strict-markers", "--fail") | ||||||
| 
 | 
 | ||||||
|     result.stdout.fnmatch_lines(["*test_success_before_fail PASSED*"]) |     result.stdout.fnmatch_lines(["*test_success_before_fail PASSED*"]) | ||||||
|     result.stdout.fnmatch_lines(["*test_fail_on_flag FAILED*"]) |     result.stdout.fnmatch_lines(["*test_fail_on_flag FAILED*"]) | ||||||
|  | @ -85,7 +85,9 @@ def test_run_without_stepwise(stepwise_testdir): | ||||||
| 
 | 
 | ||||||
| def test_fail_and_continue_with_stepwise(stepwise_testdir): | def test_fail_and_continue_with_stepwise(stepwise_testdir): | ||||||
|     # Run the tests with a failing second test. |     # Run the tests with a failing second test. | ||||||
|     result = stepwise_testdir.runpytest("-v", "--strict", "--stepwise", "--fail") |     result = stepwise_testdir.runpytest( | ||||||
|  |         "-v", "--strict-markers", "--stepwise", "--fail" | ||||||
|  |     ) | ||||||
|     assert not result.stderr.str() |     assert not result.stderr.str() | ||||||
| 
 | 
 | ||||||
|     stdout = result.stdout.str() |     stdout = result.stdout.str() | ||||||
|  | @ -95,7 +97,7 @@ def test_fail_and_continue_with_stepwise(stepwise_testdir): | ||||||
|     assert "test_success_after_fail" not in stdout |     assert "test_success_after_fail" not in stdout | ||||||
| 
 | 
 | ||||||
|     # "Fix" the test that failed in the last run and run it again. |     # "Fix" the test that failed in the last run and run it again. | ||||||
|     result = stepwise_testdir.runpytest("-v", "--strict", "--stepwise") |     result = stepwise_testdir.runpytest("-v", "--strict-markers", "--stepwise") | ||||||
|     assert not result.stderr.str() |     assert not result.stderr.str() | ||||||
| 
 | 
 | ||||||
|     stdout = result.stdout.str() |     stdout = result.stdout.str() | ||||||
|  | @ -107,7 +109,12 @@ def test_fail_and_continue_with_stepwise(stepwise_testdir): | ||||||
| 
 | 
 | ||||||
| def test_run_with_skip_option(stepwise_testdir): | def test_run_with_skip_option(stepwise_testdir): | ||||||
|     result = stepwise_testdir.runpytest( |     result = stepwise_testdir.runpytest( | ||||||
|         "-v", "--strict", "--stepwise", "--stepwise-skip", "--fail", "--fail-last" |         "-v", | ||||||
|  |         "--strict-markers", | ||||||
|  |         "--stepwise", | ||||||
|  |         "--stepwise-skip", | ||||||
|  |         "--fail", | ||||||
|  |         "--fail-last", | ||||||
|     ) |     ) | ||||||
|     assert not result.stderr.str() |     assert not result.stderr.str() | ||||||
| 
 | 
 | ||||||
|  | @ -120,7 +127,7 @@ def test_run_with_skip_option(stepwise_testdir): | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_fail_on_errors(error_testdir): | def test_fail_on_errors(error_testdir): | ||||||
|     result = error_testdir.runpytest("-v", "--strict", "--stepwise") |     result = error_testdir.runpytest("-v", "--strict-markers", "--stepwise") | ||||||
| 
 | 
 | ||||||
|     assert not result.stderr.str() |     assert not result.stderr.str() | ||||||
|     stdout = result.stdout.str() |     stdout = result.stdout.str() | ||||||
|  | @ -131,7 +138,7 @@ def test_fail_on_errors(error_testdir): | ||||||
| 
 | 
 | ||||||
| def test_change_testfile(stepwise_testdir): | def test_change_testfile(stepwise_testdir): | ||||||
|     result = stepwise_testdir.runpytest( |     result = stepwise_testdir.runpytest( | ||||||
|         "-v", "--strict", "--stepwise", "--fail", "test_a.py" |         "-v", "--strict-markers", "--stepwise", "--fail", "test_a.py" | ||||||
|     ) |     ) | ||||||
|     assert not result.stderr.str() |     assert not result.stderr.str() | ||||||
| 
 | 
 | ||||||
|  | @ -140,7 +147,9 @@ def test_change_testfile(stepwise_testdir): | ||||||
| 
 | 
 | ||||||
|     # Make sure the second test run starts from the beginning, since the |     # Make sure the second test run starts from the beginning, since the | ||||||
|     # test to continue from does not exist in testfile_b. |     # test to continue from does not exist in testfile_b. | ||||||
|     result = stepwise_testdir.runpytest("-v", "--strict", "--stepwise", "test_b.py") |     result = stepwise_testdir.runpytest( | ||||||
|  |         "-v", "--strict-markers", "--stepwise", "test_b.py" | ||||||
|  |     ) | ||||||
|     assert not result.stderr.str() |     assert not result.stderr.str() | ||||||
| 
 | 
 | ||||||
|     stdout = result.stdout.str() |     stdout = result.stdout.str() | ||||||
|  | @ -149,7 +158,11 @@ def test_change_testfile(stepwise_testdir): | ||||||
| 
 | 
 | ||||||
| def test_stop_on_collection_errors(broken_testdir): | def test_stop_on_collection_errors(broken_testdir): | ||||||
|     result = broken_testdir.runpytest( |     result = broken_testdir.runpytest( | ||||||
|         "-v", "--strict", "--stepwise", "working_testfile.py", "broken_testfile.py" |         "-v", | ||||||
|  |         "--strict-markers", | ||||||
|  |         "--stepwise", | ||||||
|  |         "working_testfile.py", | ||||||
|  |         "broken_testfile.py", | ||||||
|     ) |     ) | ||||||
| 
 | 
 | ||||||
|     stdout = result.stdout.str() |     stdout = result.stdout.str() | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | # encoding: utf-8 | ||||||
| """ | """ | ||||||
| terminal reporting of the full testing process. | terminal reporting of the full testing process. | ||||||
| """ | """ | ||||||
|  | @ -16,6 +17,8 @@ import py | ||||||
| import pytest | import pytest | ||||||
| from _pytest.main import EXIT_NOTESTSCOLLECTED | from _pytest.main import EXIT_NOTESTSCOLLECTED | ||||||
| from _pytest.reports import BaseReport | from _pytest.reports import BaseReport | ||||||
|  | from _pytest.terminal import _folded_skips | ||||||
|  | from _pytest.terminal import _get_line_with_reprcrash_message | ||||||
| from _pytest.terminal import _plugin_nameversions | from _pytest.terminal import _plugin_nameversions | ||||||
| from _pytest.terminal import build_summary_stats_line | from _pytest.terminal import build_summary_stats_line | ||||||
| from _pytest.terminal import getreportopt | from _pytest.terminal import getreportopt | ||||||
|  | @ -505,6 +508,37 @@ class TestTerminalFunctional(object): | ||||||
|         ) |         ) | ||||||
|         assert result.ret == 0 |         assert result.ret == 0 | ||||||
| 
 | 
 | ||||||
|  |     def test_deselected_with_hookwrapper(self, testdir): | ||||||
|  |         testpath = testdir.makeconftest( | ||||||
|  |             """ | ||||||
|  |             import pytest | ||||||
|  | 
 | ||||||
|  |             @pytest.hookimpl(hookwrapper=True) | ||||||
|  |             def pytest_collection_modifyitems(config, items): | ||||||
|  |                 yield | ||||||
|  |                 deselected = items.pop() | ||||||
|  |                 config.hook.pytest_deselected(items=[deselected]) | ||||||
|  |             """ | ||||||
|  |         ) | ||||||
|  |         testpath = testdir.makepyfile( | ||||||
|  |             """ | ||||||
|  |                 def test_one(): | ||||||
|  |                     pass | ||||||
|  |                 def test_two(): | ||||||
|  |                     pass | ||||||
|  |                 def test_three(): | ||||||
|  |                     pass | ||||||
|  |            """ | ||||||
|  |         ) | ||||||
|  |         result = testdir.runpytest(testpath) | ||||||
|  |         result.stdout.fnmatch_lines( | ||||||
|  |             [ | ||||||
|  |                 "collected 3 items / 1 deselected / 2 selected", | ||||||
|  |                 "*= 2 passed, 1 deselected in*", | ||||||
|  |             ] | ||||||
|  |         ) | ||||||
|  |         assert result.ret == 0 | ||||||
|  | 
 | ||||||
|     def test_show_deselected_items_using_markexpr_before_test_execution(self, testdir): |     def test_show_deselected_items_using_markexpr_before_test_execution(self, testdir): | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|             test_show_deselected=""" |             test_show_deselected=""" | ||||||
|  | @ -726,12 +760,18 @@ class TestTerminalFunctional(object): | ||||||
|         result.stdout.fnmatch_lines(["collected 3 items", "hello from hook: 3 items"]) |         result.stdout.fnmatch_lines(["collected 3 items", "hello from hook: 3 items"]) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_fail_extra_reporting(testdir): | def test_fail_extra_reporting(testdir, monkeypatch): | ||||||
|     testdir.makepyfile("def test_this(): assert 0") |     monkeypatch.setenv("COLUMNS", "80") | ||||||
|  |     testdir.makepyfile("def test_this(): assert 0, 'this_failed' * 100") | ||||||
|     result = testdir.runpytest() |     result = testdir.runpytest() | ||||||
|     assert "short test summary" not in result.stdout.str() |     assert "short test summary" not in result.stdout.str() | ||||||
|     result = testdir.runpytest("-rf") |     result = testdir.runpytest("-rf") | ||||||
|     result.stdout.fnmatch_lines(["*test summary*", "FAIL*test_fail_extra_reporting*"]) |     result.stdout.fnmatch_lines( | ||||||
|  |         [ | ||||||
|  |             "*test summary*", | ||||||
|  |             "FAILED test_fail_extra_reporting.py::test_this - AssertionError: this_failedt...", | ||||||
|  |         ] | ||||||
|  |     ) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_fail_reporting_on_pass(testdir): | def test_fail_reporting_on_pass(testdir): | ||||||
|  | @ -768,11 +808,19 @@ def test_pass_output_reporting(testdir): | ||||||
|     assert "test_pass_has_output" not in s |     assert "test_pass_has_output" not in s | ||||||
|     assert "Four score and seven years ago..." not in s |     assert "Four score and seven years ago..." not in s | ||||||
|     assert "test_pass_no_output" not in s |     assert "test_pass_no_output" not in s | ||||||
|     result = testdir.runpytest("-rP") |     result = testdir.runpytest("-rPp") | ||||||
|     result.stdout.fnmatch_lines( |     result.stdout.fnmatch_lines( | ||||||
|         ["*test_pass_has_output*", "Four score and seven years ago..."] |         [ | ||||||
|  |             "*= PASSES =*", | ||||||
|  |             "*_ test_pass_has_output _*", | ||||||
|  |             "*- Captured stdout call -*", | ||||||
|  |             "Four score and seven years ago...", | ||||||
|  |             "*= short test summary info =*", | ||||||
|  |             "PASSED test_pass_output_reporting.py::test_pass_has_output", | ||||||
|  |             "PASSED test_pass_output_reporting.py::test_pass_no_output", | ||||||
|  |             "*= 2 passed in *", | ||||||
|  |         ] | ||||||
|     ) |     ) | ||||||
|     assert "test_pass_no_output" not in result.stdout.str() |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def test_color_yes(testdir): | def test_color_yes(testdir): | ||||||
|  | @ -830,14 +878,23 @@ def test_getreportopt(): | ||||||
|     config.option.reportchars = "sfxw" |     config.option.reportchars = "sfxw" | ||||||
|     assert getreportopt(config) == "sfx" |     assert getreportopt(config) == "sfx" | ||||||
| 
 | 
 | ||||||
|     config.option.reportchars = "sfx" |     # Now with --disable-warnings. | ||||||
|     config.option.disable_warnings = False |     config.option.disable_warnings = False | ||||||
|  |     config.option.reportchars = "a" | ||||||
|  |     assert getreportopt(config) == "sxXwEf"  # NOTE: "w" included! | ||||||
|  | 
 | ||||||
|  |     config.option.reportchars = "sfx" | ||||||
|     assert getreportopt(config) == "sfxw" |     assert getreportopt(config) == "sfxw" | ||||||
| 
 | 
 | ||||||
|     config.option.reportchars = "sfxw" |     config.option.reportchars = "sfxw" | ||||||
|     config.option.disable_warnings = False |  | ||||||
|     assert getreportopt(config) == "sfxw" |     assert getreportopt(config) == "sfxw" | ||||||
| 
 | 
 | ||||||
|  |     config.option.reportchars = "a" | ||||||
|  |     assert getreportopt(config) == "sxXwEf"  # NOTE: "w" included! | ||||||
|  | 
 | ||||||
|  |     config.option.reportchars = "A" | ||||||
|  |     assert getreportopt(config) == "sxXwEfpP" | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| def test_terminalreporter_reportopt_addopts(testdir): | def test_terminalreporter_reportopt_addopts(testdir): | ||||||
|     testdir.makeini("[pytest]\naddopts=-rs") |     testdir.makeini("[pytest]\naddopts=-rs") | ||||||
|  | @ -1524,3 +1581,106 @@ class TestProgressWithTeardown(object): | ||||||
|         monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False) |         monkeypatch.delenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", raising=False) | ||||||
|         output = testdir.runpytest("-n2") |         output = testdir.runpytest("-n2") | ||||||
|         output.stdout.re_match_lines([r"[\.E]{40} \s+ \[100%\]"]) |         output.stdout.re_match_lines([r"[\.E]{40} \s+ \[100%\]"]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_skip_reasons_folding(): | ||||||
|  |     path = "xyz" | ||||||
|  |     lineno = 3 | ||||||
|  |     message = "justso" | ||||||
|  |     longrepr = (path, lineno, message) | ||||||
|  | 
 | ||||||
|  |     class X(object): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  |     ev1 = X() | ||||||
|  |     ev1.when = "execute" | ||||||
|  |     ev1.skipped = True | ||||||
|  |     ev1.longrepr = longrepr | ||||||
|  | 
 | ||||||
|  |     ev2 = X() | ||||||
|  |     ev2.when = "execute" | ||||||
|  |     ev2.longrepr = longrepr | ||||||
|  |     ev2.skipped = True | ||||||
|  | 
 | ||||||
|  |     # ev3 might be a collection report | ||||||
|  |     ev3 = X() | ||||||
|  |     ev3.when = "collect" | ||||||
|  |     ev3.longrepr = longrepr | ||||||
|  |     ev3.skipped = True | ||||||
|  | 
 | ||||||
|  |     values = _folded_skips([ev1, ev2, ev3]) | ||||||
|  |     assert len(values) == 1 | ||||||
|  |     num, fspath, lineno, reason = values[0] | ||||||
|  |     assert num == 3 | ||||||
|  |     assert fspath == path | ||||||
|  |     assert lineno == lineno | ||||||
|  |     assert reason == message | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def test_line_with_reprcrash(monkeypatch): | ||||||
|  |     import _pytest.terminal | ||||||
|  |     from wcwidth import wcswidth | ||||||
|  | 
 | ||||||
|  |     mocked_verbose_word = "FAILED" | ||||||
|  | 
 | ||||||
|  |     mocked_pos = "some::nodeid" | ||||||
|  | 
 | ||||||
|  |     def mock_get_pos(*args): | ||||||
|  |         return mocked_pos | ||||||
|  | 
 | ||||||
|  |     monkeypatch.setattr(_pytest.terminal, "_get_pos", mock_get_pos) | ||||||
|  | 
 | ||||||
|  |     class config(object): | ||||||
|  |         pass | ||||||
|  | 
 | ||||||
|  |     class rep(object): | ||||||
|  |         def _get_verbose_word(self, *args): | ||||||
|  |             return mocked_verbose_word | ||||||
|  | 
 | ||||||
|  |         class longrepr: | ||||||
|  |             class reprcrash: | ||||||
|  |                 pass | ||||||
|  | 
 | ||||||
|  |     def check(msg, width, expected): | ||||||
|  |         __tracebackhide__ = True | ||||||
|  |         if msg: | ||||||
|  |             rep.longrepr.reprcrash.message = msg | ||||||
|  |         actual = _get_line_with_reprcrash_message(config, rep(), width) | ||||||
|  | 
 | ||||||
|  |         assert actual == expected | ||||||
|  |         if actual != "%s %s" % (mocked_verbose_word, mocked_pos): | ||||||
|  |             assert len(actual) <= width | ||||||
|  |             assert wcswidth(actual) <= width | ||||||
|  | 
 | ||||||
|  |     # AttributeError with message | ||||||
|  |     check(None, 80, "FAILED some::nodeid") | ||||||
|  | 
 | ||||||
|  |     check("msg", 80, "FAILED some::nodeid - msg") | ||||||
|  |     check("msg", 3, "FAILED some::nodeid") | ||||||
|  | 
 | ||||||
|  |     check("msg", 24, "FAILED some::nodeid") | ||||||
|  |     check("msg", 25, "FAILED some::nodeid - msg") | ||||||
|  | 
 | ||||||
|  |     check("some longer msg", 24, "FAILED some::nodeid") | ||||||
|  |     check("some longer msg", 25, "FAILED some::nodeid - ...") | ||||||
|  |     check("some longer msg", 26, "FAILED some::nodeid - s...") | ||||||
|  | 
 | ||||||
|  |     check("some\nmessage", 25, "FAILED some::nodeid - ...") | ||||||
|  |     check("some\nmessage", 26, "FAILED some::nodeid - some") | ||||||
|  |     check("some\nmessage", 80, "FAILED some::nodeid - some") | ||||||
|  | 
 | ||||||
|  |     # Test unicode safety. | ||||||
|  |     check(u"😄😄😄😄😄\n2nd line", 25, u"FAILED some::nodeid - ...") | ||||||
|  |     check(u"😄😄😄😄😄\n2nd line", 26, u"FAILED some::nodeid - ...") | ||||||
|  |     check(u"😄😄😄😄😄\n2nd line", 27, u"FAILED some::nodeid - 😄...") | ||||||
|  |     check(u"😄😄😄😄😄\n2nd line", 28, u"FAILED some::nodeid - 😄...") | ||||||
|  |     check(u"😄😄😄😄😄\n2nd line", 29, u"FAILED some::nodeid - 😄😄...") | ||||||
|  | 
 | ||||||
|  |     # NOTE: constructed, not sure if this is supported. | ||||||
|  |     # It would fail if not using u"" in Python 2 for mocked_pos. | ||||||
|  |     mocked_pos = u"nodeid::😄::withunicode" | ||||||
|  |     check(u"😄😄😄😄😄\n2nd line", 29, u"FAILED nodeid::😄::withunicode") | ||||||
|  |     check(u"😄😄😄😄😄\n2nd line", 40, u"FAILED nodeid::😄::withunicode - 😄😄...") | ||||||
|  |     check(u"😄😄😄😄😄\n2nd line", 41, u"FAILED nodeid::😄::withunicode - 😄😄...") | ||||||
|  |     check(u"😄😄😄😄😄\n2nd line", 42, u"FAILED nodeid::😄::withunicode - 😄😄😄...") | ||||||
|  |     check(u"😄😄😄😄😄\n2nd line", 80, u"FAILED nodeid::😄::withunicode - 😄😄😄😄😄") | ||||||
|  |  | ||||||
|  | @ -58,8 +58,8 @@ class TestTempdirHandler(object): | ||||||
|         assert tmp2.relto(t.getbasetemp()).startswith("this") |         assert tmp2.relto(t.getbasetemp()).startswith("this") | ||||||
|         assert tmp2 != tmp |         assert tmp2 != tmp | ||||||
| 
 | 
 | ||||||
|     @pytest.mark.issue(4425) |  | ||||||
|     def test_tmppath_relative_basetemp_absolute(self, tmp_path, monkeypatch): |     def test_tmppath_relative_basetemp_absolute(self, tmp_path, monkeypatch): | ||||||
|  |         """#4425""" | ||||||
|         from _pytest.tmpdir import TempPathFactory |         from _pytest.tmpdir import TempPathFactory | ||||||
| 
 | 
 | ||||||
|         monkeypatch.chdir(tmp_path) |         monkeypatch.chdir(tmp_path) | ||||||
|  |  | ||||||
|  | @ -930,11 +930,11 @@ def test_class_method_containing_test_issue1558(testdir): | ||||||
|     reprec.assertoutcome(passed=1) |     reprec.assertoutcome(passed=1) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| @pytest.mark.issue(3498) |  | ||||||
| @pytest.mark.parametrize( | @pytest.mark.parametrize( | ||||||
|     "base", ["six.moves.builtins.object", "unittest.TestCase", "unittest2.TestCase"] |     "base", ["six.moves.builtins.object", "unittest.TestCase", "unittest2.TestCase"] | ||||||
| ) | ) | ||||||
| def test_usefixtures_marker_on_unittest(base, testdir): | def test_usefixtures_marker_on_unittest(base, testdir): | ||||||
|  |     """#3498""" | ||||||
|     module = base.rsplit(".", 1)[0] |     module = base.rsplit(".", 1)[0] | ||||||
|     pytest.importorskip(module) |     pytest.importorskip(module) | ||||||
|     testdir.makepyfile( |     testdir.makepyfile( | ||||||
|  |  | ||||||
|  | @ -302,7 +302,7 @@ def test_filterwarnings_mark_registration(testdir): | ||||||
|             pass |             pass | ||||||
|     """ |     """ | ||||||
|     ) |     ) | ||||||
|     result = testdir.runpytest("--strict") |     result = testdir.runpytest("--strict-markers") | ||||||
|     assert result.ret == 0 |     assert result.ret == 0 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | @ -630,7 +630,7 @@ def test_removed_in_pytest4_warning_as_error(testdir, change_default): | ||||||
| class TestAssertionWarnings: | class TestAssertionWarnings: | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def assert_result_warns(result, msg): |     def assert_result_warns(result, msg): | ||||||
|         result.stdout.fnmatch_lines(["*PytestWarning: %s*" % msg]) |         result.stdout.fnmatch_lines(["*PytestAssertRewriteWarning: %s*" % msg]) | ||||||
| 
 | 
 | ||||||
|     def test_tuple_warning(self, testdir): |     def test_tuple_warning(self, testdir): | ||||||
|         testdir.makepyfile( |         testdir.makepyfile( | ||||||
|  |  | ||||||
							
								
								
									
										11
									
								
								tox.ini
								
								
								
								
							
							
						
						
									
										11
									
								
								tox.ini
								
								
								
								
							|  | @ -139,7 +139,7 @@ commands = python scripts/release.py {posargs} | ||||||
| 
 | 
 | ||||||
| [pytest] | [pytest] | ||||||
| minversion = 2.0 | minversion = 2.0 | ||||||
| addopts = -ra -p pytester | addopts = -ra -p pytester --strict-markers | ||||||
| rsyncdirs = tox.ini doc src testing | rsyncdirs = tox.ini doc src testing | ||||||
| python_files = test_*.py *_test.py testing/*/*.py | python_files = test_*.py *_test.py testing/*/*.py | ||||||
| python_classes = Test Acceptance | python_classes = Test Acceptance | ||||||
|  | @ -166,7 +166,16 @@ filterwarnings = | ||||||
|     ignore::pytest.PytestExperimentalApiWarning |     ignore::pytest.PytestExperimentalApiWarning | ||||||
|     # Do not cause SyntaxError for invalid escape sequences in py37. |     # Do not cause SyntaxError for invalid escape sequences in py37. | ||||||
|     default:invalid escape sequence:DeprecationWarning |     default:invalid escape sequence:DeprecationWarning | ||||||
|  |     # ignore use of unregistered marks, because we use many to test the implementation | ||||||
|  |     ignore::_pytest.warning_types.PytestUnknownMarkWarning | ||||||
| pytester_example_dir = testing/example_scripts | pytester_example_dir = testing/example_scripts | ||||||
|  | markers = | ||||||
|  |     # dummy markers for testing | ||||||
|  |     foo | ||||||
|  |     bar | ||||||
|  |     baz | ||||||
|  |     # conftest.py reorders tests moving slow ones to the end of the list | ||||||
|  |     slow | ||||||
| 
 | 
 | ||||||
| [flake8] | [flake8] | ||||||
| max-line-length = 120 | max-line-length = 120 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue