Merge pull request #4600 from nicoddemus/release-4.1.0

Release 4.1.0
This commit is contained in:
Bruno Oliveira 2019-01-06 13:09:11 -02:00 committed by GitHub
commit 38adb23bd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
90 changed files with 2190 additions and 2931 deletions

1
.gitignore vendored
View File

@ -44,3 +44,4 @@ coverage.xml
.pydevproject .pydevproject
.project .project
.settings .settings
.vscode

View File

@ -12,11 +12,13 @@ Alan Velasco
Alexander Johnson Alexander Johnson
Alexei Kozlenok Alexei Kozlenok
Allan Feldman Allan Feldman
Aly Sivji
Anatoly Bubenkoff Anatoly Bubenkoff
Anders Hovmöller Anders Hovmöller
Andras Tim Andras Tim
Andrea Cimatoribus Andrea Cimatoribus
Andreas Zeidler Andreas Zeidler
Andrey Paramonov
Andrzej Ostrowski Andrzej Ostrowski
Andy Freeland Andy Freeland
Anthon van der Neut Anthon van der Neut
@ -165,6 +167,7 @@ Miro Hrončok
Nathaniel Waisbrot Nathaniel Waisbrot
Ned Batchelder Ned Batchelder
Neven Mundar Neven Mundar
Nicholas Devenish
Niclas Olofsson Niclas Olofsson
Nicolas Delaby Nicolas Delaby
Oleg Pidsadnyi Oleg Pidsadnyi

View File

@ -18,6 +18,232 @@ with advance notice in the **Deprecations** section of releases.
.. towncrier release notes start .. towncrier release notes start
pytest 4.1.0 (2019-01-05)
=========================
Removals
--------
- `#2169 <https://github.com/pytest-dev/pytest/issues/2169>`_: ``pytest.mark.parametrize``: in previous versions, errors raised by id functions were suppressed and changed into warnings. Now the exceptions are propagated, along with a pytest message informing the node, parameter value and index where the exception occurred.
- `#3078 <https://github.com/pytest-dev/pytest/issues/3078>`_: Remove legacy internal warnings system: ``config.warn``, ``Node.warn``. The ``pytest_logwarning`` now issues a warning when implemented.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#config-warn-and-node-warn>`__ on information on how to update your code.
- `#3079 <https://github.com/pytest-dev/pytest/issues/3079>`_: Removed support for yield tests - they are fundamentally broken because they don't support fixtures properly since collection and test execution were separated.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#yield-tests>`__ on information on how to update your code.
- `#3082 <https://github.com/pytest-dev/pytest/issues/3082>`_: Removed support for applying marks directly to values in ``@pytest.mark.parametrize``. Use ``pytest.param`` instead.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#marks-in-pytest-mark-parametrize>`__ on information on how to update your code.
- `#3083 <https://github.com/pytest-dev/pytest/issues/3083>`_: Removed ``Metafunc.addcall``. This was the predecessor mechanism to ``@pytest.mark.parametrize``.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#metafunc-addcall>`__ on information on how to update your code.
- `#3085 <https://github.com/pytest-dev/pytest/issues/3085>`_: Removed support for passing strings to ``pytest.main``. Now, always pass a list of strings instead.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#passing-command-line-string-to-pytest-main>`__ on information on how to update your code.
- `#3086 <https://github.com/pytest-dev/pytest/issues/3086>`_: ``[pytest]`` section in **setup.cfg** files is not longer supported, use ``[tool:pytest]`` instead. ``setup.cfg`` files
are meant for use with ``distutils``, and a section named ``pytest`` has notoriously been a source of conflicts and bugs.
Note that for **pytest.ini** and **tox.ini** files the section remains ``[pytest]``.
- `#3616 <https://github.com/pytest-dev/pytest/issues/3616>`_: Removed the deprecated compat properties for ``node.Class/Function/Module`` - use ``pytest.Class/Function/Module`` now.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#internal-classes-accessed-through-node>`__ on information on how to update your code.
- `#4421 <https://github.com/pytest-dev/pytest/issues/4421>`_: Removed the implementation of the ``pytest_namespace`` hook.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-namespace>`__ on information on how to update your code.
- `#4489 <https://github.com/pytest-dev/pytest/issues/4489>`_: Removed ``request.cached_setup``. This was the predecessor mechanism to modern fixtures.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#cached-setup>`__ on information on how to update your code.
- `#4535 <https://github.com/pytest-dev/pytest/issues/4535>`_: Removed the deprecated ``PyCollector.makeitem`` method. This method was made public by mistake a long time ago.
- `#4543 <https://github.com/pytest-dev/pytest/issues/4543>`_: Removed support to define fixtures using the ``pytest_funcarg__`` prefix. Use the ``@pytest.fixture`` decorator instead.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-funcarg-prefix>`__ on information on how to update your code.
- `#4545 <https://github.com/pytest-dev/pytest/issues/4545>`_: Calling fixtures directly is now always an error instead of a warning.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#calling-fixtures-directly>`__ on information on how to update your code.
- `#4546 <https://github.com/pytest-dev/pytest/issues/4546>`_: Remove ``Node.get_marker(name)`` the return value was not usable for more than a existence check.
Use ``Node.get_closest_marker(name)`` as a replacement.
- `#4547 <https://github.com/pytest-dev/pytest/issues/4547>`_: The deprecated ``record_xml_property`` fixture has been removed, use the more generic ``record_property`` instead.
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#record-xml-property>`__ for more information.
- `#4548 <https://github.com/pytest-dev/pytest/issues/4548>`_: An error is now raised if the ``pytest_plugins`` variable is defined in a non-top-level ``conftest.py`` file (i.e., not residing in the ``rootdir``).
See our `docs <https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files>`__ for more information.
- `#891 <https://github.com/pytest-dev/pytest/issues/891>`_: Remove ``testfunction.markername`` attributes - use ``Node.iter_markers(name=None)`` to iterate them.
Deprecations
------------
- `#3050 <https://github.com/pytest-dev/pytest/issues/3050>`_: Deprecated the ``pytest.config`` global.
See https://docs.pytest.org/en/latest/deprecations.html#pytest-config-global for rationale.
- `#3974 <https://github.com/pytest-dev/pytest/issues/3974>`_: Passing the ``message`` parameter of ``pytest.raises`` now issues a ``DeprecationWarning``.
It is a common mistake to think this parameter will match the exception message, while in fact
it only serves to provide a custom message in case the ``pytest.raises`` check fails. To avoid this
mistake and because it is believed to be little used, pytest is deprecating it without providing
an alternative for the moment.
If you have concerns about this, please comment on `issue #3974 <https://github.com/pytest-dev/pytest/issues/3974>`__.
- `#4435 <https://github.com/pytest-dev/pytest/issues/4435>`_: Deprecated ``raises(..., 'code(as_a_string)')`` and ``warns(..., 'code(as_a_string)')``.
See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec for rationale and examples.
Features
--------
- `#3191 <https://github.com/pytest-dev/pytest/issues/3191>`_: A warning is now issued when assertions are made for ``None``.
This is a common source of confusion among new users, which write:
.. code-block:: python
assert mocked_object.assert_called_with(3, 4, 5, key="value")
When they should write:
.. code-block:: python
mocked_object.assert_called_with(3, 4, 5, key="value")
Because the ``assert_called_with`` method of mock objects already executes an assertion.
This warning will not be issued when ``None`` is explicitly checked. An assertion like:
.. code-block:: python
assert variable is None
will not issue the warning.
- `#3632 <https://github.com/pytest-dev/pytest/issues/3632>`_: Richer equality comparison introspection on ``AssertionError`` for objects created using `attrs <http://www.attrs.org/en/stable/>`__ or `dataclasses <https://docs.python.org/3/library/dataclasses.html>`_ (Python 3.7+, `backported to 3.6 <https://pypi.org/project/dataclasses>`__).
- `#4278 <https://github.com/pytest-dev/pytest/issues/4278>`_: ``CACHEDIR.TAG`` files are now created inside cache directories.
Those files are part of the `Cache Directory Tagging Standard <http://www.bford.info/cachedir/spec.html>`__, and can
be used by backup or synchronization programs to identify pytest's cache directory as such.
- `#4292 <https://github.com/pytest-dev/pytest/issues/4292>`_: ``pytest.outcomes.Exit`` is derived from ``SystemExit`` instead of ``KeyboardInterrupt``. This allows us to better handle ``pdb`` exiting.
- `#4371 <https://github.com/pytest-dev/pytest/issues/4371>`_: Updated the ``--collect-only`` option to display test descriptions when ran using ``--verbose``.
- `#4386 <https://github.com/pytest-dev/pytest/issues/4386>`_: Restructured ``ExceptionInfo`` object construction and ensure incomplete instances have a ``repr``/``str``.
- `#4416 <https://github.com/pytest-dev/pytest/issues/4416>`_: pdb: added support for keyword arguments with ``pdb.set_trace``.
It handles ``header`` similar to Python 3.7 does it, and forwards any
other keyword arguments to the ``Pdb`` constructor.
This allows for ``__import__("pdb").set_trace(skip=["foo.*"])``.
- `#4483 <https://github.com/pytest-dev/pytest/issues/4483>`_: Added ini parameter ``junit_duration_report`` to optionally report test call durations, excluding setup and teardown times.
The JUnit XML specification and the default pytest behavior is to include setup and teardown times in the test duration
report. You can include just the call durations instead (excluding setup and teardown) by adding this to your ``pytest.ini`` file:
.. code-block:: ini
[pytest]
junit_duration_report = call
- `#4532 <https://github.com/pytest-dev/pytest/issues/4532>`_: ``-ra`` now will show errors and failures last, instead of as the first items in the summary.
This makes it easier to obtain a list of errors and failures to run tests selectively.
- `#4599 <https://github.com/pytest-dev/pytest/issues/4599>`_: ``pytest.importorskip`` now supports a ``reason`` parameter, which will be shown when the
requested module cannot be imported.
Bug Fixes
---------
- `#3532 <https://github.com/pytest-dev/pytest/issues/3532>`_: ``-p`` now accepts its argument without a space between the value, for example ``-pmyplugin``.
- `#4327 <https://github.com/pytest-dev/pytest/issues/4327>`_: ``approx`` again works with more generic containers, more precisely instances of ``Iterable`` and ``Sized`` instead of more restrictive ``Sequence``.
- `#4397 <https://github.com/pytest-dev/pytest/issues/4397>`_: Ensure that node ids are printable.
- `#4435 <https://github.com/pytest-dev/pytest/issues/4435>`_: Fixed ``raises(..., 'code(string)')`` frame filename.
- `#4458 <https://github.com/pytest-dev/pytest/issues/4458>`_: Display actual test ids in ``--collect-only``.
Improved Documentation
----------------------
- `#4557 <https://github.com/pytest-dev/pytest/issues/4557>`_: Markers example documentation page updated to support latest pytest version.
- `#4558 <https://github.com/pytest-dev/pytest/issues/4558>`_: Update cache documentation example to correctly show cache hit and miss.
- `#4580 <https://github.com/pytest-dev/pytest/issues/4580>`_: Improved detailed summary report documentation.
Trivial/Internal Changes
------------------------
- `#4447 <https://github.com/pytest-dev/pytest/issues/4447>`_: Changed the deprecation type of ``--result-log`` to ``PytestDeprecationWarning``.
It was decided to remove this feature at the next major revision.
pytest 4.0.2 (2018-12-13) pytest 4.0.2 (2018-12-13)
========================= =========================
@ -1757,7 +1983,7 @@ Bug Fixes
Trivial/Internal Changes Trivial/Internal Changes
------------------------ ------------------------
- pytest now depends on `attrs <https://pypi.org/project/attrs/>`_ for internal - pytest now depends on `attrs <https://pypi.org/project/attrs/>`__ for internal
structures to ease code maintainability. (`#2641 structures to ease code maintainability. (`#2641
<https://github.com/pytest-dev/pytest/issues/2641>`_) <https://github.com/pytest-dev/pytest/issues/2641>`_)

View File

@ -1 +0,0 @@
Markers example documentation page updated to support latest pytest version.

View File

@ -1 +0,0 @@
Update cache documentation example to correctly show cache hit and miss.

View File

@ -1 +0,0 @@
Improved detailed summary report documentation.

View File

@ -39,7 +39,7 @@ clean:
-rm -rf $(BUILDDIR)/* -rm -rf $(BUILDDIR)/*
regen: regen:
PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPT=-pno:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS} PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPTS=-pno:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
html: html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

View File

@ -6,6 +6,7 @@ Release announcements
:maxdepth: 2 :maxdepth: 2
release-4.1.0
release-4.0.2 release-4.0.2
release-4.0.1 release-4.0.1
release-4.0.0 release-4.0.0

View File

@ -0,0 +1,44 @@
pytest-4.1.0
=======================================
The pytest team is proud to announce the 4.1.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:
* Adam Johnson
* Aly Sivji
* Andrey Paramonov
* Anthony Sottile
* Bruno Oliveira
* Daniel Hahler
* David Vo
* Hyunchel Kim
* Jeffrey Rackauckas
* Kanguros
* Nicholas Devenish
* Pedro Algarvio
* Randy Barlow
* Ronny Pfannschmidt
* Tomer Keren
* feuillemorte
* wim glenn
Happy testing,
The Pytest Development Team

View File

@ -100,10 +100,9 @@ If you want to write test code that works on Python 2.4 as well,
you may also use two other ways to test for an expected exception:: you may also use two other ways to test for an expected exception::
pytest.raises(ExpectedException, func, *args, **kwargs) pytest.raises(ExpectedException, func, *args, **kwargs)
pytest.raises(ExpectedException, "func(*args, **kwargs)")
both of which execute the specified function with args and kwargs and which will execute the specified function with args and kwargs and
asserts that the given ``ExpectedException`` is raised. The reporter will assert that the given ``ExpectedException`` is raised. The reporter will
provide you with helpful output in case of failures such as *no provide you with helpful output in case of failures such as *no
exception* or *wrong exception*. exception* or *wrong exception*.

View File

@ -68,8 +68,6 @@ 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_property
(Deprecated) use record_property.
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

View File

@ -215,7 +215,9 @@ If you run this command for the first time, you can see the print statement:
> assert mydata == 23 > assert mydata == 23
E assert 42 == 23 E assert 42 == 23
test_caching.py:14: AssertionError test_caching.py:17: AssertionError
-------------------------- Captured stdout setup ---------------------------
running expensive computation...
1 failed in 0.12 seconds 1 failed in 0.12 seconds
If you run it a second time the value will be retrieved from If you run it a second time the value will be retrieved from
@ -234,7 +236,7 @@ the cache and nothing will be printed:
> assert mydata == 23 > assert mydata == 23
E assert 42 == 23 E assert 42 == 23
test_caching.py:14: AssertionError test_caching.py:17: AssertionError
1 failed in 0.12 seconds 1 failed in 0.12 seconds
See the :ref:`cache-api` for more details. See the :ref:`cache-api` for more details.

View File

@ -7,6 +7,11 @@ This page lists all pytest features that are currently deprecated or have been r
The objective is to give users a clear rationale why a certain feature has been removed, and what alternatives The objective is to give users a clear rationale why a certain feature has been removed, and what alternatives
should be used instead. should be used instead.
.. contents::
:depth: 3
:local:
Deprecated Features Deprecated Features
------------------- -------------------
@ -14,24 +19,205 @@ Below is a complete list of all pytest features which are considered deprecated.
:class:`_pytest.warning_types.PytestWarning` or subclasses, which can be filtered using :class:`_pytest.warning_types.PytestWarning` or subclasses, which can be filtered using
:ref:`standard warning filters <warnings>`. :ref:`standard warning filters <warnings>`.
Internal classes accessed through ``Node`` ``"message"`` parameter of ``pytest.raises``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.9 .. deprecated:: 4.1
Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances now issue It is a common mistake to think this parameter will match the exception message, while in fact
this warning:: it only serves to provide a custom message in case the ``pytest.raises`` check fails. To avoid this
mistake and because it is believed to be little used, pytest is deprecating it without providing
an alternative for the moment.
usage of Function.Module is deprecated, please use pytest.Module instead If you have concerns about this, please comment on `issue #3974 <https://github.com/pytest-dev/pytest/issues/3974>`__.
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings. ``pytest.config`` global
~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 4.1
The ``pytest.config`` global object is deprecated. Instead use
``request.config`` (via the ``request`` fixture) or if you are a plugin author
use the ``pytest_configure(config)`` hook.
.. _raises-warns-exec:
``raises`` / ``warns`` with a string as the second argument
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 4.1
Use the context manager form of these instead. When necessary, invoke ``exec``
directly.
Example:
.. code-block:: python
pytest.raises(ZeroDivisionError, "1 / 0")
pytest.raises(SyntaxError, "a $ b")
pytest.warns(DeprecationWarning, "my_function()")
pytest.warns(SyntaxWarning, "assert(1, 2)")
Becomes:
.. code-block:: python
with pytest.raises(ZeroDivisionError):
1 / 0
with pytest.raises(SyntaxError):
exec("a $ b") # exec is required for invalid syntax
with pytest.warns(DeprecationWarning):
my_function()
with pytest.warns(SyntaxWarning):
exec("assert(1, 2)") # exec is used to avoid a top-level warning
Result log (``--result-log``)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.0
The ``--resultlog`` command line option has been deprecated: it is little used
and there are more modern and better alternatives, for example `pytest-tap <https://tappy.readthedocs.io/en/latest/>`_.
This feature will be effectively removed in pytest 4.0 as the team intends to include a better alternative in the core.
If you have any concerns, please don't hesitate to `open an issue <https://github.com/pytest-dev/pytest/issues>`__.
Removed Features
----------------
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
an appropriate period of deprecation has passed.
Using ``Class`` in custom Collectors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*Removed in version 4.0.*
Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector``
subclasses has been deprecated. Users instead should use ``pytest_pycollect_makeitem`` to customize node types during
collection.
This issue should affect only advanced plugins who create new collection types, so if you see this warning
message please contact the authors so they can change the code.
marks in ``pytest.mark.parametrize``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*Removed in version 4.0.*
Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated. For example:
.. code-block:: python
@pytest.mark.parametrize(
"a, b",
[
(3, 9),
pytest.mark.xfail(reason="flaky")(6, 36),
(10, 100),
(20, 200),
(40, 400),
(50, 500),
],
)
def test_foo(a, b):
...
This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
call.
This was considered hard to read and understand, and also its implementation presented problems to the code preventing
further internal improvements in the marks architecture.
To update the code, use ``pytest.param``:
.. code-block:: python
@pytest.mark.parametrize(
"a, b",
[
(3, 9),
pytest.param(6, 36, marks=pytest.mark.xfail(reason="flaky")),
(10, 100),
(20, 200),
(40, 400),
(50, 500),
],
)
def test_foo(a, b):
...
``pytest_funcarg__`` prefix
~~~~~~~~~~~~~~~~~~~~~~~~~~~
*Removed in version 4.0.*
In very early pytest versions fixtures could be defined using the ``pytest_funcarg__`` prefix:
.. code-block:: python
def pytest_funcarg__data():
return SomeData()
Switch over to the ``@pytest.fixture`` decorator:
.. code-block:: python
@pytest.fixture
def data():
return SomeData()
[pytest] section in setup.cfg files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*Removed in version 4.0.*
``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]``
to avoid conflicts with other distutils commands.
Metafunc.addcall
~~~~~~~~~~~~~~~~
*Removed in version 4.0.*
:meth:`_pytest.python.Metafunc.addcall` was a precursor to the current parametrized mechanism. Users should use
:meth:`_pytest.python.Metafunc.parametrize` instead.
Example:
.. code-block:: python
def pytest_generate_tests(metafunc):
metafunc.addcall({"i": 1}, id="1")
metafunc.addcall({"i": 2}, id="2")
Becomes:
.. code-block:: python
def pytest_generate_tests(metafunc):
metafunc.parametrize("i", [1, 2], ids=["1", "2"])
``cached_setup`` ``cached_setup``
~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~
.. deprecated:: 3.9 *Removed in version 4.0.*
``request.cached_setup`` was the precursor of the setup/teardown mechanism available to fixtures. ``request.cached_setup`` was the precursor of the setup/teardown mechanism available to fixtures.
@ -59,26 +245,21 @@ This should be updated to make use of standard fixture mechanisms:
You can consult `funcarg comparison section in the docs <https://docs.pytest.org/en/latest/funcarg_compare.html>`_ for You can consult `funcarg comparison section in the docs <https://docs.pytest.org/en/latest/funcarg_compare.html>`_ for
more information. more information.
This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings.
pytest_plugins in non-top-level conftest files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Using ``Class`` in custom Collectors *Removed in version 4.0.*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.9 Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py
files because they will activate referenced plugins *globally*, which is surprising because for all other pytest
Using objects named ``"Class"`` as a way to customize the type of nodes that are collected in ``Collector`` features ``conftest.py`` files are only *active* for tests at or below it.
subclasses has been deprecated. Users instead should use ``pytest_pycollect_makeitem`` to customize node types during
collection.
This issue should affect only advanced plugins who create new collection types, so if you see this warning
message please contact the authors so they can change the code.
``Config.warn`` and ``Node.warn`` ``Config.warn`` and ``Node.warn``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.8 *Removed in version 4.0.*
Those methods were part of the internal pytest warnings system, but since ``3.8`` pytest is using the builtin warning Those methods were part of the internal pytest warnings system, but since ``3.8`` pytest is using the builtin warning
system for its own warnings, so those two functions are now deprecated. system for its own warnings, so those two functions are now deprecated.
@ -100,47 +281,57 @@ Becomes:
* ``node.warn(PytestWarning("some message"))``: is now the **recommended** way to call this function. * ``node.warn(PytestWarning("some message"))``: is now the **recommended** way to call this function.
The warning instance must be a PytestWarning or subclass. The warning instance must be a PytestWarning or subclass.
* ``node.warn("CI", "some message")``: this code/message form is now **deprecated** and should be converted to the warning instance form above. * ``node.warn("CI", "some message")``: this code/message form has been **removed** and should be converted to the warning instance form above.
record_xml_property
~~~~~~~~~~~~~~~~~~~
``pytest_namespace`` *Removed in version 4.0.*
~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.7 The ``record_xml_property`` fixture is now deprecated in favor of the more generic ``record_property``, which
can be used by other consumers (for example ``pytest-html``) to obtain custom information about the test run.
This hook is deprecated because it greatly complicates the pytest internals regarding configuration and initialization, making some This is just a matter of renaming the fixture as the API is the same:
bug fixes and refactorings impossible.
Example of usage:
.. code-block:: python .. code-block:: python
class MySymbol: def test_foo(record_xml_property):
...
Change to:
.. code-block:: python
def test_foo(record_property):
... ...
def pytest_namespace(): Passing command-line string to ``pytest.main()``
return {"my_symbol": MySymbol()} ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*Removed in version 4.0.*
Plugin authors relying on this hook should instead require that users now import the plugin modules directly (with an appropriate public API). Passing a command-line string to ``pytest.main()`` is deprecated:
As a stopgap measure, plugin authors may still inject their names into pytest's namespace, usually during ``pytest_configure``:
.. code-block:: python .. code-block:: python
import pytest pytest.main("-v -s")
Pass a list instead:
.. code-block:: python
pytest.main(["-v", "-s"])
def pytest_configure(): By passing a string, users expect that pytest will interpret that command-line using the shell rules they are working
pytest.my_symbol = MySymbol() on (for example ``bash`` or ``Powershell``), but this is very hard/impossible to do in a portable way.
Calling fixtures directly Calling fixtures directly
~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.7 *Removed in version 4.0.*
Calling a fixture function directly, as opposed to request them in a test function, is deprecated. Calling a fixture function directly, as opposed to request them in a test function, is deprecated.
@ -175,116 +366,27 @@ In those cases just request the function directly in the dependent fixture:
cell.make_full() cell.make_full()
return cell return cell
``Node.get_marker`` Alternatively if the fixture function is called multiple times inside a test (making it hard to apply the above pattern) or
~~~~~~~~~~~~~~~~~~~ if you would like to make minimal changes to the code, you can create a fixture which calls the original function together
with the ``name`` parameter:
.. deprecated:: 3.6
As part of a large :ref:`marker-revamp`, :meth:`_pytest.nodes.Node.get_marker` is deprecated. See
:ref:`the documentation <update marker code>` on tips on how to update your code.
record_xml_property
~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.5
The ``record_xml_property`` fixture is now deprecated in favor of the more generic ``record_property``, which
can be used by other consumers (for example ``pytest-html``) to obtain custom information about the test run.
This is just a matter of renaming the fixture as the API is the same:
.. code-block:: python .. code-block:: python
def test_foo(record_xml_property): def cell():
... return ...
Change to:
.. code-block:: python
def test_foo(record_property):
...
pytest_plugins in non-top-level conftest files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.5
Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py
files because they will activate referenced plugins *globally*, which is surprising because for all other pytest
features ``conftest.py`` files are only *active* for tests at or below it.
Metafunc.addcall
~~~~~~~~~~~~~~~~
.. deprecated:: 3.3
:meth:`_pytest.python.Metafunc.addcall` was a precursor to the current parametrized mechanism. Users should use
:meth:`_pytest.python.Metafunc.parametrize` instead.
marks in ``pytest.mark.parametrize``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.2
Applying marks to values of a ``pytest.mark.parametrize`` call is now deprecated. For example:
.. code-block:: python
@pytest.mark.parametrize(
"a, b", [(3, 9), pytest.mark.xfail(reason="flaky")(6, 36), (10, 100)]
)
def test_foo(a, b):
...
This code applies the ``pytest.mark.xfail(reason="flaky")`` mark to the ``(6, 36)`` value of the above parametrization
call.
This was considered hard to read and understand, and also its implementation presented problems to the code preventing
further internal improvements in the marks architecture.
To update the code, use ``pytest.param``:
.. code-block:: python
@pytest.mark.parametrize(
"a, b",
[(3, 9), pytest.param((6, 36), marks=pytest.mark.xfail(reason="flaky")), (10, 100)],
)
def test_foo(a, b):
...
@pytest.fixture(name="cell")
Passing command-line string to ``pytest.main()`` def cell_fixture():
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ return cell()
.. deprecated:: 3.0
Passing a command-line string to ``pytest.main()`` is deprecated:
.. code-block:: python
pytest.main("-v -s")
Pass a list instead:
.. code-block:: python
pytest.main(["-v", "-s"])
By passing a string, users expect that pytest will interpret that command-line using the shell rules they are working
on (for example ``bash`` or ``Powershell``), but this is very hard/impossible to do in a portable way.
``yield`` tests ``yield`` tests
~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
.. deprecated:: 3.0 *Removed in version 4.0.*
pytest supports ``yield``-style tests, where a test function actually ``yield`` functions and values pytest supported ``yield``-style tests, where a test function actually ``yield`` functions and values
that are then turned into proper test methods. Example: that are then turned into proper test methods. Example:
.. code-block:: python .. code-block:: python
@ -307,48 +409,53 @@ This form of test function doesn't support fixtures properly, and users should s
def test_squared(x, y): def test_squared(x, y):
assert x ** x == y assert x ** x == y
Internal classes accessed through ``Node``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
``pytest_funcarg__`` prefix *Removed in version 4.0.*
~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.0 Access of ``Module``, ``Function``, ``Class``, ``Instance``, ``File`` and ``Item`` through ``Node`` instances now issue
this warning::
In very early pytest versions fixtures could be defined using the ``pytest_funcarg__`` prefix: usage of Function.Module is deprecated, please use pytest.Module instead
Users should just ``import pytest`` and access those objects using the ``pytest`` module.
This has been documented as deprecated for years, but only now we are actually emitting deprecation warnings.
``pytest_namespace``
~~~~~~~~~~~~~~~~~~~~
*Removed in version 4.0.*
This hook is deprecated because it greatly complicates the pytest internals regarding configuration and initialization, making some
bug fixes and refactorings impossible.
Example of usage:
.. code-block:: python .. code-block:: python
def pytest_funcarg__data(): class MySymbol:
return SomeData() ...
Switch over to the ``@pytest.fixture`` decorator:
def pytest_namespace():
return {"my_symbol": MySymbol()}
Plugin authors relying on this hook should instead require that users now import the plugin modules directly (with an appropriate public API).
As a stopgap measure, plugin authors may still inject their names into pytest's namespace, usually during ``pytest_configure``:
.. code-block:: python .. code-block:: python
@pytest.fixture import pytest
def data():
return SomeData()
[pytest] section in setup.cfg files
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.0 def pytest_configure():
pytest.my_symbol = MySymbol()
``[pytest]`` sections in ``setup.cfg`` files should now be named ``[tool:pytest]``
to avoid conflicts with other distutils commands.
Result log (``--result-log``)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. deprecated:: 3.0
The ``--resultlog`` command line option has been deprecated: it is little used
and there are more modern and better alternatives, for example `pytest-tap <https://tappy.readthedocs.io/en/latest/>`_.
Removed Features
----------------
As stated in our :ref:`backwards-compatibility` policy, deprecated features are removed only in major releases after
an appropriate period of deprecation has passed.
Reinterpretation mode (``--assert=reinterp``) Reinterpretation mode (``--assert=reinterp``)
@ -384,3 +491,21 @@ Removed all ``py.test-X*`` entry points. The versioned, suffixed entry points
were never documented and a leftover from a pre-virtualenv era. These entry were never documented and a leftover from a pre-virtualenv era. These entry
points also created broken entry points in wheels, so removing them also points also created broken entry points in wheels, so removing them also
removes a source of confusion for users. removes a source of confusion for users.
``Node.get_marker``
~~~~~~~~~~~~~~~~~~~
*Removed in version 4.0*
As part of a large :ref:`marker-revamp`, :meth:`_pytest.nodes.Node.get_marker` is deprecated. See
:ref:`the documentation <update marker code>` on tips on how to update your code.
``somefunction.markname``
~~~~~~~~~~~~~~~~~~~~~~~~~
*Removed in version 4.0*
As part of a large :ref:`marker-revamp` we already deprecated using ``MarkInfo``
the only correct way to get markers of an element is via ``node.iter_markers(name)``.

View File

@ -98,6 +98,30 @@ class TestSpecialisedExplanations(object):
text = "head " * 50 + "f" * 70 + "tail " * 20 text = "head " * 50 + "f" * 70 + "tail " * 20
assert "f" * 70 not in text assert "f" * 70 not in text
def test_eq_dataclass(self):
from dataclasses import dataclass
@dataclass
class Foo(object):
a: int
b: str
left = Foo(1, "b")
right = Foo(1, "c")
assert left == right
def test_eq_attrs(self):
import attr
@attr.s
class Foo(object):
a = attr.ib()
b = attr.ib()
left = Foo(1, "b")
right = Foo(1, "c")
assert left == right
def test_attribute(): def test_attribute():
class Foo(object): class Foo(object):
@ -141,11 +165,11 @@ def globf(x):
class TestRaises(object): class TestRaises(object):
def test_raises(self): def test_raises(self):
s = "qwe" # NOQA s = "qwe"
raises(TypeError, "int(s)") raises(TypeError, int, s)
def test_raises_doesnt(self): def test_raises_doesnt(self):
raises(IOError, "int('3')") raises(IOError, int, "3")
def test_raise(self): def test_raise(self):
raise ValueError("demo error") raise ValueError("demo error")

View File

@ -9,5 +9,5 @@ def test_failure_demo_fails_properly(testdir):
failure_demo.copy(target) failure_demo.copy(target)
failure_demo.copy(testdir.tmpdir.join(failure_demo.basename)) failure_demo.copy(testdir.tmpdir.join(failure_demo.basename))
result = testdir.runpytest(target, syspathinsert=True) result = testdir.runpytest(target, syspathinsert=True)
result.stdout.fnmatch_lines(["*42 failed*"]) result.stdout.fnmatch_lines(["*44 failed*"])
assert result.ret != 0 assert result.ret != 0

View File

@ -90,9 +90,9 @@ interesting to just look at the collection tree:
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR/nonpython, inifile: rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
collected 2 items collected 2 items
<Package '$REGENDOC_TMPDIR/nonpython'> <Package $REGENDOC_TMPDIR/nonpython>
<YamlFile 'test_simple.yml'> <YamlFile test_simple.yml>
<YamlItem 'hello'> <YamlItem hello>
<YamlItem 'ok'> <YamlItem ok>
======================= no tests ran in 0.12 seconds ======================= ======================= no tests ran in 0.12 seconds =======================

View File

@ -147,15 +147,15 @@ objects, they are still using the default pytest representation:
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 8 items collected 8 items
<Module 'test_time.py'> <Module test_time.py>
<Function 'test_timedistance_v0[a0-b0-expected0]'> <Function test_timedistance_v0[a0-b0-expected0]>
<Function 'test_timedistance_v0[a1-b1-expected1]'> <Function test_timedistance_v0[a1-b1-expected1]>
<Function 'test_timedistance_v1[forward]'> <Function test_timedistance_v1[forward]>
<Function 'test_timedistance_v1[backward]'> <Function test_timedistance_v1[backward]>
<Function 'test_timedistance_v2[20011212-20011211-expected0]'> <Function test_timedistance_v2[20011212-20011211-expected0]>
<Function 'test_timedistance_v2[20011211-20011212-expected1]'> <Function test_timedistance_v2[20011211-20011212-expected1]>
<Function 'test_timedistance_v3[forward]'> <Function test_timedistance_v3[forward]>
<Function 'test_timedistance_v3[backward]'> <Function test_timedistance_v3[backward]>
======================= no tests ran in 0.12 seconds ======================= ======================= no tests ran in 0.12 seconds =======================
@ -219,12 +219,12 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items collected 4 items
<Module 'test_scenarios.py'> <Module test_scenarios.py>
<Class 'TestSampleWithScenarios'> <Class TestSampleWithScenarios>
<Function 'test_demo1[basic]'> <Function test_demo1[basic]>
<Function 'test_demo2[basic]'> <Function test_demo2[basic]>
<Function 'test_demo1[advanced]'> <Function test_demo1[advanced]>
<Function 'test_demo2[advanced]'> <Function test_demo2[advanced]>
======================= no tests ran in 0.12 seconds ======================= ======================= no tests ran in 0.12 seconds =======================
@ -285,9 +285,9 @@ Let's first see how it looks like at collection time:
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items collected 2 items
<Module 'test_backends.py'> <Module test_backends.py>
<Function 'test_db_initialized[d1]'> <Function test_db_initialized[d1]>
<Function 'test_db_initialized[d2]'> <Function test_db_initialized[d2]>
======================= no tests ran in 0.12 seconds ======================= ======================= no tests ran in 0.12 seconds =======================
@ -350,8 +350,8 @@ The result of this test will be successful:
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 item collected 1 item
<Module 'test_indirect_list.py'> <Module test_indirect_list.py>
<Function 'test_indirect[a-b]'> <Function test_indirect[a-b]>
======================= no tests ran in 0.12 seconds ======================= ======================= no tests ran in 0.12 seconds =======================
@ -388,7 +388,8 @@ parametrizer`_ but in a lot less code::
assert a == b assert a == b
def test_zerodivision(self, a, b): def test_zerodivision(self, a, b):
pytest.raises(ZeroDivisionError, "a/b") with pytest.raises(ZeroDivisionError):
a / b
Our test generator looks up a class-level definition which specifies which Our test generator looks up a class-level definition which specifies which
argument sets to use for each test function. Let's run it: argument sets to use for each test function. Let's run it:

View File

@ -134,10 +134,10 @@ The test collection would look like this:
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 2 items collected 2 items
<Module 'check_myapp.py'> <Module check_myapp.py>
<Class 'CheckMyApp'> <Class CheckMyApp>
<Function 'simple_check'> <Function simple_check>
<Function 'complex_check'> <Function complex_check>
======================= no tests ran in 0.12 seconds ======================= ======================= no tests ran in 0.12 seconds =======================
@ -189,11 +189,11 @@ You can always peek at the collection tree without running tests like this:
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 3 items collected 3 items
<Module 'CWD/pythoncollection.py'> <Module CWD/pythoncollection.py>
<Function 'test_function'> <Function test_function>
<Class 'TestClass'> <Class TestClass>
<Function 'test_method'> <Function test_method>
<Function 'test_anothermethod'> <Function test_anothermethod>
======================= no tests ran in 0.12 seconds ======================= ======================= no tests ran in 0.12 seconds =======================

View File

@ -15,9 +15,9 @@ get on the terminal - we are working on that):
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR/assertion, inifile: rootdir: $REGENDOC_TMPDIR/assertion, inifile:
collected 42 items collected 44 items
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [100%] failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [100%]
================================= FAILURES ================================= ================================= FAILURES =================================
___________________________ test_generative[3-6] ___________________________ ___________________________ test_generative[3-6] ___________________________
@ -289,6 +289,48 @@ get on the terminal - we are working on that):
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
failure_demo.py:99: AssertionError failure_demo.py:99: AssertionError
______________ TestSpecialisedExplanations.test_eq_dataclass _______________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
def test_eq_dataclass(self):
from dataclasses import dataclass
@dataclass
class Foo(object):
a: int
b: str
left = Foo(1, "b")
right = Foo(1, "c")
> assert left == right
E AssertionError: assert TestSpecialis...oo(a=1, b='b') == TestSpecialise...oo(a=1, b='c')
E Omitting 1 identical items, use -vv to show
E Differing attributes:
E b: 'b' != 'c'
failure_demo.py:111: AssertionError
________________ TestSpecialisedExplanations.test_eq_attrs _________________
self = <failure_demo.TestSpecialisedExplanations object at 0xdeadbeef>
def test_eq_attrs(self):
import attr
@attr.s
class Foo(object):
a = attr.ib()
b = attr.ib()
left = Foo(1, "b")
right = Foo(1, "c")
> assert left == right
E AssertionError: assert Foo(a=1, b='b') == Foo(a=1, b='c')
E Omitting 1 identical items, use -vv to show
E Differing attributes:
E b: 'b' != 'c'
failure_demo.py:123: AssertionError
______________________________ test_attribute ______________________________ ______________________________ test_attribute ______________________________
def test_attribute(): def test_attribute():
@ -300,7 +342,7 @@ get on the terminal - we are working on that):
E assert 1 == 2 E assert 1 == 2
E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b E + where 1 = <failure_demo.test_attribute.<locals>.Foo object at 0xdeadbeef>.b
failure_demo.py:107: AssertionError failure_demo.py:131: AssertionError
_________________________ test_attribute_instance __________________________ _________________________ test_attribute_instance __________________________
def test_attribute_instance(): def test_attribute_instance():
@ -312,7 +354,7 @@ get on the terminal - we are working on that):
E + where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef>.b E + where 1 = <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef>.b
E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>() E + where <failure_demo.test_attribute_instance.<locals>.Foo object at 0xdeadbeef> = <class 'failure_demo.test_attribute_instance.<locals>.Foo'>()
failure_demo.py:114: AssertionError failure_demo.py:138: AssertionError
__________________________ test_attribute_failure __________________________ __________________________ test_attribute_failure __________________________
def test_attribute_failure(): def test_attribute_failure():
@ -325,7 +367,7 @@ get on the terminal - we are working on that):
i = Foo() i = Foo()
> assert i.b == 2 > assert i.b == 2
failure_demo.py:125: failure_demo.py:149:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef> self = <failure_demo.test_attribute_failure.<locals>.Foo object at 0xdeadbeef>
@ -334,7 +376,7 @@ get on the terminal - we are working on that):
> raise Exception("Failed to get attrib") > raise Exception("Failed to get attrib")
E Exception: Failed to get attrib E Exception: Failed to get attrib
failure_demo.py:120: Exception failure_demo.py:144: Exception
_________________________ test_attribute_multiple __________________________ _________________________ test_attribute_multiple __________________________
def test_attribute_multiple(): def test_attribute_multiple():
@ -351,31 +393,26 @@ get on the terminal - we are working on that):
E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b E + and 2 = <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef>.b
E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>() E + where <failure_demo.test_attribute_multiple.<locals>.Bar object at 0xdeadbeef> = <class 'failure_demo.test_attribute_multiple.<locals>.Bar'>()
failure_demo.py:135: AssertionError failure_demo.py:159: AssertionError
__________________________ TestRaises.test_raises __________________________ __________________________ TestRaises.test_raises __________________________
self = <failure_demo.TestRaises object at 0xdeadbeef> self = <failure_demo.TestRaises object at 0xdeadbeef>
def test_raises(self): def test_raises(self):
s = "qwe" # NOQA s = "qwe"
> raises(TypeError, "int(s)") > raises(TypeError, int, s)
E ValueError: invalid literal for int() with base 10: 'qwe'
failure_demo.py:145: failure_demo.py:169: ValueError
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
> int(s)
E ValueError: invalid literal for int() with base 10: 'qwe'
<0-codegen $REGENDOC_TMPDIR/assertion/failure_demo.py:145>:1: ValueError
______________________ TestRaises.test_raises_doesnt _______________________ ______________________ TestRaises.test_raises_doesnt _______________________
self = <failure_demo.TestRaises object at 0xdeadbeef> self = <failure_demo.TestRaises object at 0xdeadbeef>
def test_raises_doesnt(self): def test_raises_doesnt(self):
> raises(IOError, "int('3')") > raises(IOError, int, "3")
E Failed: DID NOT RAISE <class 'OSError'> E Failed: DID NOT RAISE <class 'OSError'>
failure_demo.py:148: Failed failure_demo.py:172: Failed
__________________________ TestRaises.test_raise ___________________________ __________________________ TestRaises.test_raise ___________________________
self = <failure_demo.TestRaises object at 0xdeadbeef> self = <failure_demo.TestRaises object at 0xdeadbeef>
@ -384,7 +421,7 @@ get on the terminal - we are working on that):
> raise ValueError("demo error") > raise ValueError("demo error")
E ValueError: demo error E ValueError: demo error
failure_demo.py:151: ValueError failure_demo.py:175: ValueError
________________________ TestRaises.test_tupleerror ________________________ ________________________ TestRaises.test_tupleerror ________________________
self = <failure_demo.TestRaises object at 0xdeadbeef> self = <failure_demo.TestRaises object at 0xdeadbeef>
@ -393,7 +430,7 @@ get on the terminal - we are working on that):
> a, b = [1] # NOQA > a, b = [1] # NOQA
E ValueError: not enough values to unpack (expected 2, got 1) E ValueError: not enough values to unpack (expected 2, got 1)
failure_demo.py:154: ValueError failure_demo.py:178: ValueError
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______ ______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
self = <failure_demo.TestRaises object at 0xdeadbeef> self = <failure_demo.TestRaises object at 0xdeadbeef>
@ -404,7 +441,7 @@ get on the terminal - we are working on that):
> a, b = items.pop() > a, b = items.pop()
E TypeError: 'int' object is not iterable E TypeError: 'int' object is not iterable
failure_demo.py:159: TypeError failure_demo.py:183: TypeError
--------------------------- Captured stdout call --------------------------- --------------------------- Captured stdout call ---------------------------
items is [1, 2, 3] items is [1, 2, 3]
________________________ TestRaises.test_some_error ________________________ ________________________ TestRaises.test_some_error ________________________
@ -415,7 +452,7 @@ get on the terminal - we are working on that):
> if namenotexi: # NOQA > if namenotexi: # NOQA
E NameError: name 'namenotexi' is not defined E NameError: name 'namenotexi' is not defined
failure_demo.py:162: NameError failure_demo.py:186: NameError
____________________ test_dynamic_compile_shows_nicely _____________________ ____________________ test_dynamic_compile_shows_nicely _____________________
def test_dynamic_compile_shows_nicely(): def test_dynamic_compile_shows_nicely():
@ -430,14 +467,14 @@ get on the terminal - we are working on that):
sys.modules[name] = module sys.modules[name] = module
> module.foo() > module.foo()
failure_demo.py:180: failure_demo.py:204:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
def foo(): def foo():
> assert 1 == 0 > assert 1 == 0
E AssertionError E AssertionError
<2-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:177>:2: AssertionError <0-codegen 'abc-123' $REGENDOC_TMPDIR/assertion/failure_demo.py:201>:2: AssertionError
____________________ TestMoreErrors.test_complex_error _____________________ ____________________ TestMoreErrors.test_complex_error _____________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -451,7 +488,7 @@ get on the terminal - we are working on that):
> somefunc(f(), g()) > somefunc(f(), g())
failure_demo.py:191: failure_demo.py:215:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:13: in somefunc failure_demo.py:13: in somefunc
otherfunc(x, y) otherfunc(x, y)
@ -473,7 +510,7 @@ get on the terminal - we are working on that):
> a, b = items > a, b = items
E ValueError: not enough values to unpack (expected 2, got 0) E ValueError: not enough values to unpack (expected 2, got 0)
failure_demo.py:195: ValueError failure_demo.py:219: ValueError
____________________ TestMoreErrors.test_z2_type_error _____________________ ____________________ TestMoreErrors.test_z2_type_error _____________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -483,7 +520,7 @@ get on the terminal - we are working on that):
> a, b = items > a, b = items
E TypeError: 'int' object is not iterable E TypeError: 'int' object is not iterable
failure_demo.py:199: TypeError failure_demo.py:223: TypeError
______________________ TestMoreErrors.test_startswith ______________________ ______________________ TestMoreErrors.test_startswith ______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -496,7 +533,7 @@ get on the terminal - we are working on that):
E + where False = <built-in method startswith of str object at 0xdeadbeef>('456') E + where False = <built-in method startswith of str object at 0xdeadbeef>('456')
E + where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith E + where <built-in method startswith of str object at 0xdeadbeef> = '123'.startswith
failure_demo.py:204: AssertionError failure_demo.py:228: AssertionError
__________________ TestMoreErrors.test_startswith_nested ___________________ __________________ TestMoreErrors.test_startswith_nested ___________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -515,7 +552,7 @@ get on the terminal - we are working on that):
E + where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef>() E + where '123' = <function TestMoreErrors.test_startswith_nested.<locals>.f at 0xdeadbeef>()
E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>() E + and '456' = <function TestMoreErrors.test_startswith_nested.<locals>.g at 0xdeadbeef>()
failure_demo.py:213: AssertionError failure_demo.py:237: AssertionError
_____________________ TestMoreErrors.test_global_func ______________________ _____________________ TestMoreErrors.test_global_func ______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -526,7 +563,7 @@ get on the terminal - we are working on that):
E + where False = isinstance(43, float) E + where False = isinstance(43, float)
E + where 43 = globf(42) E + where 43 = globf(42)
failure_demo.py:216: AssertionError failure_demo.py:240: AssertionError
_______________________ TestMoreErrors.test_instance _______________________ _______________________ TestMoreErrors.test_instance _______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -537,7 +574,7 @@ get on the terminal - we are working on that):
E assert 42 != 42 E assert 42 != 42
E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x E + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
failure_demo.py:220: AssertionError failure_demo.py:244: AssertionError
_______________________ TestMoreErrors.test_compare ________________________ _______________________ TestMoreErrors.test_compare ________________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -547,7 +584,7 @@ get on the terminal - we are working on that):
E assert 11 < 5 E assert 11 < 5
E + where 11 = globf(10) E + where 11 = globf(10)
failure_demo.py:223: AssertionError failure_demo.py:247: AssertionError
_____________________ TestMoreErrors.test_try_finally ______________________ _____________________ TestMoreErrors.test_try_finally ______________________
self = <failure_demo.TestMoreErrors object at 0xdeadbeef> self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
@ -558,7 +595,7 @@ get on the terminal - we are working on that):
> assert x == 0 > assert x == 0
E assert 1 == 0 E assert 1 == 0
failure_demo.py:228: AssertionError failure_demo.py:252: AssertionError
___________________ TestCustomAssertMsg.test_single_line ___________________ ___________________ TestCustomAssertMsg.test_single_line ___________________
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef> self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
@ -573,7 +610,7 @@ get on the terminal - we are working on that):
E assert 1 == 2 E assert 1 == 2
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_single_line.<locals>.A'>.a
failure_demo.py:239: AssertionError failure_demo.py:263: AssertionError
____________________ TestCustomAssertMsg.test_multiline ____________________ ____________________ TestCustomAssertMsg.test_multiline ____________________
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef> self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
@ -592,7 +629,7 @@ get on the terminal - we are working on that):
E assert 1 == 2 E assert 1 == 2
E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a E + where 1 = <class 'failure_demo.TestCustomAssertMsg.test_multiline.<locals>.A'>.a
failure_demo.py:246: AssertionError failure_demo.py:270: AssertionError
___________________ TestCustomAssertMsg.test_custom_repr ___________________ ___________________ TestCustomAssertMsg.test_custom_repr ___________________
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef> self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
@ -614,5 +651,5 @@ get on the terminal - we are working on that):
E assert 1 == 2 E assert 1 == 2
E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a E + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
failure_demo.py:259: AssertionError failure_demo.py:283: AssertionError
======================== 42 failed in 0.12 seconds ========================= ======================== 44 failed in 0.12 seconds =========================

View File

@ -598,7 +598,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, record_xml_property, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory > available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
> use 'pytest --fixtures [testpath]' for help on them. > use 'pytest --fixtures [testpath]' for help on them.
$REGENDOC_TMPDIR/b/test_error.py:1 $REGENDOC_TMPDIR/b/test_error.py:1

View File

@ -628,7 +628,7 @@ So let's just do another run:
response, msg = smtp_connection.ehlo() response, msg = smtp_connection.ehlo()
assert response == 250 assert response == 250
> assert b"smtp.gmail.com" in msg > assert b"smtp.gmail.com" in msg
E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8' E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8\nCHUNKING'
test_module.py:5: AssertionError test_module.py:5: AssertionError
-------------------------- Captured stdout setup --------------------------- -------------------------- Captured stdout setup ---------------------------
@ -703,19 +703,19 @@ Running the above tests results in the following test IDs being used:
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 10 items collected 10 items
<Module 'test_anothersmtp.py'> <Module test_anothersmtp.py>
<Function 'test_showhelo[smtp.gmail.com]'> <Function test_showhelo[smtp.gmail.com]>
<Function 'test_showhelo[mail.python.org]'> <Function test_showhelo[mail.python.org]>
<Module 'test_ids.py'> <Module test_ids.py>
<Function 'test_a[spam]'> <Function test_a[spam]>
<Function 'test_a[ham]'> <Function test_a[ham]>
<Function 'test_b[eggs]'> <Function test_b[eggs]>
<Function 'test_b[1]'> <Function test_b[1]>
<Module 'test_module.py'> <Module test_module.py>
<Function 'test_ehlo[smtp.gmail.com]'> <Function test_ehlo[smtp.gmail.com]>
<Function 'test_noop[smtp.gmail.com]'> <Function test_noop[smtp.gmail.com]>
<Function 'test_ehlo[mail.python.org]'> <Function test_ehlo[mail.python.org]>
<Function 'test_noop[mail.python.org]'> <Function test_noop[mail.python.org]>
======================= no tests ran in 0.12 seconds ======================= ======================= no tests ran in 0.12 seconds =======================

View File

@ -618,7 +618,6 @@ Session related reporting hooks:
.. autofunction:: pytest_terminal_summary .. autofunction:: pytest_terminal_summary
.. autofunction:: pytest_fixture_setup .. autofunction:: pytest_fixture_setup
.. autofunction:: pytest_fixture_post_finalizer .. autofunction:: pytest_fixture_post_finalizer
.. autofunction:: pytest_logwarning
.. autofunction:: pytest_warning_captured .. autofunction:: pytest_warning_captured
And here is the central hook for reporting about And here is the central hook for reporting about
@ -725,13 +724,6 @@ MarkGenerator
:members: :members:
MarkInfo
~~~~~~~~
.. autoclass:: _pytest.mark.MarkInfo
:members:
Mark Mark
~~~~ ~~~~

View File

@ -152,40 +152,77 @@ making it easy in large test suites to get a clear picture of all failures, skip
Example: Example:
.. code-block:: python
# content of test_example.py
import pytest
@pytest.fixture
def error_fixture():
assert 0
def test_ok():
print("ok")
def test_fail():
assert 0
def test_error(error_fixture):
pass
def test_skip():
pytest.skip("skipping this test")
def test_xfail():
pytest.xfail("xfailing this test")
@pytest.mark.xfail(reason="always xfail")
def test_xpass():
pass
.. code-block:: pytest .. code-block:: pytest
$ pytest -ra $ pytest -ra
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 7 items collected 6 items
test_examples.py ..FEsxX [100%] test_example.py .FEsxX [100%]
==================================== ERRORS ==================================== ================================== ERRORS ==================================
_________________________ ERROR at setup of test_error _________________________ _______________________ ERROR at setup of test_error _______________________
file /Users/chainz/tmp/pytestratest/test_examples.py, line 17
def test_error(unknown_fixture):
E fixture 'unknown_fixture' not found
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, record_xml_property, recwarn, tmp_path, tmp_path_factory, tmpdir, tmpdir_factory
> use 'pytest --fixtures [testpath]' for help on them.
/Users/chainz/tmp/pytestratest/test_examples.py:17 @pytest.fixture
=================================== FAILURES =================================== def error_fixture():
__________________________________ test_fail ___________________________________ > assert 0
E assert 0
test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________
def test_fail(): def test_fail():
> assert 0 > assert 0
E assert 0 E assert 0
test_examples.py:14: AssertionError test_example.py:14: AssertionError
=========================== short test summary info ============================ ========================= short test summary info ==========================
FAIL test_examples.py::test_fail SKIP [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
ERROR test_examples.py::test_error XFAIL test_example.py::test_xfail
SKIP [1] test_examples.py:21: Example reason: xfailing this test
XFAIL test_examples.py::test_xfail XPASS test_example.py::test_xpass always xfail
XPASS test_examples.py::test_xpass ERROR test_example.py::test_error
= 1 failed, 2 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.07 seconds = FAIL test_example.py::test_fail
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".
@ -208,22 +245,31 @@ More than one character can be used, so for example to only see failed and skipp
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items collected 6 items
test_examples.py Fs [100%] test_example.py .FEsxX [100%]
=================================== FAILURES =================================== ================================== ERRORS ==================================
__________________________________ test_fail ___________________________________ _______________________ ERROR at setup of test_error _______________________
@pytest.fixture
def error_fixture():
> assert 0
E assert 0
test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________
def test_fail(): def test_fail():
> assert 0 > assert 0
E assert 0 E assert 0
test_examples.py:14: AssertionError test_example.py:14: AssertionError
=========================== short test summary info ============================ ========================= short test summary info ==========================
FAIL test_examples.py::test_fail FAIL test_example.py::test_fail
SKIP [1] test_examples.py:21: Example SKIP [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
===================== 1 failed, 1 skipped in 0.09 seconds ====================== 1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds
Using ``p`` lists the passing tests, whilst ``P`` adds an extra section "PASSES" with those tests that passed but had Using ``p`` lists the passing tests, whilst ``P`` adds an extra section "PASSES" with those tests that passed but had
captured output: captured output:
@ -234,18 +280,34 @@ captured output:
=========================== test session starts ============================ =========================== test session starts ============================
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items collected 6 items
test_examples.py .. [100%] test_example.py .FEsxX [100%]
=========================== short test summary info ============================
PASSED test_examples.py::test_pass
PASSED test_examples.py::test_pass_with_output
==================================== PASSES ==================================== ================================== ERRORS ==================================
____________________________ test_pass_with_output _____________________________ _______________________ ERROR at setup of test_error _______________________
----------------------------- Captured stdout call -----------------------------
Passing test @pytest.fixture
=========================== 2 passed in 0.04 seconds =========================== def error_fixture():
> assert 0
E assert 0
test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________
def test_fail():
> assert 0
E assert 0
test_example.py:14: AssertionError
========================= short test summary info ==========================
PASSED test_example.py::test_ok
================================== PASSES ==================================
_________________________________ test_ok __________________________________
--------------------------- Captured stdout call ---------------------------
ok
1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds
.. _pdb-option: .. _pdb-option:
@ -354,6 +416,20 @@ To set the name of the root test suite xml item, you can configure the ``junit_s
[pytest] [pytest]
junit_suite_name = my_suite junit_suite_name = my_suite
.. versionadded:: 4.0
JUnit XML specification seems to indicate that ``"time"`` attribute
should report total test execution times, including setup and teardown
(`1 <http://windyroad.com.au/dl/Open%20Source/JUnit.xsd>`_, `2
<https://www.ibm.com/support/knowledgecenter/en/SSQ2R2_14.1.0/com.ibm.rsar.analysis.codereview.cobol.doc/topics/cac_useresults_junit.html>`_).
It is the default pytest behavior. To report just call durations
instead, configure the ``junit_duration_report`` option like this:
.. code-block:: ini
[pytest]
junit_duration_report = call
.. _record_property example: .. _record_property example:
record_property record_property
@ -543,14 +619,10 @@ Creating resultlog format files
.. deprecated:: 3.0 .. deprecated:: 3.0
This option is rarely used and is scheduled for removal in 4.0. This option is rarely used and is scheduled for removal in 5.0.
An alternative for users which still need similar functionality is to use the See `the deprecation docs <https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log>`__
`pytest-tap <https://pypi.org/project/pytest-tap/>`_ plugin which provides for more information.
a stream of test data.
If you have any concerns, please don't hesitate to
`open an issue <https://github.com/pytest-dev/pytest/issues>`_.
To create plain-text machine-readable result files you can issue:: To create plain-text machine-readable result files you can issue::
@ -621,8 +693,25 @@ Running it will show that ``MyPlugin`` was added and its
hook was invoked:: hook was invoked::
$ python myinvoke.py $ python myinvoke.py
. [100%]*** test run reporting finishing .FEsxX. [100%]*** test run reporting finishing
================================== ERRORS ==================================
_______________________ ERROR at setup of test_error _______________________
@pytest.fixture
def error_fixture():
> assert 0
E assert 0
test_example.py:6: AssertionError
================================= FAILURES =================================
________________________________ test_fail _________________________________
def test_fail():
> assert 0
E assert 0
test_example.py:14: AssertionError
.. note:: .. note::

View File

@ -391,44 +391,85 @@ co_equal = compile(
) )
@attr.s(repr=False)
class ExceptionInfo(object): class ExceptionInfo(object):
""" wraps sys.exc_info() objects and offers """ wraps sys.exc_info() objects and offers
help for navigating the traceback. help for navigating the traceback.
""" """
_striptext = ""
_assert_start_repr = ( _assert_start_repr = (
"AssertionError(u'assert " if _PY2 else "AssertionError('assert " "AssertionError(u'assert " if _PY2 else "AssertionError('assert "
) )
def __init__(self, tup=None, exprinfo=None): _excinfo = attr.ib()
import _pytest._code _striptext = attr.ib(default="")
_traceback = attr.ib(default=None)
if tup is None: @classmethod
tup = sys.exc_info() def from_current(cls, exprinfo=None):
if exprinfo is None and isinstance(tup[1], AssertionError): """returns an ExceptionInfo matching the current traceback
exprinfo = getattr(tup[1], "msg", None)
if exprinfo is None: .. warning::
exprinfo = py.io.saferepr(tup[1])
if exprinfo and exprinfo.startswith(self._assert_start_repr): Experimental API
self._striptext = "AssertionError: "
self._excinfo = tup
#: the exception class :param exprinfo: a text string helping to determine if we should
self.type = tup[0] strip ``AssertionError`` from the output, defaults
#: the exception instance to the exception message/``__str__()``
self.value = tup[1] """
#: the exception raw traceback tup = sys.exc_info()
self.tb = tup[2] _striptext = ""
#: the exception type name if exprinfo is None and isinstance(tup[1], AssertionError):
self.typename = self.type.__name__ exprinfo = getattr(tup[1], "msg", None)
#: the exception traceback (_pytest._code.Traceback instance) if exprinfo is None:
self.traceback = _pytest._code.Traceback(self.tb, excinfo=ref(self)) exprinfo = py.io.saferepr(tup[1])
if exprinfo and exprinfo.startswith(cls._assert_start_repr):
_striptext = "AssertionError: "
return cls(tup, _striptext)
@classmethod
def for_later(cls):
"""return an unfilled ExceptionInfo
"""
return cls(None)
@property
def type(self):
"""the exception class"""
return self._excinfo[0]
@property
def value(self):
"""the exception value"""
return self._excinfo[1]
@property
def tb(self):
"""the exception raw traceback"""
return self._excinfo[2]
@property
def typename(self):
"""the type name of the exception"""
return self.type.__name__
@property
def traceback(self):
"""the traceback"""
if self._traceback is None:
self._traceback = Traceback(self.tb, excinfo=ref(self))
return self._traceback
@traceback.setter
def traceback(self, value):
self._traceback = value
def __repr__(self): def __repr__(self):
try: if self._excinfo is None:
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback)) return "<ExceptionInfo for raises contextmanager>"
except AttributeError: return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
return "<ExceptionInfo uninitialized>"
def exconly(self, tryshort=False): def exconly(self, tryshort=False):
""" return the exception as a string """ return the exception as a string
@ -516,13 +557,11 @@ class ExceptionInfo(object):
return fmt.repr_excinfo(self) return fmt.repr_excinfo(self)
def __str__(self): def __str__(self):
try: if self._excinfo is None:
entry = self.traceback[-1]
except AttributeError:
return repr(self) return repr(self)
else: entry = self.traceback[-1]
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly()) loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
return str(loc) return str(loc)
def __unicode__(self): def __unicode__(self):
entry = self.traceback[-1] entry = self.traceback[-1]

View File

@ -51,6 +51,19 @@ else:
return ast.Call(a, b, c, None, None) return ast.Call(a, b, c, None, None)
def ast_Call_helper(func_name, *args, **kwargs):
"""
func_name: str
args: Iterable[ast.expr]
kwargs: Dict[str,ast.expr]
"""
return ast.Call(
ast.Name(func_name, ast.Load()),
list(args),
[ast.keyword(key, val) for key, val in kwargs.items()],
)
class AssertionRewritingHook(object): class AssertionRewritingHook(object):
"""PEP302 Import hook which rewrites asserts.""" """PEP302 Import hook which rewrites asserts."""
@ -265,11 +278,11 @@ class AssertionRewritingHook(object):
def _warn_already_imported(self, name): def _warn_already_imported(self, name):
from _pytest.warning_types import PytestWarning from _pytest.warning_types import PytestWarning
from _pytest.warnings import _issue_config_warning from _pytest.warnings import _issue_warning_captured
_issue_config_warning( _issue_warning_captured(
PytestWarning("Module already imported so cannot be rewritten: %s" % name), PytestWarning("Module already imported so cannot be rewritten: %s" % name),
self.config, self.config.hook,
stacklevel=5, stacklevel=5,
) )
@ -828,6 +841,13 @@ class AssertionRewriter(ast.NodeVisitor):
self.push_format_context() self.push_format_context()
# Rewrite assert into a bunch of statements. # Rewrite assert into a bunch of statements.
top_condition, explanation = self.visit(assert_.test) top_condition, explanation = self.visit(assert_.test)
# If in a test module, check if directly asserting None, in order to warn [Issue #3191]
if self.module_path is not None:
self.statements.append(
self.warn_about_none_ast(
top_condition, module_path=self.module_path, lineno=assert_.lineno
)
)
# Create failure message. # Create failure message.
body = self.on_failure body = self.on_failure
negation = ast.UnaryOp(ast.Not(), top_condition) negation = ast.UnaryOp(ast.Not(), top_condition)
@ -858,6 +878,33 @@ class AssertionRewriter(ast.NodeVisitor):
set_location(stmt, assert_.lineno, assert_.col_offset) set_location(stmt, assert_.lineno, assert_.col_offset)
return self.statements return self.statements
def warn_about_none_ast(self, node, module_path, lineno):
"""
Returns an AST issuing a warning if the value of node is `None`.
This is used to warn the user when asserting a function that asserts
internally already.
See issue #3191 for more details.
"""
# Using parse because it is different between py2 and py3.
AST_NONE = ast.parse("None").body[0].value
val_is_none = ast.Compare(node, [ast.Is()], [AST_NONE])
send_warning = ast.parse(
"""
from _pytest.warning_types import PytestWarning
from warnings import warn_explicit
warn_explicit(
PytestWarning('asserting the value None, please use "assert is None"'),
category=None,
filename={filename!r},
lineno={lineno},
)
""".format(
filename=module_path.strpath, lineno=lineno
)
).body
return ast.If(val_is_none, send_warning, [])
def visit_Name(self, name): def visit_Name(self, name):
# Display the repr of the name if it's a local variable or # Display the repr of the name if it's a local variable or
# _should_repr_global_name() thinks it's acceptable. # _should_repr_global_name() thinks it's acceptable.

View File

@ -122,6 +122,12 @@ def assertrepr_compare(config, op, left, right):
def isset(x): def isset(x):
return isinstance(x, (set, frozenset)) return isinstance(x, (set, frozenset))
def isdatacls(obj):
return getattr(obj, "__dataclass_fields__", None) is not None
def isattrs(obj):
return getattr(obj, "__attrs_attrs__", None) is not None
def isiterable(obj): def isiterable(obj):
try: try:
iter(obj) iter(obj)
@ -142,6 +148,9 @@ def assertrepr_compare(config, op, left, right):
explanation = _compare_eq_set(left, right, verbose) explanation = _compare_eq_set(left, right, verbose)
elif isdict(left) and isdict(right): elif isdict(left) and isdict(right):
explanation = _compare_eq_dict(left, right, verbose) explanation = _compare_eq_dict(left, right, verbose)
elif type(left) == type(right) and (isdatacls(left) or isattrs(left)):
type_fn = (isdatacls, isattrs)
explanation = _compare_eq_cls(left, right, verbose, type_fn)
if isiterable(left) and isiterable(right): if isiterable(left) and isiterable(right):
expl = _compare_eq_iterable(left, right, verbose) expl = _compare_eq_iterable(left, right, verbose)
if explanation is not None: if explanation is not None:
@ -155,7 +164,7 @@ def assertrepr_compare(config, op, left, right):
explanation = [ explanation = [
u"(pytest_assertion plugin: representation of details failed. " u"(pytest_assertion plugin: representation of details failed. "
u"Probably an object has a faulty __repr__.)", u"Probably an object has a faulty __repr__.)",
six.text_type(_pytest._code.ExceptionInfo()), six.text_type(_pytest._code.ExceptionInfo.from_current()),
] ]
if not explanation: if not explanation:
@ -315,6 +324,38 @@ def _compare_eq_dict(left, right, verbose=False):
return explanation return explanation
def _compare_eq_cls(left, right, verbose, type_fns):
isdatacls, isattrs = type_fns
if isdatacls(left):
all_fields = left.__dataclass_fields__
fields_to_check = [field for field, info in all_fields.items() if info.compare]
elif isattrs(left):
all_fields = left.__attrs_attrs__
fields_to_check = [field.name for field in all_fields if field.cmp]
same = []
diff = []
for field in fields_to_check:
if getattr(left, field) == getattr(right, field):
same.append(field)
else:
diff.append(field)
explanation = []
if same and verbose < 2:
explanation.append(u"Omitting %s identical items, use -vv to show" % len(same))
elif same:
explanation += [u"Matching attributes:"]
explanation += pprint.pformat(same).splitlines()
if diff:
explanation += [u"Differing attributes:"]
for field in diff:
explanation += [
(u"%s: %r != %r") % (field, getattr(left, field), getattr(right, field))
]
return explanation
def _notin_text(term, text, verbose=False): def _notin_text(term, text, verbose=False):
index = text.find(term) index = text.find(term)
head = text[:index] head = text[:index]

View File

@ -33,6 +33,13 @@ which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
See [the docs](https://docs.pytest.org/en/latest/cache.html) for more information. See [the docs](https://docs.pytest.org/en/latest/cache.html) for more information.
""" """
CACHEDIR_TAG_CONTENT = b"""\
Signature: 8a477f597d28d172789f06886806bc55
# This file is a cache directory tag created by pytest.
# For information about cache directory tags, see:
# http://www.bford.info/cachedir/spec.html
"""
@attr.s @attr.s
class Cache(object): class Cache(object):
@ -52,12 +59,12 @@ class Cache(object):
return resolve_from_str(config.getini("cache_dir"), config.rootdir) return resolve_from_str(config.getini("cache_dir"), config.rootdir)
def warn(self, fmt, **args): def warn(self, fmt, **args):
from _pytest.warnings import _issue_config_warning from _pytest.warnings import _issue_warning_captured
from _pytest.warning_types import PytestWarning from _pytest.warning_types import PytestWarning
_issue_config_warning( _issue_warning_captured(
PytestWarning(fmt.format(**args) if args else fmt), PytestWarning(fmt.format(**args) if args else fmt),
self._config, self._config.hook,
stacklevel=3, stacklevel=3,
) )
@ -140,6 +147,10 @@ class Cache(object):
msg = u"# Created by pytest automatically.\n*" msg = u"# Created by pytest automatically.\n*"
gitignore_path.write_text(msg, encoding="UTF-8") gitignore_path.write_text(msg, encoding="UTF-8")
cachedir_tag_path = self._cachedir.joinpath("CACHEDIR.TAG")
if not cachedir_tag_path.is_file():
cachedir_tag_path.write_bytes(CACHEDIR_TAG_CONTENT)
class LFPlugin(object): class LFPlugin(object):
""" Plugin which implements the --lf (run last-failing) option """ """ Plugin which implements the --lf (run last-failing) option """

View File

@ -773,9 +773,9 @@ def _py36_windowsconsoleio_workaround(stream):
f.line_buffering, f.line_buffering,
) )
sys.__stdin__ = sys.stdin = _reopen_stdio(sys.stdin, "rb") sys.stdin = _reopen_stdio(sys.stdin, "rb")
sys.__stdout__ = sys.stdout = _reopen_stdio(sys.stdout, "wb") sys.stdout = _reopen_stdio(sys.stdout, "wb")
sys.__stderr__ = sys.stderr = _reopen_stdio(sys.stderr, "wb") sys.stderr = _reopen_stdio(sys.stderr, "wb")
def _attempt_to_close_capture_file(f): def _attempt_to_close_capture_file(f):

View File

@ -45,11 +45,11 @@ MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
if _PY3: if _PY3:
from collections.abc import MutableMapping as MappingMixin from collections.abc import MutableMapping as MappingMixin
from collections.abc import Mapping, Sequence from collections.abc import Iterable, Mapping, Sequence, Sized
else: else:
# those raise DeprecationWarnings in Python >=3.7 # those raise DeprecationWarnings in Python >=3.7
from collections import MutableMapping as MappingMixin # noqa from collections import MutableMapping as MappingMixin # noqa
from collections import Mapping, Sequence # noqa from collections import Iterable, Mapping, Sequence, Sized # noqa
if sys.version_info >= (3, 4): if sys.version_info >= (3, 4):
@ -182,6 +182,18 @@ def get_default_arg_names(function):
) )
_non_printable_ascii_translate_table = {
i: u"\\x{:02x}".format(i) for i in range(128) if i not in range(32, 127)
}
_non_printable_ascii_translate_table.update(
{ord("\t"): u"\\t", ord("\r"): u"\\r", ord("\n"): u"\\n"}
)
def _translate_non_printable(s):
return s.translate(_non_printable_ascii_translate_table)
if _PY3: if _PY3:
STRING_TYPES = bytes, str STRING_TYPES = bytes, str
UNICODE_TYPES = six.text_type UNICODE_TYPES = six.text_type
@ -221,9 +233,10 @@ if _PY3:
""" """
if isinstance(val, bytes): if isinstance(val, bytes):
return _bytes_to_ascii(val) ret = _bytes_to_ascii(val)
else: else:
return val.encode("unicode_escape").decode("ascii") ret = val.encode("unicode_escape").decode("ascii")
return _translate_non_printable(ret)
else: else:
@ -241,11 +254,12 @@ else:
""" """
if isinstance(val, bytes): if isinstance(val, bytes):
try: try:
return val.encode("ascii") ret = val.decode("ascii")
except UnicodeDecodeError: except UnicodeDecodeError:
return val.encode("string-escape") ret = val.encode("string-escape").decode("ascii")
else: else:
return val.encode("unicode-escape") ret = val.encode("unicode-escape").decode("ascii")
return _translate_non_printable(ret)
class _PytestWrapper(object): class _PytestWrapper(object):
@ -375,7 +389,6 @@ else:
COLLECT_FAKEMODULE_ATTRIBUTES = ( COLLECT_FAKEMODULE_ATTRIBUTES = (
"Collector", "Collector",
"Module", "Module",
"Generator",
"Function", "Function",
"Instance", "Instance",
"Session", "Session",

View File

@ -26,11 +26,14 @@ from .exceptions import PrintHelp
from .exceptions import UsageError from .exceptions import UsageError
from .findpaths import determine_setup from .findpaths import determine_setup
from .findpaths import exists from .findpaths import exists
from _pytest import deprecated
from _pytest._code import ExceptionInfo from _pytest._code import ExceptionInfo
from _pytest._code import filter_traceback from _pytest._code import filter_traceback
from _pytest.compat import lru_cache 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 Skipped from _pytest.outcomes import Skipped
from _pytest.warning_types import PytestWarning
hookimpl = HookimplMarker("pytest") hookimpl = HookimplMarker("pytest")
hookspec = HookspecMarker("pytest") hookspec = HookspecMarker("pytest")
@ -173,12 +176,9 @@ def _prepareconfig(args=None, plugins=None):
elif isinstance(args, py.path.local): elif isinstance(args, py.path.local):
args = [str(args)] args = [str(args)]
elif not isinstance(args, (tuple, list)): elif not isinstance(args, (tuple, list)):
if not isinstance(args, str): msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})"
raise ValueError("not a string or argument list: %r" % (args,)) raise TypeError(msg.format(args, type(args)))
args = shlex.split(args, posix=sys.platform != "win32")
from _pytest import deprecated
warning = deprecated.MAIN_STR_ARGS
config = get_config() config = get_config()
pluginmanager = config.pluginmanager pluginmanager = config.pluginmanager
try: try:
@ -189,9 +189,9 @@ def _prepareconfig(args=None, plugins=None):
else: else:
pluginmanager.register(plugin) pluginmanager.register(plugin)
if warning: if warning:
from _pytest.warnings import _issue_config_warning from _pytest.warnings import _issue_warning_captured
_issue_config_warning(warning, config=config, stacklevel=4) _issue_warning_captured(warning, hook=config.hook, stacklevel=4)
return pluginmanager.hook.pytest_cmdline_parse( return pluginmanager.hook.pytest_cmdline_parse(
pluginmanager=pluginmanager, args=args pluginmanager=pluginmanager, args=args
) )
@ -245,14 +245,7 @@ class PytestPluginManager(PluginManager):
Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>` Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>`
instead. instead.
""" """
warning = dict( warnings.warn(deprecated.PLUGIN_MANAGER_ADDHOOKS, stacklevel=2)
code="I2",
fslocation=_pytest._code.getfslineno(sys._getframe(1)),
nodeid=None,
message="use pluginmanager.add_hookspecs instead of "
"deprecated addhooks() method.",
)
self._warn(warning)
return self.add_hookspecs(module_or_class) return self.add_hookspecs(module_or_class)
def parse_hookimpl_opts(self, plugin, name): def parse_hookimpl_opts(self, plugin, name):
@ -261,8 +254,8 @@ class PytestPluginManager(PluginManager):
# (see issue #1073) # (see issue #1073)
if not name.startswith("pytest_"): if not name.startswith("pytest_"):
return return
# ignore some historic special names which can not be hooks anyway # ignore names which can not be hooks
if name == "pytest_plugins" or name.startswith("pytest_funcarg__"): if name == "pytest_plugins":
return return
method = getattr(plugin, name) method = getattr(plugin, name)
@ -275,10 +268,14 @@ class PytestPluginManager(PluginManager):
# collect unmarked hooks as long as they have the `pytest_' prefix # collect unmarked hooks as long as they have the `pytest_' prefix
if opts is None and name.startswith("pytest_"): if opts is None and name.startswith("pytest_"):
opts = {} opts = {}
if opts is not None: if opts is not None:
# TODO: DeprecationWarning, people should use hookimpl
# https://github.com/pytest-dev/pytest/issues/4562
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))
opts.setdefault(name, hasattr(method, name) or name in known_marks)
return opts return opts
def parse_hookspec_opts(self, module_or_class, name): def parse_hookspec_opts(self, module_or_class, name):
@ -287,19 +284,27 @@ class PytestPluginManager(PluginManager):
) )
if opts is None: if opts is None:
method = getattr(module_or_class, name) method = getattr(module_or_class, name)
if name.startswith("pytest_"): if name.startswith("pytest_"):
# todo: deprecate hookspec hacks
# https://github.com/pytest-dev/pytest/issues/4562
known_marks = {m.name for m in getattr(method, "pytestmark", [])}
opts = { opts = {
"firstresult": hasattr(method, "firstresult"), "firstresult": hasattr(method, "firstresult")
"historic": hasattr(method, "historic"), or "firstresult" in known_marks,
"historic": hasattr(method, "historic")
or "historic" in known_marks,
} }
return opts return opts
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"]:
self._warn( warnings.warn(
"{} plugin has been merged into the core, " PytestWarning(
"please remove it from your requirements.".format( "{} plugin has been merged into the core, "
name.replace("_", "-") "please remove it from your requirements.".format(
name.replace("_", "-")
)
) )
) )
return return
@ -336,14 +341,6 @@ class PytestPluginManager(PluginManager):
) )
self._configured = True self._configured = True
def _warn(self, message):
kwargs = (
message
if isinstance(message, dict)
else {"code": "I1", "message": message, "fslocation": None, "nodeid": None}
)
self.hook.pytest_logwarning.call_historic(kwargs=kwargs)
# #
# internal API for local conftest plugin handling # internal API for local conftest plugin handling
# #
@ -443,11 +440,11 @@ class PytestPluginManager(PluginManager):
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
) )
warnings.warn_explicit( fail(
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST, PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST.format(
category=None, conftestpath, self._confcutdir
filename=str(conftestpath), ),
lineno=0, pytrace=False,
) )
except Exception: except Exception:
raise ConftestImportFailure(conftestpath, sys.exc_info()) raise ConftestImportFailure(conftestpath, sys.exc_info())
@ -470,9 +467,20 @@ class PytestPluginManager(PluginManager):
# #
def consider_preparse(self, args): def consider_preparse(self, args):
for opt1, opt2 in zip(args, args[1:]): i = 0
if opt1 == "-p": n = len(args)
self.consider_pluginarg(opt2) while i < n:
opt = args[i]
i += 1
if isinstance(opt, six.string_types):
if opt == "-p":
parg = args[i]
i += 1
elif opt.startswith("-p"):
parg = opt[2:]
else:
continue
self.consider_pluginarg(parg)
def consider_pluginarg(self, arg): def consider_pluginarg(self, arg):
if arg.startswith("no:"): if arg.startswith("no:"):
@ -507,7 +515,7 @@ class PytestPluginManager(PluginManager):
# "terminal" or "capture". Those plugins are registered under their # "terminal" or "capture". Those plugins are registered under their
# basename for historic purposes but must be imported with the # basename for historic purposes but must be imported with the
# _pytest prefix. # _pytest prefix.
assert isinstance(modname, (six.text_type, str)), ( assert isinstance(modname, six.string_types), (
"module name as text required, got %r" % modname "module name as text required, got %r" % modname
) )
modname = str(modname) modname = str(modname)
@ -531,7 +539,13 @@ class PytestPluginManager(PluginManager):
six.reraise(new_exc_type, new_exc, sys.exc_info()[2]) six.reraise(new_exc_type, new_exc, sys.exc_info()[2])
except Skipped as e: except Skipped as e:
self._warn("skipped plugin %r: %s" % ((modname, e.msg))) from _pytest.warnings import _issue_warning_captured
_issue_warning_captured(
PytestWarning("skipped plugin %r: %s" % (modname, e.msg)),
self.hook,
stacklevel=1,
)
else: else:
mod = sys.modules[importspec] mod = sys.modules[importspec]
self.register(mod, modname) self.register(mod, modname)
@ -606,16 +620,9 @@ class Config(object):
self._override_ini = () self._override_ini = ()
self._opt2dest = {} self._opt2dest = {}
self._cleanup = [] self._cleanup = []
self._warn = self.pluginmanager._warn
self.pluginmanager.register(self, "pytestconfig") self.pluginmanager.register(self, "pytestconfig")
self._configured = False self._configured = False
self.invocation_dir = py.path.local()
def do_setns(dic):
import pytest
setns(pytest, dic)
self.hook.pytest_namespace.call_historic(do_setns, {})
self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser)) self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
def add_cleanup(self, func): def add_cleanup(self, func):
@ -637,36 +644,6 @@ class Config(object):
fin = self._cleanup.pop() fin = self._cleanup.pop()
fin() fin()
def warn(self, code, message, fslocation=None, nodeid=None):
"""
.. deprecated:: 3.8
Use :py:func:`warnings.warn` or :py:func:`warnings.warn_explicit` directly instead.
Generate a warning for this test session.
"""
from _pytest.warning_types import RemovedInPytest4Warning
if isinstance(fslocation, (tuple, list)) and len(fslocation) > 2:
filename, lineno = fslocation[:2]
else:
filename = "unknown file"
lineno = 0
msg = "config.warn has been deprecated, use warnings.warn instead"
if nodeid:
msg = "{}: {}".format(nodeid, msg)
warnings.warn_explicit(
RemovedInPytest4Warning(msg),
category=None,
filename=filename,
lineno=lineno,
)
self.hook.pytest_logwarning.call_historic(
kwargs=dict(
code=code, message=message, fslocation=fslocation, nodeid=nodeid
)
)
def get_terminal_writer(self): def get_terminal_writer(self):
return self.pluginmanager.get_plugin("terminalreporter")._tw return self.pluginmanager.get_plugin("terminalreporter")._tw
@ -731,7 +708,6 @@ class Config(object):
self.rootdir, self.inifile, self.inicfg = r self.rootdir, self.inifile, self.inicfg = r
self._parser.extra_info["rootdir"] = self.rootdir self._parser.extra_info["rootdir"] = self.rootdir
self._parser.extra_info["inifile"] = self.inifile self._parser.extra_info["inifile"] = self.inifile
self.invocation_dir = py.path.local()
self._parser.addini("addopts", "extra command line options", "args") self._parser.addini("addopts", "extra command line options", "args")
self._parser.addini("minversion", "minimally required pytest version") self._parser.addini("minversion", "minimally required pytest version")
self._override_ini = ns.override_ini or () self._override_ini = ns.override_ini or ()
@ -822,7 +798,15 @@ class Config(object):
if ns.help or ns.version: if ns.help or ns.version:
# we don't want to prevent --help/--version to work # we don't want to prevent --help/--version to work
# so just let is pass and print a warning at the end # so just let is pass and print a warning at the end
self._warn("could not load initial conftests (%s)\n" % e.path) from _pytest.warnings import _issue_warning_captured
_issue_warning_captured(
PytestWarning(
"could not load initial conftests: {}".format(e.path)
),
self.hook,
stacklevel=2,
)
else: else:
raise raise

View File

@ -18,6 +18,8 @@ class Parser(object):
there's an error processing the command line arguments. there's an error processing the command line arguments.
""" """
prog = None
def __init__(self, usage=None, processopt=None): def __init__(self, usage=None, processopt=None):
self._anonymous = OptionGroup("custom options", parser=self) self._anonymous = OptionGroup("custom options", parser=self)
self._groups = [] self._groups = []
@ -82,7 +84,7 @@ class Parser(object):
def _getparser(self): def _getparser(self):
from _pytest._argcomplete import filescompleter from _pytest._argcomplete import filescompleter
optparser = MyOptionParser(self, self.extra_info) optparser = MyOptionParser(self, self.extra_info, prog=self.prog)
groups = self._groups + [self._anonymous] groups = self._groups + [self._anonymous]
for group in groups: for group in groups:
if group.options: if group.options:
@ -319,12 +321,13 @@ class OptionGroup(object):
class MyOptionParser(argparse.ArgumentParser): class MyOptionParser(argparse.ArgumentParser):
def __init__(self, parser, extra_info=None): def __init__(self, parser, extra_info=None, prog=None):
if not extra_info: if not extra_info:
extra_info = {} extra_info = {}
self._parser = parser self._parser = parser
argparse.ArgumentParser.__init__( argparse.ArgumentParser.__init__(
self, self,
prog=prog,
usage=parser._usage, usage=parser._usage,
add_help=False, add_help=False,
formatter_class=DropShorterLongHelpFormatter, formatter_class=DropShorterLongHelpFormatter,

View File

@ -3,6 +3,7 @@ import os
import py import py
from .exceptions import UsageError from .exceptions import UsageError
from _pytest.outcomes import fail
def exists(path, ignore=EnvironmentError): def exists(path, ignore=EnvironmentError):
@ -34,15 +35,10 @@ def getcfg(args, config=None):
iniconfig = py.iniconfig.IniConfig(p) iniconfig = py.iniconfig.IniConfig(p)
if "pytest" in iniconfig.sections: if "pytest" in iniconfig.sections:
if inibasename == "setup.cfg" and config is not None: if inibasename == "setup.cfg" and config is not None:
from _pytest.warnings import _issue_config_warning
from _pytest.warning_types import RemovedInPytest4Warning
_issue_config_warning( fail(
RemovedInPytest4Warning( CFG_PYTEST_SECTION.format(filename=inibasename),
CFG_PYTEST_SECTION.format(filename=inibasename) pytrace=False,
),
config=config,
stacklevel=2,
) )
return base, p, iniconfig["pytest"] return base, p, iniconfig["pytest"]
if ( if (
@ -112,40 +108,41 @@ def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None):
inicfg = iniconfig[section] inicfg = iniconfig[section]
if is_cfg_file and section == "pytest" and config is not None: if is_cfg_file and section == "pytest" and config is not None:
from _pytest.deprecated import CFG_PYTEST_SECTION from _pytest.deprecated import CFG_PYTEST_SECTION
from _pytest.warnings import _issue_config_warning
# TODO: [pytest] section in *.cfg files is deprecated. Need refactoring once fail(
# the deprecation expires. CFG_PYTEST_SECTION.format(filename=str(inifile)), pytrace=False
_issue_config_warning(
CFG_PYTEST_SECTION.format(filename=str(inifile)),
config,
stacklevel=2,
) )
break break
except KeyError: except KeyError:
inicfg = None inicfg = None
rootdir = get_common_ancestor(dirs) if rootdir_cmd_arg is None:
rootdir = get_common_ancestor(dirs)
else: else:
ancestor = get_common_ancestor(dirs) ancestor = get_common_ancestor(dirs)
rootdir, inifile, inicfg = getcfg([ancestor], config=config) rootdir, inifile, inicfg = getcfg([ancestor], config=config)
if rootdir is None: if rootdir is None and rootdir_cmd_arg is None:
for rootdir in ancestor.parts(reverse=True): for possible_rootdir in ancestor.parts(reverse=True):
if rootdir.join("setup.py").exists(): if possible_rootdir.join("setup.py").exists():
rootdir = possible_rootdir
break break
else: else:
rootdir, inifile, inicfg = getcfg(dirs, config=config) if dirs != [ancestor]:
rootdir, inifile, inicfg = getcfg(dirs, config=config)
if rootdir is None: if rootdir is None:
rootdir = get_common_ancestor([py.path.local(), ancestor]) if config is not None:
cwd = config.invocation_dir
else:
cwd = py.path.local()
rootdir = get_common_ancestor([cwd, ancestor])
is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/" is_fs_root = os.path.splitdrive(str(rootdir))[1] == "/"
if is_fs_root: if is_fs_root:
rootdir = ancestor rootdir = ancestor
if rootdir_cmd_arg: if rootdir_cmd_arg:
rootdir_abs_path = py.path.local(os.path.expandvars(rootdir_cmd_arg)) rootdir = py.path.local(os.path.expandvars(rootdir_cmd_arg))
if not os.path.isdir(str(rootdir_abs_path)): if not rootdir.isdir():
raise UsageError( raise UsageError(
"Directory '{}' not found. Check your '--rootdir' option.".format( "Directory '{}' not found. Check your '--rootdir' option.".format(
rootdir_abs_path rootdir
) )
) )
rootdir = rootdir_abs_path
return rootdir, inifile, inicfg or {} return rootdir, inifile, inicfg or {}

View File

@ -77,18 +77,21 @@ class pytestPDB(object):
_saved = [] _saved = []
@classmethod @classmethod
def set_trace(cls, set_break=True): def _init_pdb(cls, *args, **kwargs):
""" invoke PDB set_trace debugging, dropping any IO capturing. """ """ Initialize PDB debugging, dropping any IO capturing. """
import _pytest.config import _pytest.config
frame = sys._getframe().f_back
if cls._pluginmanager is not None: if cls._pluginmanager is not None:
capman = cls._pluginmanager.getplugin("capturemanager") capman = cls._pluginmanager.getplugin("capturemanager")
if capman: if capman:
capman.suspend_global_capture(in_=True) capman.suspend_global_capture(in_=True)
tw = _pytest.config.create_terminal_writer(cls._config) tw = _pytest.config.create_terminal_writer(cls._config)
tw.line() tw.line()
if capman and capman.is_globally_capturing(): # Handle header similar to pdb.set_trace in py37+.
header = kwargs.pop("header", None)
if header is not None:
tw.sep(">", header)
elif capman and capman.is_globally_capturing():
tw.sep(">", "PDB set_trace (IO-capturing turned off)") tw.sep(">", "PDB set_trace (IO-capturing turned off)")
else: else:
tw.sep(">", "PDB set_trace") tw.sep(">", "PDB set_trace")
@ -129,13 +132,18 @@ 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() _pdb = _PdbWrapper(**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() _pdb = cls._pdb_cls(**kwargs)
return _pdb
if set_break: @classmethod
_pdb.set_trace(frame) def set_trace(cls, *args, **kwargs):
"""Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing."""
frame = sys._getframe().f_back
_pdb = cls._init_pdb(*args, **kwargs)
_pdb.set_trace(frame)
class PdbInvoke(object): class PdbInvoke(object):
@ -161,9 +169,9 @@ class PdbTrace(object):
def _test_pytest_function(pyfuncitem): def _test_pytest_function(pyfuncitem):
pytestPDB.set_trace(set_break=False) _pdb = pytestPDB._init_pdb()
testfunction = pyfuncitem.obj testfunction = pyfuncitem.obj
pyfuncitem.obj = pdb.runcall pyfuncitem.obj = _pdb.runcall
if pyfuncitem._isyieldedfunction(): if pyfuncitem._isyieldedfunction():
arg_list = list(pyfuncitem._args) arg_list = list(pyfuncitem._args)
arg_list.insert(0, testfunction) arg_list.insert(0, testfunction)

View File

@ -14,66 +14,38 @@ from __future__ import print_function
from _pytest.warning_types import PytestDeprecationWarning from _pytest.warning_types import PytestDeprecationWarning
from _pytest.warning_types import RemovedInPytest4Warning from _pytest.warning_types import RemovedInPytest4Warning
from _pytest.warning_types import UnformattedWarning
MAIN_STR_ARGS = RemovedInPytest4Warning( YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored"
"passing a string to pytest.main() is deprecated, "
"pass a list of arguments instead."
)
YIELD_TESTS = RemovedInPytest4Warning(
"yield tests are deprecated, and scheduled to be removed in pytest 4.0"
)
CACHED_SETUP = RemovedInPytest4Warning( FIXTURE_FUNCTION_CALL = (
"cached_setup is deprecated and will be removed in a future release. " 'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n'
"Use standard fixture functions instead." "but are created automatically when test functions request them as parameters.\n"
) "See https://docs.pytest.org/en/latest/fixture.html for more information about fixtures, and\n"
"https://docs.pytest.org/en/latest/deprecations.html#calling-fixtures-directly about how to update your code."
COMPAT_PROPERTY = UnformattedWarning(
RemovedInPytest4Warning,
"usage of {owner}.{name} is deprecated, please use pytest.{name} instead",
)
CUSTOM_CLASS = UnformattedWarning(
RemovedInPytest4Warning,
'use of special named "{name}" objects in collectors of type "{type_name}" to '
"customize the created nodes is deprecated. "
"Use pytest_pycollect_makeitem(...) to create custom "
"collection nodes instead.",
)
FUNCARG_PREFIX = UnformattedWarning(
RemovedInPytest4Warning,
'{name}: declaring fixtures using "pytest_funcarg__" prefix is deprecated '
"and scheduled to be removed in pytest 4.0. "
"Please remove the prefix and use the @pytest.fixture decorator instead.",
)
FIXTURE_FUNCTION_CALL = UnformattedWarning(
RemovedInPytest4Warning,
'Fixture "{name}" called directly. Fixtures are not meant to be called directly, '
"are created automatically when test functions request them as parameters. "
"See https://docs.pytest.org/en/latest/fixture.html for more information.",
) )
FIXTURE_NAMED_REQUEST = PytestDeprecationWarning( FIXTURE_NAMED_REQUEST = PytestDeprecationWarning(
"'request' is a reserved name for fixtures and will raise an error in future versions" "'request' is a reserved name for fixtures and will raise an error in future versions"
) )
CFG_PYTEST_SECTION = UnformattedWarning( CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead."
RemovedInPytest4Warning,
"[pytest] section in {filename} files is deprecated, use [tool:pytest] instead.",
)
GETFUNCARGVALUE = RemovedInPytest4Warning( GETFUNCARGVALUE = RemovedInPytest4Warning(
"getfuncargvalue is deprecated, use getfixturevalue" "getfuncargvalue is deprecated, use getfixturevalue"
) )
RESULT_LOG = RemovedInPytest4Warning( RAISES_MESSAGE_PARAMETER = PytestDeprecationWarning(
"--result-log is deprecated and scheduled for removal in pytest 4.0.\n" "The 'message' parameter is deprecated.\n"
"See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information." "(did you mean to use `match='some regex'` to check the exception message?)\n"
"Please comment on https://github.com/pytest-dev/pytest/issues/3974 "
"if you have concerns about removal of this parameter."
)
RESULT_LOG = PytestDeprecationWarning(
"--result-log is deprecated and scheduled for removal in pytest 5.0.\n"
"See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information."
) )
MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning( MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
@ -82,42 +54,36 @@ MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
"Docs: https://docs.pytest.org/en/latest/mark.html#updating-code" "Docs: https://docs.pytest.org/en/latest/mark.html#updating-code"
) )
MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning( RAISES_EXEC = PytestDeprecationWarning(
"Applying marks directly to parameters is deprecated," "raises(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly\n\n"
" please use pytest.param(..., marks=...) instead.\n" "See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec"
"For more details, see: https://docs.pytest.org/en/latest/parametrize.html" )
WARNS_EXEC = PytestDeprecationWarning(
"warns(..., 'code(as_a_string)') is deprecated, use the context manager form or use `exec()` directly.\n\n"
"See https://docs.pytest.org/en/latest/deprecations.html#raises-warns-exec"
) )
NODE_WARN = RemovedInPytest4Warning( PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = (
"Node.warn(code, message) form has been deprecated, use Node.warn(warning_instance) instead." "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported "
)
RECORD_XML_PROPERTY = RemovedInPytest4Warning(
'Fixture renamed from "record_xml_property" to "record_property" as user '
"properties are now available to all reporters.\n"
'"record_xml_property" is now deprecated.'
)
COLLECTOR_MAKEITEM = RemovedInPytest4Warning(
"pycollector makeitem was removed as it is an accidentially leaked internal api"
)
METAFUNC_ADD_CALL = RemovedInPytest4Warning(
"Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.\n"
"Please use Metafunc.parametrize instead."
)
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = RemovedInPytest4Warning(
"Defining pytest_plugins in a non-top-level conftest is deprecated, "
"because it affects the entire directory tree in a non-explicit way.\n" "because it affects the entire directory tree in a non-explicit way.\n"
"Please move it to the top level conftest file instead." " {}\n"
"Please move it to a top level conftest file at the rootdir:\n"
" {}\n"
"For more information, visit:\n"
" https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files"
) )
PYTEST_NAMESPACE = RemovedInPytest4Warning( PYTEST_CONFIG_GLOBAL = PytestDeprecationWarning(
"pytest_namespace is deprecated and will be removed soon" "the `pytest.config` global is deprecated. Please use `request.config` "
"or `pytest_configure` (if you're a pytest plugin) instead."
) )
PYTEST_ENSURETEMP = RemovedInPytest4Warning( PYTEST_ENSURETEMP = RemovedInPytest4Warning(
"pytest/tmpdir_factory.ensuretemp is deprecated, \n" "pytest/tmpdir_factory.ensuretemp is deprecated, \n"
"please use the tmp_path fixture or tmp_path_factory.mktemp" "please use the tmp_path fixture or tmp_path_factory.mktemp"
) )
PYTEST_LOGWARNING = PytestDeprecationWarning(
"pytest_logwarning is deprecated, no longer being called, and will be removed soon\n"
"please use pytest_warning_captured instead"
)

View File

@ -38,8 +38,6 @@ from _pytest.deprecated import FIXTURE_NAMED_REQUEST
from _pytest.outcomes import fail from _pytest.outcomes import fail
from _pytest.outcomes import TEST_OUTCOME from _pytest.outcomes import TEST_OUTCOME
FIXTURE_MSG = 'fixtures cannot have "pytest_funcarg__" prefix and be decorated with @pytest.fixture:\n{}'
@attr.s(frozen=True) @attr.s(frozen=True)
class PseudoFixtureDef(object): class PseudoFixtureDef(object):
@ -469,43 +467,6 @@ class FixtureRequest(FuncargnamesCompatAttr):
if argname not in item.funcargs: if argname not in item.funcargs:
item.funcargs[argname] = self.getfixturevalue(argname) item.funcargs[argname] = self.getfixturevalue(argname)
def cached_setup(self, setup, teardown=None, scope="module", extrakey=None):
""" (deprecated) Return a testing resource managed by ``setup`` &
``teardown`` calls. ``scope`` and ``extrakey`` determine when the
``teardown`` function will be called so that subsequent calls to
``setup`` would recreate the resource. With pytest-2.3 you often
do not need ``cached_setup()`` as you can directly declare a scope
on a fixture function and register a finalizer through
``request.addfinalizer()``.
:arg teardown: function receiving a previously setup resource.
:arg setup: a no-argument function creating a resource.
:arg scope: a string value out of ``function``, ``class``, ``module``
or ``session`` indicating the caching lifecycle of the resource.
:arg extrakey: added to internal caching key of (funcargname, scope).
"""
from _pytest.deprecated import CACHED_SETUP
warnings.warn(CACHED_SETUP, stacklevel=2)
if not hasattr(self.config, "_setupcache"):
self.config._setupcache = {} # XXX weakref?
cachekey = (self.fixturename, self._getscopeitem(scope), extrakey)
cache = self.config._setupcache
try:
val = cache[cachekey]
except KeyError:
self._check_scope(self.fixturename, self.scope, scope)
val = setup()
cache[cachekey] = val
if teardown is not None:
def finalizer():
del cache[cachekey]
teardown(val)
self._addfinalizer(finalizer, scope=scope)
return val
def getfixturevalue(self, argname): def getfixturevalue(self, argname):
""" Dynamically run a named fixture function. """ Dynamically run a named fixture function.
@ -605,8 +566,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
) )
fail(msg, pytrace=False) fail(msg, pytrace=False)
else: else:
# indices might not be set if old-style metafunc.addcall() was used param_index = funcitem.callspec.indices[argname]
param_index = funcitem.callspec.indices.get(argname, 0)
# if a parametrize invocation set a scope it will override # if a parametrize invocation set a scope it will override
# the static scope defined with the fixture function # the static scope defined with the fixture function
paramscopenum = funcitem.callspec._arg2scopenum.get(argname) paramscopenum = funcitem.callspec._arg2scopenum.get(argname)
@ -982,34 +942,17 @@ def _ensure_immutable_ids(ids):
return tuple(ids) return tuple(ids)
def wrap_function_to_warning_if_called_directly(function, fixture_marker): def wrap_function_to_error_out_if_called_directly(function, fixture_marker):
"""Wrap the given fixture function so we can issue warnings about it being called directly, instead of """Wrap the given fixture function so we can raise an error about it being called directly,
used as an argument in a test function. instead of used as an argument in a test function.
""" """
is_yield_function = is_generator(function) message = FIXTURE_FUNCTION_CALL.format(
warning = FIXTURE_FUNCTION_CALL.format(
name=fixture_marker.name or function.__name__ name=fixture_marker.name or function.__name__
) )
if is_yield_function: @six.wraps(function)
def result(*args, **kwargs):
@functools.wraps(function) fail(message, pytrace=False)
def result(*args, **kwargs):
__tracebackhide__ = True
warnings.warn(warning, stacklevel=3)
for x in function(*args, **kwargs):
yield x
else:
@functools.wraps(function)
def result(*args, **kwargs):
__tracebackhide__ = True
warnings.warn(warning, stacklevel=3)
return function(*args, **kwargs)
if six.PY2:
result.__wrapped__ = function
# keep reference to the original function in our own custom attribute so we don't unwrap # keep reference to the original function in our own custom attribute so we don't unwrap
# further than this point and lose useful wrappings like @mock.patch (#3774) # further than this point and lose useful wrappings like @mock.patch (#3774)
@ -1035,7 +978,7 @@ class FixtureFunctionMarker(object):
"fixture is being applied more than once to the same function" "fixture is being applied more than once to the same function"
) )
function = wrap_function_to_warning_if_called_directly(function, self) function = wrap_function_to_error_out_if_called_directly(function, self)
name = self.name or function.__name__ name = self.name or function.__name__
if name == "request": if name == "request":
@ -1155,7 +1098,6 @@ class FixtureManager(object):
by a lookup of their FuncFixtureInfo. by a lookup of their FuncFixtureInfo.
""" """
_argprefix = "pytest_funcarg__"
FixtureLookupError = FixtureLookupError FixtureLookupError = FixtureLookupError
FixtureLookupErrorRepr = FixtureLookupErrorRepr FixtureLookupErrorRepr = FixtureLookupErrorRepr
@ -1265,19 +1207,20 @@ class FixtureManager(object):
if faclist: if faclist:
fixturedef = faclist[-1] fixturedef = faclist[-1]
if fixturedef.params is not None: if fixturedef.params is not None:
parametrize_func = getattr(metafunc.function, "parametrize", None) markers = list(metafunc.definition.iter_markers("parametrize"))
if parametrize_func is not None: for parametrize_mark in markers:
parametrize_func = parametrize_func.combined if "argnames" in parametrize_mark.kwargs:
func_params = getattr(parametrize_func, "args", [[None]]) argnames = parametrize_mark.kwargs["argnames"]
func_kwargs = getattr(parametrize_func, "kwargs", {}) else:
# skip directly parametrized arguments argnames = parametrize_mark.args[0]
if "argnames" in func_kwargs:
argnames = parametrize_func.kwargs["argnames"] if not isinstance(argnames, (tuple, list)):
argnames = [
x.strip() for x in argnames.split(",") if x.strip()
]
if argname in argnames:
break
else: else:
argnames = func_params[0]
if not isinstance(argnames, (tuple, list)):
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
if argname not in func_params and argname not in argnames:
metafunc.parametrize( metafunc.parametrize(
argname, argname,
fixturedef.params, fixturedef.params,
@ -1293,8 +1236,6 @@ class FixtureManager(object):
items[:] = reorder_items(items) items[:] = reorder_items(items)
def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False): def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
from _pytest import deprecated
if nodeid is not NOTSET: if nodeid is not NOTSET:
holderobj = node_or_obj holderobj = node_or_obj
else: else:
@ -1303,44 +1244,20 @@ class FixtureManager(object):
if holderobj in self._holderobjseen: if holderobj in self._holderobjseen:
return return
from _pytest.nodes import _CompatProperty
self._holderobjseen.add(holderobj) self._holderobjseen.add(holderobj)
autousenames = [] autousenames = []
for name in dir(holderobj): for name in dir(holderobj):
# The attribute can be an arbitrary descriptor, so the attribute # The attribute can be an arbitrary descriptor, so the attribute
# access below can raise. safe_getatt() ignores such exceptions. # access below can raise. safe_getatt() ignores such exceptions.
maybe_property = safe_getattr(type(holderobj), name, None)
if isinstance(maybe_property, _CompatProperty):
# deprecated
continue
obj = safe_getattr(holderobj, name, None) obj = safe_getattr(holderobj, name, None)
marker = getfixturemarker(obj) marker = getfixturemarker(obj)
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style) if not isinstance(marker, FixtureFunctionMarker):
# or are "@pytest.fixture" marked
if marker is None:
if not name.startswith(self._argprefix):
continue
if not callable(obj):
continue
marker = defaultfuncargprefixmarker
filename, lineno = getfslineno(obj)
warnings.warn_explicit(
deprecated.FUNCARG_PREFIX.format(name=name),
category=None,
filename=str(filename),
lineno=lineno + 1,
)
name = name[len(self._argprefix) :]
elif not isinstance(marker, FixtureFunctionMarker):
# magic globals with __getattr__ might have got us a wrong # magic globals with __getattr__ might have got us a wrong
# fixture attribute # fixture attribute
continue continue
else:
if marker.name: if marker.name:
name = marker.name name = marker.name
assert not name.startswith(self._argprefix), FIXTURE_MSG.format(name)
# during fixture definition we wrap the original fixture function # during fixture definition we wrap the original fixture function
# to issue a warning if called directly, so here we unwrap it in order to not emit the warning # to issue a warning if called directly, so here we unwrap it in order to not emit the warning

View File

@ -1,7 +1,7 @@
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """ """ hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
from pluggy import HookspecMarker from pluggy import HookspecMarker
from .deprecated import PYTEST_NAMESPACE from _pytest.deprecated import PYTEST_LOGWARNING
hookspec = HookspecMarker("pytest") hookspec = HookspecMarker("pytest")
@ -24,32 +24,6 @@ def pytest_addhooks(pluginmanager):
""" """
@hookspec(historic=True, warn_on_impl=PYTEST_NAMESPACE)
def pytest_namespace():
"""
return dict of name->object to be made globally available in
the pytest namespace.
This hook is called at plugin registration time.
.. note::
This hook is incompatible with ``hookwrapper=True``.
.. warning::
This hook has been **deprecated** and will be removed in pytest 4.0.
Plugins whose users depend on the current namespace functionality should prepare to migrate to a
namespace they actually own.
To support the migration it's suggested to trigger ``DeprecationWarnings`` for objects they put into the
pytest namespace.
A stopgap measure to avoid the warning is to monkeypatch the ``pytest`` module, but just as the
``pytest_namespace`` hook this should be seen as a temporary measure to be removed in future versions after
an appropriate transition period.
"""
@hookspec(historic=True) @hookspec(historic=True)
def pytest_plugin_registered(plugin, manager): def pytest_plugin_registered(plugin, manager):
""" a new pytest plugin got registered. """ a new pytest plugin got registered.
@ -524,7 +498,7 @@ def pytest_terminal_summary(terminalreporter, exitstatus):
""" """
@hookspec(historic=True) @hookspec(historic=True, warn_on_impl=PYTEST_LOGWARNING)
def pytest_logwarning(message, code, nodeid, fslocation): def pytest_logwarning(message, code, nodeid, fslocation):
""" """
.. deprecated:: 3.8 .. deprecated:: 3.8

View File

@ -263,16 +263,6 @@ def record_property(request):
return append_property return append_property
@pytest.fixture
def record_xml_property(record_property, request):
"""(Deprecated) use record_property."""
from _pytest import deprecated
request.node.warn(deprecated.RECORD_XML_PROPERTY)
return record_property
@pytest.fixture @pytest.fixture
def record_xml_attribute(request): def record_xml_attribute(request):
"""Add extra xml attributes to the tag for the calling test. """Add extra xml attributes to the tag for the calling test.
@ -323,6 +313,11 @@ def pytest_addoption(parser):
"one of no|system-out|system-err", "one of no|system-out|system-err",
default="no", default="no",
) # choices=['no', 'stdout', 'stderr']) ) # choices=['no', 'stdout', 'stderr'])
parser.addini(
"junit_duration_report",
"Duration time to report: one of total|call",
default="total",
) # choices=['total', 'call'])
def pytest_configure(config): def pytest_configure(config):
@ -334,6 +329,7 @@ def pytest_configure(config):
config.option.junitprefix, config.option.junitprefix,
config.getini("junit_suite_name"), config.getini("junit_suite_name"),
config.getini("junit_logging"), config.getini("junit_logging"),
config.getini("junit_duration_report"),
) )
config.pluginmanager.register(config._xml) config.pluginmanager.register(config._xml)
@ -361,12 +357,20 @@ def mangle_test_address(address):
class LogXML(object): class LogXML(object):
def __init__(self, logfile, prefix, suite_name="pytest", logging="no"): def __init__(
self,
logfile,
prefix,
suite_name="pytest",
logging="no",
report_duration="total",
):
logfile = os.path.expanduser(os.path.expandvars(logfile)) logfile = os.path.expanduser(os.path.expandvars(logfile))
self.logfile = os.path.normpath(os.path.abspath(logfile)) self.logfile = os.path.normpath(os.path.abspath(logfile))
self.prefix = prefix self.prefix = prefix
self.suite_name = suite_name self.suite_name = suite_name
self.logging = logging self.logging = logging
self.report_duration = report_duration
self.stats = dict.fromkeys(["error", "passed", "failure", "skipped"], 0) self.stats = dict.fromkeys(["error", "passed", "failure", "skipped"], 0)
self.node_reporters = {} # nodeid -> _NodeReporter self.node_reporters = {} # nodeid -> _NodeReporter
self.node_reporters_ordered = [] self.node_reporters_ordered = []
@ -500,8 +504,9 @@ class LogXML(object):
"""accumulates total duration for nodeid from given report and updates """accumulates total duration for nodeid from given report and updates
the Junit.testcase with the new total if already created. the Junit.testcase with the new total if already created.
""" """
reporter = self.node_reporter(report) if self.report_duration == "total" or report.when == self.report_duration:
reporter.duration += getattr(report, "duration", 0.0) reporter = self.node_reporter(report)
reporter.duration += getattr(report, "duration", 0.0)
def pytest_collectreport(self, report): def pytest_collectreport(self, report):
if not report.passed: if not report.passed:

View File

@ -8,6 +8,7 @@ import functools
import os import os
import pkgutil import pkgutil
import sys import sys
import warnings
import attr import attr
import py import py
@ -18,6 +19,7 @@ from _pytest import nodes
from _pytest.config import directory_arg from _pytest.config import directory_arg
from _pytest.config import hookimpl from _pytest.config import hookimpl
from _pytest.config import UsageError from _pytest.config import UsageError
from _pytest.deprecated import PYTEST_CONFIG_GLOBAL
from _pytest.outcomes import exit from _pytest.outcomes import exit
from _pytest.runner import collect_one_node from _pytest.runner import collect_one_node
@ -167,8 +169,24 @@ def pytest_addoption(parser):
) )
class _ConfigDeprecated(object):
def __init__(self, config):
self.__dict__["_config"] = config
def __getattr__(self, attr):
warnings.warn(PYTEST_CONFIG_GLOBAL, stacklevel=2)
return getattr(self._config, attr)
def __setattr__(self, attr, val):
warnings.warn(PYTEST_CONFIG_GLOBAL, stacklevel=2)
return setattr(self._config, attr, val)
def __repr__(self):
return "{}({!r})".format(type(self).__name__, self._config)
def pytest_configure(config): def pytest_configure(config):
__import__("pytest").config = config # compatibility __import__("pytest").config = _ConfigDeprecated(config) # compatibility
def wrap_session(config, doit): def wrap_session(config, doit):
@ -187,8 +205,8 @@ def wrap_session(config, doit):
raise raise
except Failed: except Failed:
session.exitstatus = EXIT_TESTSFAILED session.exitstatus = EXIT_TESTSFAILED
except KeyboardInterrupt: except (KeyboardInterrupt, exit.Exception):
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo.from_current()
exitstatus = EXIT_INTERRUPTED exitstatus = EXIT_INTERRUPTED
if initstate <= 2 and isinstance(excinfo.value, exit.Exception): if initstate <= 2 and isinstance(excinfo.value, exit.Exception):
sys.stderr.write("{}: {}\n".format(excinfo.typename, excinfo.value.msg)) sys.stderr.write("{}: {}\n".format(excinfo.typename, excinfo.value.msg))
@ -197,7 +215,7 @@ def wrap_session(config, doit):
config.hook.pytest_keyboard_interrupt(excinfo=excinfo) config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
session.exitstatus = exitstatus session.exitstatus = exitstatus
except: # noqa except: # noqa
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo.from_current()
config.notify_exception(excinfo, config.option) config.notify_exception(excinfo, config.option)
session.exitstatus = EXIT_INTERNALERROR session.exitstatus = EXIT_INTERNALERROR
if excinfo.errisinstance(SystemExit): if excinfo.errisinstance(SystemExit):

View File

@ -11,19 +11,10 @@ from .structures import Mark
from .structures import MARK_GEN from .structures import MARK_GEN
from .structures import MarkDecorator from .structures import MarkDecorator
from .structures import MarkGenerator from .structures import MarkGenerator
from .structures import MarkInfo
from .structures import ParameterSet from .structures import ParameterSet
from .structures import transfer_markers
from _pytest.config import UsageError from _pytest.config import UsageError
__all__ = [ __all__ = ["Mark", "MarkDecorator", "MarkGenerator", "get_empty_parameterset_mark"]
"Mark",
"MarkInfo",
"MarkDecorator",
"MarkGenerator",
"transfer_markers",
"get_empty_parameterset_mark",
]
def param(*values, **kw): def param(*values, **kw):

View File

@ -1,17 +1,15 @@
import inspect import inspect
import warnings import warnings
from collections import namedtuple from collections import namedtuple
from functools import reduce
from operator import attrgetter from operator import attrgetter
import attr import attr
from six.moves import map import six
from ..compat import ascii_escaped
from ..compat import getfslineno from ..compat import getfslineno
from ..compat import MappingMixin from ..compat import MappingMixin
from ..compat import NOTSET from ..compat import NOTSET
from ..deprecated import MARK_INFO_ATTRIBUTE
from ..deprecated import MARK_PARAMETERSET_UNPACKING
from _pytest.outcomes import fail from _pytest.outcomes import fail
@ -70,46 +68,33 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
else: else:
assert isinstance(marks, (tuple, list, set)) assert isinstance(marks, (tuple, list, set))
def param_extract_id(id=None): id_ = kw.pop("id", None)
return id if id_ is not None:
if not isinstance(id_, six.string_types):
id_ = param_extract_id(**kw) raise TypeError(
"Expected id to be a string, got {}: {!r}".format(type(id_), id_)
)
id_ = ascii_escaped(id_)
return cls(values, marks, id_) return cls(values, marks, id_)
@classmethod @classmethod
def extract_from(cls, parameterset, belonging_definition, legacy_force_tuple=False): def extract_from(cls, parameterset, force_tuple=False):
""" """
:param parameterset: :param parameterset:
a legacy style parameterset that may or may not be a tuple, a legacy style parameterset that may or may not be a tuple,
and may or may not be wrapped into a mess of mark objects and may or may not be wrapped into a mess of mark objects
:param legacy_force_tuple: :param force_tuple:
enforce tuple wrapping so single argument tuple values enforce tuple wrapping so single argument tuple values
don't get decomposed and break tests don't get decomposed and break tests
:param belonging_definition: the item that we will be extracting the parameters from.
""" """
if isinstance(parameterset, cls): if isinstance(parameterset, cls):
return parameterset return parameterset
if not isinstance(parameterset, MarkDecorator) and legacy_force_tuple: if force_tuple:
return cls.param(parameterset) return cls.param(parameterset)
else:
newmarks = [] return cls(parameterset, marks=[], id=None)
argval = parameterset
while isinstance(argval, MarkDecorator):
newmarks.append(
MarkDecorator(Mark(argval.markname, argval.args[:-1], argval.kwargs))
)
argval = argval.args[-1]
assert not isinstance(argval, ParameterSet)
if legacy_force_tuple:
argval = (argval,)
if newmarks and belonging_definition is not None:
belonging_definition.warn(MARK_PARAMETERSET_UNPACKING)
return cls(argval, marks=newmarks, id=None)
@classmethod @classmethod
def _for_parametrize(cls, argnames, argvalues, func, config, function_definition): def _for_parametrize(cls, argnames, argvalues, func, config, function_definition):
@ -119,12 +104,7 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
else: else:
force_tuple = False force_tuple = False
parameters = [ parameters = [
ParameterSet.extract_from( ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues
x,
legacy_force_tuple=force_tuple,
belonging_definition=function_definition,
)
for x in argvalues
] ]
del argvalues del argvalues
@ -132,11 +112,21 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
# check all parameter sets have the correct number of values # check all parameter sets have the correct number of values
for param in parameters: for param in parameters:
if len(param.values) != len(argnames): if len(param.values) != len(argnames):
raise ValueError( msg = (
'In "parametrize" the number of values ({}) must be ' '{nodeid}: in "parametrize" the number of names ({names_len}):\n'
"equal to the number of names ({})".format( " {names}\n"
param.values, argnames "must be equal to the number of values ({values_len}):\n"
) " {values}"
)
fail(
msg.format(
nodeid=function_definition.nodeid,
values=param.values,
names=argnames,
names_len=len(argnames),
values_len=len(param.values),
),
pytrace=False,
) )
else: else:
# empty parameter set (likely computed at runtime): create a single # empty parameter set (likely computed at runtime): create a single
@ -240,11 +230,7 @@ class MarkDecorator(object):
func = args[0] func = args[0]
is_class = inspect.isclass(func) is_class = inspect.isclass(func)
if len(args) == 1 and (istestfunc(func) or is_class): if len(args) == 1 and (istestfunc(func) or is_class):
if is_class: store_mark(func, self.mark)
store_mark(func, self.mark)
else:
store_legacy_markinfo(func, self.mark)
store_mark(func, self.mark)
return func return func
return self.with_args(*args, **kwargs) return self.with_args(*args, **kwargs)
@ -266,7 +252,13 @@ def normalize_mark_list(mark_list):
:type mark_list: List[Union[Mark, Markdecorator]] :type mark_list: List[Union[Mark, Markdecorator]]
:rtype: List[Mark] :rtype: List[Mark]
""" """
return [getattr(mark, "mark", mark) for mark in mark_list] # unpack MarkDecorator extracted = [
getattr(mark, "mark", mark) for mark in mark_list
] # unpack MarkDecorator
for mark in extracted:
if not isinstance(mark, Mark):
raise TypeError("got {!r} instead of Mark".format(mark))
return [x for x in extracted if isinstance(x, Mark)]
def store_mark(obj, mark): def store_mark(obj, mark):
@ -279,90 +271,6 @@ def store_mark(obj, mark):
obj.pytestmark = get_unpacked_marks(obj) + [mark] obj.pytestmark = get_unpacked_marks(obj) + [mark]
def store_legacy_markinfo(func, mark):
"""create the legacy MarkInfo objects and put them onto the function
"""
if not isinstance(mark, Mark):
raise TypeError("got {mark!r} instead of a Mark".format(mark=mark))
holder = getattr(func, mark.name, None)
if holder is None:
holder = MarkInfo.for_mark(mark)
setattr(func, mark.name, holder)
elif isinstance(holder, MarkInfo):
holder.add_mark(mark)
def transfer_markers(funcobj, cls, mod):
"""
this function transfers class level markers and module level markers
into function level markinfo objects
this is the main reason why marks are so broken
the resolution will involve phasing out function level MarkInfo objects
"""
for obj in (cls, mod):
for mark in get_unpacked_marks(obj):
if not _marked(funcobj, mark):
store_legacy_markinfo(funcobj, mark)
def _marked(func, mark):
""" Returns True if :func: is already marked with :mark:, False otherwise.
This can happen if marker is applied to class and the test file is
invoked more than once.
"""
try:
func_mark = getattr(func, getattr(mark, "combined", mark).name)
except AttributeError:
return False
return any(mark == info.combined for info in func_mark)
@attr.s(repr=False)
class MarkInfo(object):
""" Marking object created by :class:`MarkDecorator` instances. """
_marks = attr.ib(converter=list)
@_marks.validator
def validate_marks(self, attribute, value):
for item in value:
if not isinstance(item, Mark):
raise ValueError(
"MarkInfo expects Mark instances, got {!r} ({!r})".format(
item, type(item)
)
)
combined = attr.ib(
repr=False,
default=attr.Factory(
lambda self: reduce(Mark.combined_with, self._marks), takes_self=True
),
)
name = alias("combined.name", warning=MARK_INFO_ATTRIBUTE)
args = alias("combined.args", warning=MARK_INFO_ATTRIBUTE)
kwargs = alias("combined.kwargs", warning=MARK_INFO_ATTRIBUTE)
@classmethod
def for_mark(cls, mark):
return cls([mark])
def __repr__(self):
return "<MarkInfo {!r}>".format(self.combined)
def add_mark(self, mark):
""" add a MarkInfo with the given args and kwargs. """
self._marks.append(mark)
self.combined = self.combined.combined_with(mark)
def __iter__(self):
""" yield MarkInfo objects each relating to a marking-call. """
return map(MarkInfo.for_mark, self._marks)
class MarkGenerator(object): class MarkGenerator(object):
""" Factory for :class:`MarkDecorator` objects - exposed as """ Factory for :class:`MarkDecorator` objects - exposed as
a ``pytest.mark`` singleton instance. Example:: a ``pytest.mark`` singleton instance. Example::

View File

@ -5,13 +5,11 @@ from __future__ import print_function
import os import os
import warnings import warnings
import attr
import py import py
import six import six
import _pytest._code import _pytest._code
from _pytest.compat import getfslineno from _pytest.compat import getfslineno
from _pytest.mark.structures import MarkInfo
from _pytest.mark.structures import NodeKeywords from _pytest.mark.structures import NodeKeywords
from _pytest.outcomes import fail from _pytest.outcomes import fail
@ -56,22 +54,6 @@ def ischildnode(baseid, nodeid):
return node_parts[: len(base_parts)] == base_parts return node_parts[: len(base_parts)] == base_parts
@attr.s
class _CompatProperty(object):
name = attr.ib()
def __get__(self, obj, owner):
if obj is None:
return self
from _pytest.deprecated import COMPAT_PROPERTY
warnings.warn(
COMPAT_PROPERTY.format(name=self.name, owner=owner.__name__), stacklevel=2
)
return getattr(__import__("pytest"), self.name)
class Node(object): class Node(object):
""" base class for Collector and Item the test collection tree. """ base class for Collector and Item the test collection tree.
Collector subclasses have children, Items are terminal nodes.""" Collector subclasses have children, Items are terminal nodes."""
@ -119,95 +101,10 @@ class Node(object):
""" fspath sensitive hook proxy used to call pytest hooks""" """ fspath sensitive hook proxy used to call pytest hooks"""
return self.session.gethookproxy(self.fspath) return self.session.gethookproxy(self.fspath)
Module = _CompatProperty("Module")
Class = _CompatProperty("Class")
Instance = _CompatProperty("Instance")
Function = _CompatProperty("Function")
File = _CompatProperty("File")
Item = _CompatProperty("Item")
def _getcustomclass(self, name):
maybe_compatprop = getattr(type(self), name)
if isinstance(maybe_compatprop, _CompatProperty):
return getattr(__import__("pytest"), name)
else:
from _pytest.deprecated import CUSTOM_CLASS
cls = getattr(self, name)
self.warn(CUSTOM_CLASS.format(name=name, type_name=type(self).__name__))
return cls
def __repr__(self): def __repr__(self):
return "<%s %r>" % (self.__class__.__name__, getattr(self, "name", None)) return "<%s %s>" % (self.__class__.__name__, getattr(self, "name", None))
def warn(self, _code_or_warning=None, message=None, code=None): def warn(self, warning):
"""Issue a warning for this item.
Warnings will be displayed after the test session, unless explicitly suppressed.
This can be called in two forms:
**Warning instance**
This was introduced in pytest 3.8 and uses the standard warning mechanism to issue warnings.
.. code-block:: python
node.warn(PytestWarning("some message"))
The warning instance must be a subclass of :class:`pytest.PytestWarning`.
**code/message (deprecated)**
This form was used in pytest prior to 3.8 and is considered deprecated. Using this form will emit another
warning about the deprecation:
.. code-block:: python
node.warn("CI", "some message")
:param Union[Warning,str] _code_or_warning:
warning instance or warning code (legacy). This parameter receives an underscore for backward
compatibility with the legacy code/message form, and will be replaced for something
more usual when the legacy form is removed.
:param Union[str,None] message: message to display when called in the legacy form.
:param str code: code for the warning, in legacy form when using keyword arguments.
:return:
"""
if message is None:
if _code_or_warning is None:
raise ValueError("code_or_warning must be given")
self._std_warn(_code_or_warning)
else:
if _code_or_warning and code:
raise ValueError(
"code_or_warning and code cannot both be passed to this function"
)
code = _code_or_warning or code
self._legacy_warn(code, message)
def _legacy_warn(self, code, message):
"""
.. deprecated:: 3.8
Use :meth:`Node.std_warn <_pytest.nodes.Node.std_warn>` instead.
Generate a warning with the given code and message for this item.
"""
from _pytest.deprecated import NODE_WARN
self._std_warn(NODE_WARN)
assert isinstance(code, str)
fslocation = get_fslocation_from_item(self)
self.ihook.pytest_logwarning.call_historic(
kwargs=dict(
code=code, message=message, nodeid=self.nodeid, fslocation=fslocation
)
)
def _std_warn(self, warning):
"""Issue a warning for this item. """Issue a warning for this item.
Warnings will be displayed after the test session, unless explicitly suppressed Warnings will be displayed after the test session, unless explicitly suppressed
@ -215,6 +112,12 @@ class Node(object):
:param Warning warning: the warning instance to issue. Must be a subclass of PytestWarning. :param Warning warning: the warning instance to issue. Must be a subclass of PytestWarning.
:raise ValueError: if ``warning`` instance is not a subclass of PytestWarning. :raise ValueError: if ``warning`` instance is not a subclass of PytestWarning.
Example usage::
.. code-block:: python
node.warn(PytestWarning("some message"))
""" """
from _pytest.warning_types import PytestWarning from _pytest.warning_types import PytestWarning
@ -307,20 +210,6 @@ class Node(object):
""" """
return next(self.iter_markers(name=name), default) return next(self.iter_markers(name=name), default)
def get_marker(self, name):
""" get a marker object from this node or None if
the node doesn't have a marker with that name.
.. deprecated:: 3.6
This function has been deprecated in favor of
:meth:`Node.get_closest_marker <_pytest.nodes.Node.get_closest_marker>` and
:meth:`Node.iter_markers <_pytest.nodes.Node.iter_markers>`, see :ref:`update marker code`
for more details.
"""
markers = list(self.iter_markers(name=name))
if markers:
return MarkInfo(markers)
def listextrakeywords(self): def listextrakeywords(self):
""" Return a set of all extra keywords in self and any parents.""" """ Return a set of all extra keywords in self and any parents."""
extra_keywords = set() extra_keywords = set()

View File

@ -23,20 +23,15 @@ def get_skip_exceptions():
def pytest_runtest_makereport(item, call): 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 = call.__class__(lambda: runner.skip(str(call.excinfo.value)), call.when) call2 = runner.CallInfo.from_call(
lambda: runner.skip(str(call.excinfo.value)), call.when
)
call.excinfo = call2.excinfo call.excinfo = call2.excinfo
@hookimpl(trylast=True) @hookimpl(trylast=True)
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
if is_potential_nosetest(item): if is_potential_nosetest(item):
if isinstance(item.parent, python.Generator):
gen = item.parent
if not hasattr(gen, "_nosegensetup"):
call_optional(gen.obj, "setup")
if isinstance(gen.parent, python.Instance):
call_optional(gen.parent.obj, "setup")
gen._nosegensetup = True
if not call_optional(item.obj, "setup"): if not call_optional(item.obj, "setup"):
# call module level setup if there is no object level one # call module level setup if there is no object level one
call_optional(item.parent.obj, "setup") call_optional(item.parent.obj, "setup")
@ -53,11 +48,6 @@ def teardown_nose(item):
# del item.parent._nosegensetup # del item.parent._nosegensetup
def pytest_make_collect_report(collector):
if isinstance(collector, python.Generator):
call_optional(collector.obj, "setup")
def is_potential_nosetest(item): def is_potential_nosetest(item):
# extra check needed since we do not do nose style setup/teardown # extra check needed since we do not do nose style setup/teardown
# on direct unittest style classes # on direct unittest style classes

View File

@ -49,13 +49,13 @@ class Failed(OutcomeException):
__module__ = "builtins" __module__ = "builtins"
class Exit(KeyboardInterrupt): class Exit(SystemExit):
""" raised for immediate program exits (no tracebacks/summaries)""" """ raised for immediate program exits (no tracebacks/summaries)"""
def __init__(self, msg="unknown reason", returncode=None): def __init__(self, msg="unknown reason", returncode=None):
self.msg = msg self.msg = msg
self.returncode = returncode self.returncode = returncode
KeyboardInterrupt.__init__(self, msg) SystemExit.__init__(self, msg)
# exposed helper methods # exposed helper methods
@ -63,7 +63,7 @@ class Exit(KeyboardInterrupt):
def exit(msg, returncode=None): def exit(msg, returncode=None):
""" """
Exit testing process as if KeyboardInterrupt was triggered. Exit testing process as if SystemExit was triggered.
:param str msg: message to display upon exit. :param str msg: message to display upon exit.
:param int returncode: return code to be used when exiting pytest. :param int returncode: return code to be used when exiting pytest.
@ -137,10 +137,15 @@ def xfail(reason=""):
xfail.Exception = XFailed xfail.Exception = XFailed
def importorskip(modname, minversion=None): def importorskip(modname, minversion=None, reason=None):
""" return imported module if it has at least "minversion" as its """Imports and returns the requested module ``modname``, or skip the current test
__version__ attribute. If no minversion is specified the a skip if the module cannot be imported.
is only triggered if the module can not be imported.
:param str modname: the name of the module to import
:param str minversion: if given, the imported module ``__version__`` attribute must be
at least this minimal version, otherwise the test is still skipped.
:param str reason: if given, this reason is shown as the message when the module
cannot be imported.
""" """
import warnings import warnings
@ -159,7 +164,9 @@ def importorskip(modname, minversion=None):
# Do not raise chained exception here(#1485) # Do not raise chained exception here(#1485)
should_skip = True should_skip = True
if should_skip: if should_skip:
raise Skipped("could not import %r" % (modname,), allow_module_level=True) if reason is None:
reason = "could not import %r" % (modname,)
raise Skipped(reason, allow_module_level=True)
mod = sys.modules[modname] mod = sys.modules[modname]
if minversion is None: if minversion is None:
return mod return mod

View File

@ -38,13 +38,12 @@ from _pytest.compat import safe_str
from _pytest.compat import STRING_TYPES from _pytest.compat import STRING_TYPES
from _pytest.config import hookimpl from _pytest.config import hookimpl
from _pytest.main import FSHookProxy from _pytest.main import FSHookProxy
from _pytest.mark import MARK_GEN
from _pytest.mark.structures import get_unpacked_marks from _pytest.mark.structures import get_unpacked_marks
from _pytest.mark.structures import normalize_mark_list from _pytest.mark.structures import normalize_mark_list
from _pytest.mark.structures import transfer_markers
from _pytest.outcomes import fail from _pytest.outcomes import fail
from _pytest.pathlib import parts from _pytest.pathlib import parts
from _pytest.warning_types import PytestWarning from _pytest.warning_types import PytestWarning
from _pytest.warning_types import RemovedInPytest4Warning
def pyobj_property(name): def pyobj_property(name):
@ -125,10 +124,10 @@ def pytest_generate_tests(metafunc):
# those alternative spellings are common - raise a specific error to alert # those alternative spellings are common - raise a specific error to alert
# the user # the user
alt_spellings = ["parameterize", "parametrise", "parameterise"] alt_spellings = ["parameterize", "parametrise", "parameterise"]
for attr in alt_spellings: for mark_name in alt_spellings:
if hasattr(metafunc.function, attr): if metafunc.definition.get_closest_marker(mark_name):
msg = "{0} has '{1}' mark, spelling should be 'parametrize'" msg = "{0} has '{1}' mark, spelling should be 'parametrize'"
fail(msg.format(metafunc.function.__name__, attr), pytrace=False) fail(msg.format(metafunc.function.__name__, mark_name), pytrace=False)
for marker in metafunc.definition.iter_markers(name="parametrize"): for marker in metafunc.definition.iter_markers(name="parametrize"):
metafunc.parametrize(*marker.args, **marker.kwargs) metafunc.parametrize(*marker.args, **marker.kwargs)
@ -199,7 +198,6 @@ def pytest_pycollect_makeitem(collector, name, obj):
# nothing was collected elsewhere, let's do it here # nothing was collected elsewhere, let's do it here
if safe_isclass(obj): if safe_isclass(obj):
if collector.istestclass(obj, name): if collector.istestclass(obj, name):
Class = collector._getcustomclass("Class")
outcome.force_result(Class(name, parent=collector)) outcome.force_result(Class(name, parent=collector))
elif collector.istestfunction(obj, name): elif collector.istestfunction(obj, name):
# mock seems to store unbound methods (issue473), normalize it # mock seems to store unbound methods (issue473), normalize it
@ -219,7 +217,10 @@ def pytest_pycollect_makeitem(collector, name, obj):
) )
elif getattr(obj, "__test__", True): elif getattr(obj, "__test__", True):
if is_generator(obj): if is_generator(obj):
res = Generator(name, parent=collector) res = Function(name, parent=collector)
reason = deprecated.YIELD_TESTS.format(name=name)
res.add_marker(MARK_GEN.xfail(run=False, reason=reason))
res.warn(PytestWarning(reason))
else: else:
res = list(collector._genfunctions(name, obj)) res = list(collector._genfunctions(name, obj))
outcome.force_result(res) outcome.force_result(res)
@ -375,10 +376,6 @@ class PyCollector(PyobjMixin, nodes.Collector):
values.sort(key=lambda item: item.reportinfo()[:2]) values.sort(key=lambda item: item.reportinfo()[:2])
return values return values
def makeitem(self, name, obj):
warnings.warn(deprecated.COLLECTOR_MAKEITEM, stacklevel=2)
self._makeitem(name, obj)
def _makeitem(self, name, obj): def _makeitem(self, name, obj):
# assert self.ihook.fspath == self.fspath, self # assert self.ihook.fspath == self.fspath, self
return self.ihook.pytest_pycollect_makeitem(collector=self, name=name, obj=obj) return self.ihook.pytest_pycollect_makeitem(collector=self, name=name, obj=obj)
@ -387,7 +384,6 @@ class PyCollector(PyobjMixin, nodes.Collector):
module = self.getparent(Module).obj module = self.getparent(Module).obj
clscol = self.getparent(Class) clscol = self.getparent(Class)
cls = clscol and clscol.obj or None cls = clscol and clscol.obj or None
transfer_markers(funcobj, cls, module)
fm = self.session._fixturemanager fm = self.session._fixturemanager
definition = FunctionDefinition(name=name, parent=self, callobj=funcobj) definition = FunctionDefinition(name=name, parent=self, callobj=funcobj)
@ -408,7 +404,6 @@ class PyCollector(PyobjMixin, nodes.Collector):
else: else:
self.ihook.pytest_generate_tests(metafunc=metafunc) self.ihook.pytest_generate_tests(metafunc=metafunc)
Function = self._getcustomclass("Function")
if not metafunc._calls: if not metafunc._calls:
yield Function(name, parent=self, fixtureinfo=fixtureinfo) yield Function(name, parent=self, fixtureinfo=fixtureinfo)
else: else:
@ -450,7 +445,7 @@ class Module(nodes.File, PyCollector):
mod = self.fspath.pyimport(ensuresyspath=importmode) mod = self.fspath.pyimport(ensuresyspath=importmode)
except SyntaxError: except SyntaxError:
raise self.CollectError( raise self.CollectError(
_pytest._code.ExceptionInfo().getrepr(style="short") _pytest._code.ExceptionInfo.from_current().getrepr(style="short")
) )
except self.fspath.ImportMismatchError: except self.fspath.ImportMismatchError:
e = sys.exc_info()[1] e = sys.exc_info()[1]
@ -466,7 +461,7 @@ class Module(nodes.File, PyCollector):
except ImportError: except ImportError:
from _pytest._code.code import ExceptionInfo from _pytest._code.code import ExceptionInfo
exc_info = ExceptionInfo() exc_info = ExceptionInfo.from_current()
if self.config.getoption("verbose") < 2: if self.config.getoption("verbose") < 2:
exc_info.traceback = exc_info.traceback.filter(filter_traceback) exc_info.traceback = exc_info.traceback.filter(filter_traceback)
exc_repr = ( exc_repr = (
@ -648,7 +643,7 @@ class Class(PyCollector):
) )
) )
return [] return []
return [self._getcustomclass("Instance")(name="()", parent=self)] return [Instance(name="()", parent=self)]
def setup(self): def setup(self):
setup_class = _get_xunit_func(self.obj, "setup_class") setup_class = _get_xunit_func(self.obj, "setup_class")
@ -739,51 +734,6 @@ class FunctionMixin(PyobjMixin):
return self._repr_failure_py(excinfo, style=style) return self._repr_failure_py(excinfo, style=style)
class Generator(FunctionMixin, PyCollector):
def collect(self):
# test generators are seen as collectors but they also
# invoke setup/teardown on popular request
# (induced by the common "test_*" naming shared with normal tests)
from _pytest import deprecated
self.warn(deprecated.YIELD_TESTS)
self.session._setupstate.prepare(self)
# see FunctionMixin.setup and test_setupstate_is_preserved_134
self._preservedparent = self.parent.obj
values = []
seen = {}
_Function = self._getcustomclass("Function")
for i, x in enumerate(self.obj()):
name, call, args = self.getcallargs(x)
if not callable(call):
raise TypeError("%r yielded non callable test %r" % (self.obj, call))
if name is None:
name = "[%d]" % i
else:
name = "['%s']" % name
if name in seen:
raise ValueError(
"%r generated tests with non-unique name %r" % (self, name)
)
seen[name] = True
values.append(_Function(name, self, args=args, callobj=call))
return values
def getcallargs(self, obj):
if not isinstance(obj, (tuple, list)):
obj = (obj,)
# explicit naming
if isinstance(obj[0], six.string_types):
name = obj[0]
obj = obj[1:]
else:
name = None
call, args = obj[0], obj[1:]
return name, call, args
def hasinit(obj): def hasinit(obj):
init = getattr(obj, "__init__", None) init = getattr(obj, "__init__", None)
if init: if init:
@ -1065,48 +1015,6 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
pytrace=False, pytrace=False,
) )
def addcall(self, funcargs=None, id=NOTSET, param=NOTSET):
""" Add a new call to the underlying test function during the collection phase of a test run.
.. deprecated:: 3.3
Use :meth:`parametrize` instead.
Note that request.addcall() is called during the test collection phase prior and
independently to actual test execution. You should only use addcall()
if you need to specify multiple arguments of a test function.
:arg funcargs: argument keyword dictionary used when invoking
the test function.
:arg id: used for reporting and identification purposes. If you
don't supply an `id` an automatic unique id will be generated.
:arg param: a parameter which will be exposed to a later fixture function
invocation through the ``request.param`` attribute.
"""
warnings.warn(deprecated.METAFUNC_ADD_CALL, stacklevel=2)
assert funcargs is None or isinstance(funcargs, dict)
if funcargs is not None:
for name in funcargs:
if name not in self.fixturenames:
fail("funcarg %r not used in this function." % name)
else:
funcargs = {}
if id is None:
raise ValueError("id=None not allowed")
if id is NOTSET:
id = len(self._calls)
id = str(id)
if id in self._ids:
raise ValueError("duplicate id %r" % id)
self._ids.add(id)
cs = CallSpec2(self)
cs.setall(funcargs, id, param)
self._calls.append(cs)
def _find_parametrized_scope(argnames, arg2fixturedefs, indirect): def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
"""Find the most appropriate scope for a parametrized call based on its arguments. """Find the most appropriate scope for a parametrized call based on its arguments.
@ -1148,13 +1056,11 @@ def _idval(val, argname, idx, idfn, item, config):
s = idfn(val) s = idfn(val)
except Exception as e: except Exception as e:
# See issue https://github.com/pytest-dev/pytest/issues/2169 # See issue https://github.com/pytest-dev/pytest/issues/2169
msg = ( msg = "{}: error raised while trying to determine id of parameter '{}' at position {}\n"
"While trying to determine id of parameter {} at position " msg = msg.format(item.nodeid, argname, idx)
"{} the following exception was raised:\n".format(argname, idx) # we only append the exception type and message because on Python 2 reraise does nothing
)
msg += " {}: {}\n".format(type(e).__name__, e) msg += " {}: {}\n".format(type(e).__name__, e)
msg += "This warning will be an error error in pytest-4.0." six.raise_from(ValueError(msg), e)
item.warn(RemovedInPytest4Warning(msg))
if s: if s:
return ascii_escaped(s) return ascii_escaped(s)
@ -1326,8 +1232,7 @@ def _showfixtures_main(config, session):
tw.line(" %s: no docstring available" % (loc,), red=True) tw.line(" %s: no docstring available" % (loc,), red=True)
def write_docstring(tw, doc): def write_docstring(tw, doc, indent=" "):
INDENT = " "
doc = doc.rstrip() doc = doc.rstrip()
if "\n" in doc: if "\n" in doc:
firstline, rest = doc.split("\n", 1) firstline, rest = doc.split("\n", 1)
@ -1335,11 +1240,11 @@ def write_docstring(tw, doc):
firstline, rest = doc, "" firstline, rest = doc, ""
if firstline.strip(): if firstline.strip():
tw.line(INDENT + firstline.strip()) tw.line(indent + firstline.strip())
if rest: if rest:
for line in dedent(rest).split("\n"): for line in dedent(rest).split("\n"):
tw.write(INDENT + line + "\n") tw.write(indent + line + "\n")
class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr): class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
@ -1384,6 +1289,20 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
if keywords: if keywords:
self.keywords.update(keywords) self.keywords.update(keywords)
# todo: this is a hell of a hack
# https://github.com/pytest-dev/pytest/issues/4569
self.keywords.update(
dict.fromkeys(
[
mark.name
for mark in self.iter_markers()
if mark.name not in self.keywords
],
True,
)
)
if fixtureinfo is None: if fixtureinfo is None:
fixtureinfo = self.session._fixturemanager.getfixtureinfo( fixtureinfo = self.session._fixturemanager.getfixtureinfo(
self, self.obj, self.cls, funcargs=not self._isyieldedfunction() self, self.obj, self.cls, funcargs=not self._isyieldedfunction()

View File

@ -1,6 +1,9 @@
from __future__ import absolute_import
import math import math
import pprint import pprint
import sys import sys
import warnings
from decimal import Decimal from decimal import Decimal
from numbers import Number from numbers import Number
@ -10,9 +13,11 @@ from six.moves import filterfalse
from six.moves import zip from six.moves import zip
import _pytest._code import _pytest._code
from _pytest import deprecated
from _pytest.compat import isclass from _pytest.compat import isclass
from _pytest.compat import Iterable
from _pytest.compat import Mapping from _pytest.compat import Mapping
from _pytest.compat import Sequence from _pytest.compat import Sized
from _pytest.compat import STRING_TYPES from _pytest.compat import STRING_TYPES
from _pytest.outcomes import fail from _pytest.outcomes import fail
@ -182,7 +187,7 @@ class ApproxMapping(ApproxBase):
raise _non_numeric_type_error(self.expected, at="key={!r}".format(key)) raise _non_numeric_type_error(self.expected, at="key={!r}".format(key))
class ApproxSequence(ApproxBase): class ApproxSequencelike(ApproxBase):
""" """
Perform approximate comparisons where the expected value is a sequence of Perform approximate comparisons where the expected value is a sequence of
numbers. numbers.
@ -518,10 +523,14 @@ def approx(expected, rel=None, abs=None, nan_ok=False):
cls = ApproxScalar cls = ApproxScalar
elif isinstance(expected, Mapping): elif isinstance(expected, Mapping):
cls = ApproxMapping cls = ApproxMapping
elif isinstance(expected, Sequence) and not isinstance(expected, STRING_TYPES):
cls = ApproxSequence
elif _is_numpy_array(expected): elif _is_numpy_array(expected):
cls = ApproxNumpy cls = ApproxNumpy
elif (
isinstance(expected, Iterable)
and isinstance(expected, Sized)
and not isinstance(expected, STRING_TYPES)
):
cls = ApproxSequencelike
else: else:
raise _non_numeric_type_error(expected, at=None) raise _non_numeric_type_error(expected, at=None)
@ -547,29 +556,47 @@ def _is_numpy_array(obj):
def raises(expected_exception, *args, **kwargs): def raises(expected_exception, *args, **kwargs):
r""" r"""
Assert that a code block/function call raises ``expected_exception`` Assert that a code block/function call raises ``expected_exception``
and raise a failure exception otherwise. or raise a failure exception otherwise.
:arg message: if specified, provides a custom failure message if the :kwparam match: if specified, asserts that the exception matches a text or regex
exception is not raised
:arg match: if specified, asserts that the exception matches a text or regex
This helper produces a ``ExceptionInfo()`` object (see below). :kwparam message: **(deprecated since 4.1)** if specified, provides a custom failure message
if the exception is not raised
You may use this function as a context manager:: .. currentmodule:: _pytest._code
Use ``pytest.raises`` as a context manager, which will capture the exception of the given
type::
>>> with raises(ZeroDivisionError): >>> with raises(ZeroDivisionError):
... 1/0 ... 1/0
.. versionchanged:: 2.10 If the code block does not raise the expected exception (``ZeroDivisionError`` in the example
above), or no exception at all, the check will fail instead.
In the context manager form you may use the keyword argument You can also use the keyword argument ``match`` to assert that the
``message`` to specify a custom failure message:: exception matches a text or regex::
>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"): >>> with raises(ValueError, match='must be 0 or None'):
... pass ... raise ValueError("value must be 0 or None")
Traceback (most recent call last):
... >>> with raises(ValueError, match=r'must be \d+$'):
Failed: Expecting ZeroDivisionError ... raise ValueError("value must be 42")
The context manager produces an :class:`ExceptionInfo` object which can be used to inspect the
details of the captured exception::
>>> with raises(ValueError) as exc_info:
... raise ValueError("value must be 42")
>>> assert exc_info.type is ValueError
>>> assert exc_info.value.args[0] == "value must be 42"
.. deprecated:: 4.1
In the context manager form you may use the keyword argument
``message`` to specify a custom failure message that will be displayed
in case the ``pytest.raises`` check fails. This has been deprecated as it
is considered error prone as users often mean to use ``match`` instead.
.. note:: .. note::
@ -583,7 +610,7 @@ def raises(expected_exception, *args, **kwargs):
>>> with raises(ValueError) as exc_info: >>> with raises(ValueError) as exc_info:
... if value > 10: ... if value > 10:
... raise ValueError("value must be <= 10") ... raise ValueError("value must be <= 10")
... assert exc_info.type == ValueError # this will not execute ... assert exc_info.type is ValueError # this will not execute
Instead, the following approach must be taken (note the difference in Instead, the following approach must be taken (note the difference in
scope):: scope)::
@ -592,22 +619,9 @@ def raises(expected_exception, *args, **kwargs):
... if value > 10: ... if value > 10:
... raise ValueError("value must be <= 10") ... raise ValueError("value must be <= 10")
... ...
>>> assert exc_info.type == ValueError >>> assert exc_info.type is ValueError
**Legacy form**
Since version ``3.1`` you can use the keyword argument ``match`` to assert that the
exception matches a text or regex::
>>> with raises(ValueError, match='must be 0 or None'):
... raise ValueError("value must be 0 or None")
>>> with raises(ValueError, match=r'must be \d+$'):
... raise ValueError("value must be 42")
**Legacy forms**
The forms below are fully supported but are discouraged for new code because the
context manager form is regarded as more readable and less error-prone.
It is possible to specify a callable by passing a to-be-called lambda:: It is possible to specify a callable by passing a to-be-called lambda::
@ -623,17 +637,8 @@ def raises(expected_exception, *args, **kwargs):
>>> raises(ZeroDivisionError, f, x=0) >>> raises(ZeroDivisionError, f, x=0)
<ExceptionInfo ...> <ExceptionInfo ...>
It is also possible to pass a string to be evaluated at runtime:: The form above is fully supported but discouraged for new code because the
context manager form is regarded as more readable and less error-prone.
>>> raises(ZeroDivisionError, "f(0)")
<ExceptionInfo ...>
The string will be evaluated using the same ``locals()`` and ``globals()``
at the moment of the ``raises`` call.
.. currentmodule:: _pytest._code
Consult the API of ``excinfo`` objects: :class:`ExceptionInfo`.
.. note:: .. note::
Similar to caught exception objects in Python, explicitly clearing Similar to caught exception objects in Python, explicitly clearing
@ -664,6 +669,7 @@ def raises(expected_exception, *args, **kwargs):
if not args: if not args:
if "message" in kwargs: if "message" in kwargs:
message = kwargs.pop("message") message = kwargs.pop("message")
warnings.warn(deprecated.RAISES_MESSAGE_PARAMETER, stacklevel=2)
if "match" in kwargs: if "match" in kwargs:
match_expr = kwargs.pop("match") match_expr = kwargs.pop("match")
if kwargs: if kwargs:
@ -672,6 +678,7 @@ def raises(expected_exception, *args, **kwargs):
raise TypeError(msg) raise TypeError(msg)
return RaisesContext(expected_exception, message, match_expr) return RaisesContext(expected_exception, message, match_expr)
elif isinstance(args[0], str): elif isinstance(args[0], str):
warnings.warn(deprecated.RAISES_EXEC, stacklevel=2)
code, = args code, = args
assert isinstance(code, str) assert isinstance(code, str)
frame = sys._getframe(1) frame = sys._getframe(1)
@ -684,13 +691,13 @@ def raises(expected_exception, *args, **kwargs):
# XXX didn't mean f_globals == f_locals something special? # XXX didn't mean f_globals == f_locals something special?
# this is destroyed here ... # this is destroyed here ...
except expected_exception: except expected_exception:
return _pytest._code.ExceptionInfo() return _pytest._code.ExceptionInfo.from_current()
else: else:
func = args[0] func = args[0]
try: try:
func(*args[1:], **kwargs) func(*args[1:], **kwargs)
except expected_exception: except expected_exception:
return _pytest._code.ExceptionInfo() return _pytest._code.ExceptionInfo.from_current()
fail(message) fail(message)
@ -705,7 +712,7 @@ class RaisesContext(object):
self.excinfo = None self.excinfo = None
def __enter__(self): def __enter__(self):
self.excinfo = object.__new__(_pytest._code.ExceptionInfo) self.excinfo = _pytest._code.ExceptionInfo.for_later()
return self.excinfo return self.excinfo
def __exit__(self, *tp): def __exit__(self, *tp):

View File

@ -11,6 +11,7 @@ import warnings
import six import six
import _pytest._code import _pytest._code
from _pytest.deprecated import WARNS_EXEC
from _pytest.fixtures import yield_fixture from _pytest.fixtures import yield_fixture
from _pytest.outcomes import fail from _pytest.outcomes import fail
@ -89,6 +90,7 @@ def warns(expected_warning, *args, **kwargs):
match_expr = kwargs.pop("match") match_expr = kwargs.pop("match")
return WarningsChecker(expected_warning, match_expr=match_expr) return WarningsChecker(expected_warning, match_expr=match_expr)
elif isinstance(args[0], str): elif isinstance(args[0], str):
warnings.warn(WARNS_EXEC, stacklevel=2)
code, = args code, = args
assert isinstance(code, str) assert isinstance(code, str)
frame = sys._getframe(1) frame = sys._getframe(1)

View File

@ -34,9 +34,9 @@ def pytest_configure(config):
config.pluginmanager.register(config._resultlog) config.pluginmanager.register(config._resultlog)
from _pytest.deprecated import RESULT_LOG from _pytest.deprecated import RESULT_LOG
from _pytest.warnings import _issue_config_warning from _pytest.warnings import _issue_warning_captured
_issue_config_warning(RESULT_LOG, config, stacklevel=2) _issue_warning_captured(RESULT_LOG, config.hook, stacklevel=2)
def pytest_unconfigure(config): def pytest_unconfigure(config):

View File

@ -8,12 +8,14 @@ import os
import sys import sys
from time import time from time import time
import attr
import six import six
from .reports import CollectErrorRepr from .reports import CollectErrorRepr
from .reports import CollectReport 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 skip 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
@ -189,43 +191,58 @@ def check_interactive_exception(call, report):
def call_runtest_hook(item, when, **kwds): def call_runtest_hook(item, when, **kwds):
hookname = "pytest_runtest_" + when hookname = "pytest_runtest_" + when
ihook = getattr(item.ihook, hookname) ihook = getattr(item.ihook, hookname)
return CallInfo( reraise = (Exit,)
lambda: ihook(item=item, **kwds), if not item.config.getvalue("usepdb"):
when=when, reraise += (KeyboardInterrupt,)
treat_keyboard_interrupt_as_exception=item.config.getvalue("usepdb"), return CallInfo.from_call(
lambda: ihook(item=item, **kwds), when=when, reraise=reraise
) )
@attr.s(repr=False)
class CallInfo(object): class CallInfo(object):
""" Result/Exception info a function invocation. """ """ Result/Exception info a function invocation. """
#: None or ExceptionInfo object. _result = attr.ib()
excinfo = None # type: Optional[ExceptionInfo]
excinfo = attr.ib()
start = attr.ib()
stop = attr.ib()
when = attr.ib()
def __init__(self, func, when, treat_keyboard_interrupt_as_exception=False): @property
def result(self):
if self.excinfo is not None:
raise AttributeError("{!r} has no valid result".format(self))
return self._result
@classmethod
def from_call(cls, func, when, reraise=None):
#: context of invocation: one of "setup", "call", #: context of invocation: one of "setup", "call",
#: "teardown", "memocollect" #: "teardown", "memocollect"
self.when = when start = time()
self.start = time() excinfo = None
try: try:
self.result = func() result = func()
except KeyboardInterrupt:
if treat_keyboard_interrupt_as_exception:
self.excinfo = ExceptionInfo()
else:
self.stop = time()
raise
except: # noqa except: # noqa
self.excinfo = ExceptionInfo() excinfo = ExceptionInfo.from_current()
self.stop = time() if reraise is not None and excinfo.errisinstance(reraise):
raise
result = None
stop = time()
return cls(start=start, stop=stop, when=when, result=result, excinfo=excinfo)
def __repr__(self): def __repr__(self):
if self.excinfo: if self.excinfo is not None:
status = "exception: %s" % str(self.excinfo.value) status = "exception"
value = self.excinfo.value
else: else:
result = getattr(self, "result", "<NOTSET>") # TODO: investigate unification
status = "result: %r" % (result,) value = repr(self._result)
return "<CallInfo when=%r %s>" % (self.when, status) status = "result"
return "<CallInfo when={when!r} {status}: {value}>".format(
when=self.when, value=value, status=status
)
def pytest_runtest_makereport(item, call): def pytest_runtest_makereport(item, call):
@ -269,7 +286,7 @@ def pytest_runtest_makereport(item, call):
def pytest_make_collect_report(collector): def pytest_make_collect_report(collector):
call = CallInfo(lambda: list(collector.collect()), "collect") call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
longrepr = None longrepr = None
if not call.excinfo: if not call.excinfo:
outcome = "passed" outcome = "passed"

View File

@ -167,7 +167,7 @@ def getreportopt(config):
if char not in reportopts and char != "a": if char not in reportopts and char != "a":
reportopts += char reportopts += char
elif char == "a": elif char == "a":
reportopts = "fEsxXw" reportopts = "sxXwEf"
return reportopts return reportopts
@ -186,20 +186,17 @@ def pytest_report_teststatus(report):
@attr.s @attr.s
class WarningReport(object): class WarningReport(object):
""" """
Simple structure to hold warnings information captured by ``pytest_logwarning`` and ``pytest_warning_captured``. Simple structure to hold warnings information captured by ``pytest_warning_captured``.
:ivar str message: user friendly message about the warning :ivar str message: user friendly message about the warning
:ivar str|None nodeid: node id that generated the warning (see ``get_location``). :ivar str|None nodeid: node id that generated the warning (see ``get_location``).
:ivar tuple|py.path.local fslocation: :ivar tuple|py.path.local fslocation:
file system location of the source of the warning (see ``get_location``). file system location of the source of the warning (see ``get_location``).
:ivar bool legacy: if this warning report was generated from the deprecated ``pytest_logwarning`` hook.
""" """
message = attr.ib() message = attr.ib()
nodeid = attr.ib(default=None) nodeid = attr.ib(default=None)
fslocation = attr.ib(default=None) fslocation = attr.ib(default=None)
legacy = attr.ib(default=False)
def get_location(self, config): def get_location(self, config):
""" """
@ -329,13 +326,6 @@ class TerminalReporter(object):
self.write_line("INTERNALERROR> " + line) self.write_line("INTERNALERROR> " + line)
return 1 return 1
def pytest_logwarning(self, fslocation, message, nodeid):
warnings = self.stats.setdefault("warnings", [])
warning = WarningReport(
fslocation=fslocation, message=message, nodeid=nodeid, legacy=True
)
warnings.append(warning)
def pytest_warning_captured(self, warning_message, item): def pytest_warning_captured(self, warning_message, item):
# from _pytest.nodes import get_fslocation_from_item # from _pytest.nodes import get_fslocation_from_item
from _pytest.warnings import warning_record_to_str from _pytest.warnings import warning_record_to_str
@ -621,6 +611,10 @@ class TerminalReporter(object):
continue continue
indent = (len(stack) - 1) * " " indent = (len(stack) - 1) * " "
self._tw.line("%s%s" % (indent, col)) self._tw.line("%s%s" % (indent, col))
if self.config.option.verbose >= 1:
if hasattr(col, "_obj") and col._obj.__doc__:
for line in col._obj.__doc__.strip().splitlines():
self._tw.line("%s%s" % (indent + " ", line.strip()))
@pytest.hookimpl(hookwrapper=True) @pytest.hookimpl(hookwrapper=True)
def pytest_sessionfinish(self, exitstatus): def pytest_sessionfinish(self, exitstatus):

View File

@ -14,8 +14,6 @@ from _pytest.outcomes import skip
from _pytest.outcomes import xfail from _pytest.outcomes import xfail
from _pytest.python import Class from _pytest.python import Class
from _pytest.python import Function from _pytest.python import Function
from _pytest.python import Module
from _pytest.python import transfer_markers
def pytest_pycollect_makeitem(collector, name, obj): def pytest_pycollect_makeitem(collector, name, obj):
@ -54,14 +52,12 @@ class UnitTestCase(Class):
return return
self.session._fixturemanager.parsefactories(self, unittest=True) self.session._fixturemanager.parsefactories(self, unittest=True)
loader = TestLoader() loader = TestLoader()
module = self.getparent(Module).obj
foundsomething = False foundsomething = False
for name in loader.getTestCaseNames(self.obj): for name in loader.getTestCaseNames(self.obj):
x = getattr(self.obj, name) x = getattr(self.obj, name)
if not getattr(x, "__test__", True): if not getattr(x, "__test__", True):
continue continue
funcobj = getimfunc(x) funcobj = getimfunc(x)
transfer_markers(funcobj, cls, module)
yield TestCaseFunction(name, parent=self, callobj=funcobj) yield TestCaseFunction(name, parent=self, callobj=funcobj)
foundsomething = True foundsomething = True
@ -115,6 +111,10 @@ class TestCaseFunction(Function):
rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo) rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo)
try: try:
excinfo = _pytest._code.ExceptionInfo(rawexcinfo) excinfo = _pytest._code.ExceptionInfo(rawexcinfo)
# invoke the attributes to trigger storing the traceback
# trial causes some issue there
excinfo.value
excinfo.traceback
except TypeError: except TypeError:
try: try:
try: try:
@ -136,7 +136,7 @@ class TestCaseFunction(Function):
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
except fail.Exception: except fail.Exception:
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo.from_current()
self.__dict__.setdefault("_excinfo", []).append(excinfo) self.__dict__.setdefault("_excinfo", []).append(excinfo)
def addError(self, testcase, rawexcinfo): def addError(self, testcase, rawexcinfo):

View File

@ -160,19 +160,19 @@ def pytest_terminal_summary(terminalreporter):
yield yield
def _issue_config_warning(warning, config, stacklevel): def _issue_warning_captured(warning, hook, stacklevel):
""" """
This function should be used instead of calling ``warnings.warn`` directly when we are in the "configure" stage: This function should be used instead of calling ``warnings.warn`` directly when we are in the "configure" stage:
at this point the actual options might not have been set, so we manually trigger the pytest_warning_captured at this point the actual options might not have been set, so we manually trigger the pytest_warning_captured
hook so we can display this warnings in the terminal. This is a hack until we can sort out #2891. hook so we can display this warnings in the terminal. This is a hack until we can sort out #2891.
:param warning: the warning instance. :param warning: the warning instance.
:param config: :param hook: the hook caller
:param stacklevel: stacklevel forwarded to warnings.warn :param stacklevel: stacklevel forwarded to warnings.warn
""" """
with warnings.catch_warnings(record=True) as records: with warnings.catch_warnings(record=True) as records:
warnings.simplefilter("always", type(warning)) warnings.simplefilter("always", type(warning))
warnings.warn(warning, stacklevel=stacklevel) warnings.warn(warning, stacklevel=stacklevel)
config.hook.pytest_warning_captured.call_historic( hook.pytest_warning_captured.call_historic(
kwargs=dict(warning_message=records[0], when="config", item=None) kwargs=dict(warning_message=records[0], when="config", item=None)
) )

View File

@ -28,7 +28,6 @@ from _pytest.outcomes import skip
from _pytest.outcomes import xfail from _pytest.outcomes import xfail
from _pytest.python import Class from _pytest.python import Class
from _pytest.python import Function from _pytest.python import Function
from _pytest.python import Generator
from _pytest.python import Instance from _pytest.python import Instance
from _pytest.python import Module from _pytest.python import Module
from _pytest.python import Package from _pytest.python import Package
@ -57,7 +56,6 @@ __all__ = [
"fixture", "fixture",
"freeze_includes", "freeze_includes",
"Function", "Function",
"Generator",
"hookimpl", "hookimpl",
"hookspec", "hookspec",
"importorskip", "importorskip",

View File

@ -146,6 +146,7 @@ class TestGeneralUsage(object):
assert result.ret assert result.ret
result.stderr.fnmatch_lines(["*ERROR: not found:*{}".format(p2.basename)]) result.stderr.fnmatch_lines(["*ERROR: not found:*{}".format(p2.basename)])
@pytest.mark.filterwarnings("default")
def test_better_reporting_on_conftest_load_failure(self, testdir, request): def test_better_reporting_on_conftest_load_failure(self, testdir, request):
"""Show a user-friendly traceback on conftest import failures (#486, #3332)""" """Show a user-friendly traceback on conftest import failures (#486, #3332)"""
testdir.makepyfile("") testdir.makepyfile("")
@ -299,7 +300,7 @@ class TestGeneralUsage(object):
""" """
import pytest import pytest
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
metafunc.addcall({'x': 3}, id='hello-123') metafunc.parametrize('x', [3], ids=['hello-123'])
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
print(item.keywords) print(item.keywords)
if 'hello-123' in item.keywords: if 'hello-123' in item.keywords:
@ -316,8 +317,7 @@ class TestGeneralUsage(object):
p = testdir.makepyfile( p = testdir.makepyfile(
""" """
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
metafunc.addcall({'i': 1}, id="1") metafunc.parametrize('i', [1, 2], ids=["1", "2"])
metafunc.addcall({'i': 2}, id="2")
def test_func(i): def test_func(i):
pass pass
""" """
@ -560,12 +560,11 @@ class TestInvocationVariants(object):
def test_equivalence_pytest_pytest(self): def test_equivalence_pytest_pytest(self):
assert pytest.main == py.test.cmdline.main assert pytest.main == py.test.cmdline.main
def test_invoke_with_string(self, capsys): def test_invoke_with_invalid_type(self, capsys):
retcode = pytest.main("-h") with pytest.raises(
assert not retcode TypeError, match="expected to be a list or tuple of strings, got: '-h'"
out, err = capsys.readouterr() ):
assert "--help" in out pytest.main("-h")
pytest.raises(ValueError, lambda: pytest.main(0))
def test_invoke_with_path(self, tmpdir, capsys): def test_invoke_with_path(self, tmpdir, capsys):
retcode = pytest.main(tmpdir) retcode = pytest.main(tmpdir)

View File

@ -37,7 +37,7 @@ def test_code_with_class():
class A(object): class A(object):
pass pass
pytest.raises(TypeError, "_pytest._code.Code(A)") pytest.raises(TypeError, _pytest._code.Code, A)
def x(): def x():
@ -169,7 +169,7 @@ class TestExceptionInfo(object):
else: else:
assert False assert False
except AssertionError: except AssertionError:
exci = _pytest._code.ExceptionInfo() exci = _pytest._code.ExceptionInfo.from_current()
assert exci.getrepr() assert exci.getrepr()
@ -181,7 +181,7 @@ class TestTracebackEntry(object):
else: else:
assert False assert False
except AssertionError: except AssertionError:
exci = _pytest._code.ExceptionInfo() exci = _pytest._code.ExceptionInfo.from_current()
entry = exci.traceback[0] entry = exci.traceback[0]
source = entry.getsource() source = entry.getsource()
assert len(source) == 6 assert len(source) == 6

View File

@ -71,7 +71,7 @@ def test_excinfo_simple():
try: try:
raise ValueError raise ValueError
except ValueError: except ValueError:
info = _pytest._code.ExceptionInfo() info = _pytest._code.ExceptionInfo.from_current()
assert info.type == ValueError assert info.type == ValueError
@ -85,7 +85,7 @@ def test_excinfo_getstatement():
try: try:
f() f()
except ValueError: except ValueError:
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo.from_current()
linenumbers = [ linenumbers = [
_pytest._code.getrawcode(f).co_firstlineno - 1 + 4, _pytest._code.getrawcode(f).co_firstlineno - 1 + 4,
_pytest._code.getrawcode(f).co_firstlineno - 1 + 1, _pytest._code.getrawcode(f).co_firstlineno - 1 + 1,
@ -126,7 +126,7 @@ class TestTraceback_f_g_h(object):
try: try:
h() h()
except ValueError: except ValueError:
self.excinfo = _pytest._code.ExceptionInfo() self.excinfo = _pytest._code.ExceptionInfo.from_current()
def test_traceback_entries(self): def test_traceback_entries(self):
tb = self.excinfo.traceback tb = self.excinfo.traceback
@ -163,7 +163,7 @@ class TestTraceback_f_g_h(object):
try: try:
exec(source.compile()) exec(source.compile())
except NameError: except NameError:
tb = _pytest._code.ExceptionInfo().traceback tb = _pytest._code.ExceptionInfo.from_current().traceback
print(tb[-1].getsource()) print(tb[-1].getsource())
s = str(tb[-1].getsource()) s = str(tb[-1].getsource())
assert s.startswith("def xyz():\n try:") assert s.startswith("def xyz():\n try:")
@ -180,7 +180,8 @@ class TestTraceback_f_g_h(object):
def test_traceback_cut_excludepath(self, testdir): def test_traceback_cut_excludepath(self, testdir):
p = testdir.makepyfile("def f(): raise ValueError") p = testdir.makepyfile("def f(): raise ValueError")
excinfo = pytest.raises(ValueError, "p.pyimport().f()") with pytest.raises(ValueError) as excinfo:
p.pyimport().f()
basedir = py.path.local(pytest.__file__).dirpath() basedir = py.path.local(pytest.__file__).dirpath()
newtraceback = excinfo.traceback.cut(excludepath=basedir) newtraceback = excinfo.traceback.cut(excludepath=basedir)
for x in newtraceback: for x in newtraceback:
@ -336,7 +337,8 @@ class TestTraceback_f_g_h(object):
def test_excinfo_exconly(): def test_excinfo_exconly():
excinfo = pytest.raises(ValueError, h) excinfo = pytest.raises(ValueError, h)
assert excinfo.exconly().startswith("ValueError") assert excinfo.exconly().startswith("ValueError")
excinfo = pytest.raises(ValueError, "raise ValueError('hello\\nworld')") with pytest.raises(ValueError) as excinfo:
raise ValueError("hello\nworld")
msg = excinfo.exconly(tryshort=True) msg = excinfo.exconly(tryshort=True)
assert msg.startswith("ValueError") assert msg.startswith("ValueError")
assert msg.endswith("world") assert msg.endswith("world")
@ -356,6 +358,12 @@ def test_excinfo_str():
assert len(s.split(":")) >= 3 # on windows it's 4 assert len(s.split(":")) >= 3 # on windows it's 4
def test_excinfo_for_later():
e = ExceptionInfo.for_later()
assert "for raises" in repr(e)
assert "for raises" in str(e)
def test_excinfo_errisinstance(): def test_excinfo_errisinstance():
excinfo = pytest.raises(ValueError, h) excinfo = pytest.raises(ValueError, h)
assert excinfo.errisinstance(ValueError) assert excinfo.errisinstance(ValueError)
@ -365,7 +373,7 @@ def test_excinfo_no_sourcecode():
try: try:
exec("raise ValueError()") exec("raise ValueError()")
except ValueError: except ValueError:
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo.from_current()
s = str(excinfo.traceback[-1]) s = str(excinfo.traceback[-1])
assert s == " File '<string>':1 in <module>\n ???\n" assert s == " File '<string>':1 in <module>\n ???\n"
@ -390,7 +398,7 @@ def test_entrysource_Queue_example():
try: try:
queue.Queue().get(timeout=0.001) queue.Queue().get(timeout=0.001)
except queue.Empty: except queue.Empty:
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo.from_current()
entry = excinfo.traceback[-1] entry = excinfo.traceback[-1]
source = entry.getsource() source = entry.getsource()
assert source is not None assert source is not None
@ -402,7 +410,7 @@ def test_codepath_Queue_example():
try: try:
queue.Queue().get(timeout=0.001) queue.Queue().get(timeout=0.001)
except queue.Empty: except queue.Empty:
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo.from_current()
entry = excinfo.traceback[-1] entry = excinfo.traceback[-1]
path = entry.path path = entry.path
assert isinstance(path, py.path.local) assert isinstance(path, py.path.local)
@ -453,7 +461,7 @@ class TestFormattedExcinfo(object):
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
except: # noqa except: # noqa
return _pytest._code.ExceptionInfo() return _pytest._code.ExceptionInfo.from_current()
assert 0, "did not raise" assert 0, "did not raise"
def test_repr_source(self): def test_repr_source(self):
@ -491,7 +499,7 @@ class TestFormattedExcinfo(object):
try: try:
exec(co) exec(co)
except ValueError: except ValueError:
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo.from_current()
repr = pr.repr_excinfo(excinfo) repr = pr.repr_excinfo(excinfo)
assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" assert repr.reprtraceback.reprentries[1].lines[0] == "> ???"
if sys.version_info[0] >= 3: if sys.version_info[0] >= 3:
@ -510,7 +518,7 @@ raise ValueError()
try: try:
exec(co) exec(co)
except ValueError: except ValueError:
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo.from_current()
repr = pr.repr_excinfo(excinfo) repr = pr.repr_excinfo(excinfo)
assert repr.reprtraceback.reprentries[1].lines[0] == "> ???" assert repr.reprtraceback.reprentries[1].lines[0] == "> ???"
if sys.version_info[0] >= 3: if sys.version_info[0] >= 3:
@ -1340,7 +1348,7 @@ def test_repr_traceback_with_unicode(style, encoding):
try: try:
raise RuntimeError(msg) raise RuntimeError(msg)
except RuntimeError: except RuntimeError:
e_info = ExceptionInfo() e_info = ExceptionInfo.from_current()
formatter = FormattedExcinfo(style=style) formatter = FormattedExcinfo(style=style)
repr_traceback = formatter.repr_traceback(e_info) repr_traceback = formatter.repr_traceback(e_info)
assert repr_traceback is not None assert repr_traceback is not None

View File

@ -6,6 +6,7 @@ from __future__ import absolute_import
from __future__ import division from __future__ import division
from __future__ import print_function from __future__ import print_function
import ast
import inspect import inspect
import sys import sys
@ -14,7 +15,6 @@ import six
import _pytest._code import _pytest._code
import pytest import pytest
from _pytest._code import Source from _pytest._code import Source
from _pytest._code.source import ast
astonly = pytest.mark.nothing astonly = pytest.mark.nothing
@ -306,8 +306,6 @@ class TestSourceParsingAndCompiling(object):
pytest.raises(SyntaxError, lambda: source.getstatementrange(0)) pytest.raises(SyntaxError, lambda: source.getstatementrange(0))
def test_compile_to_ast(self): def test_compile_to_ast(self):
import ast
source = Source("x = 4") source = Source("x = 4")
mod = source.compile(flag=ast.PyCF_ONLY_AST) mod = source.compile(flag=ast.PyCF_ONLY_AST)
assert isinstance(mod, ast.Module) assert isinstance(mod, ast.Module)
@ -317,10 +315,9 @@ class TestSourceParsingAndCompiling(object):
co = self.source.compile() co = self.source.compile()
six.exec_(co, globals()) six.exec_(co, globals())
f(7) f(7)
excinfo = pytest.raises(AssertionError, "f(6)") excinfo = pytest.raises(AssertionError, f, 6)
frame = excinfo.traceback[-1].frame frame = excinfo.traceback[-1].frame
stmt = frame.code.fullsource.getstatement(frame.lineno) stmt = frame.code.fullsource.getstatement(frame.lineno)
# print "block", str(block)
assert str(stmt).strip().startswith("assert") assert str(stmt).strip().startswith("assert")
@pytest.mark.parametrize("name", ["", None, "my"]) @pytest.mark.parametrize("name", ["", None, "my"])
@ -361,17 +358,13 @@ def test_getline_finally():
def c(): def c():
pass pass
excinfo = pytest.raises( with pytest.raises(TypeError) as excinfo:
TypeError, teardown = None
""" try:
teardown = None c(1)
try: finally:
c(1) if teardown:
finally: teardown()
if teardown:
teardown()
""",
)
source = excinfo.traceback[-1].statement source = excinfo.traceback[-1].statement
assert str(source).strip() == "c(1)" assert str(source).strip() == "c(1)"

View File

@ -10,122 +10,7 @@ from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
pytestmark = pytest.mark.pytester_example_path("deprecated") pytestmark = pytest.mark.pytester_example_path("deprecated")
def test_yield_tests_deprecation(testdir): def test_pytest_setup_cfg_unsupported(testdir):
testdir.makepyfile(
"""
def func1(arg, arg2):
assert arg == arg2
def test_gen():
yield "m1", func1, 15, 3*5
yield "m2", func1, 42, 6*7
def test_gen2():
for k in range(10):
yield func1, 1, 1
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
"*test_yield_tests_deprecation.py:3:*yield tests are deprecated*",
"*test_yield_tests_deprecation.py:6:*yield tests are deprecated*",
"*2 passed*",
]
)
assert result.stdout.str().count("yield tests are deprecated") == 2
def test_compat_properties_deprecation(testdir):
testdir.makepyfile(
"""
def test_foo(request):
print(request.node.Module)
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
"*test_compat_properties_deprecation.py:2:*usage of Function.Module is deprecated, "
"please use pytest.Module instead*",
"*1 passed, 1 warnings in*",
]
)
def test_cached_setup_deprecation(testdir):
testdir.makepyfile(
"""
import pytest
@pytest.fixture
def fix(request):
return request.cached_setup(lambda: 1)
def test_foo(fix):
assert fix == 1
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
"*test_cached_setup_deprecation.py:4:*cached_setup is deprecated*",
"*1 passed, 1 warnings in*",
]
)
def test_custom_class_deprecation(testdir):
testdir.makeconftest(
"""
import pytest
class MyModule(pytest.Module):
class Class(pytest.Class):
pass
def pytest_pycollect_makemodule(path, parent):
return MyModule(path, parent)
"""
)
testdir.makepyfile(
"""
class Test:
def test_foo(self):
pass
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
'*test_custom_class_deprecation.py:1:*"Class" objects in collectors of type "MyModule*',
"*1 passed, 1 warnings in*",
]
)
def test_funcarg_prefix_deprecation(testdir):
testdir.makepyfile(
"""
def pytest_funcarg__value():
return 10
def test_funcarg_prefix(value):
assert value == 10
"""
)
result = testdir.runpytest("-ra", SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
(
"*test_funcarg_prefix_deprecation.py:1: *pytest_funcarg__value: "
'declaring fixtures using "pytest_funcarg__" prefix is deprecated*'
),
"*1 passed*",
]
)
@pytest.mark.filterwarnings("default")
def test_pytest_setup_cfg_deprecated(testdir):
testdir.makefile( testdir.makefile(
".cfg", ".cfg",
setup=""" setup="""
@ -133,14 +18,11 @@ def test_pytest_setup_cfg_deprecated(testdir):
addopts = --verbose addopts = --verbose
""", """,
) )
result = testdir.runpytest() with pytest.raises(pytest.fail.Exception):
result.stdout.fnmatch_lines( testdir.runpytest()
["*pytest*section in setup.cfg files is deprecated*use*tool:pytest*instead*"]
)
@pytest.mark.filterwarnings("default") def test_pytest_custom_cfg_unsupported(testdir):
def test_pytest_custom_cfg_deprecated(testdir):
testdir.makefile( testdir.makefile(
".cfg", ".cfg",
custom=""" custom="""
@ -148,29 +30,8 @@ def test_pytest_custom_cfg_deprecated(testdir):
addopts = --verbose addopts = --verbose
""", """,
) )
result = testdir.runpytest("-c", "custom.cfg") with pytest.raises(pytest.fail.Exception):
result.stdout.fnmatch_lines( testdir.runpytest("-c", "custom.cfg")
["*pytest*section in custom.cfg files is deprecated*use*tool:pytest*instead*"]
)
def test_str_args_deprecated(tmpdir):
"""Deprecate passing strings to pytest.main(). Scheduled for removal in pytest-4.0."""
from _pytest.main import EXIT_NOTESTSCOLLECTED
warnings = []
class Collect(object):
def pytest_warning_captured(self, warning_message):
warnings.append(str(warning_message.message))
ret = pytest.main("%s -x" % tmpdir, plugins=[Collect()])
msg = (
"passing a string to pytest.main() is deprecated, "
"pass a list of arguments instead."
)
assert msg in warnings
assert ret == EXIT_NOTESTSCOLLECTED
def test_getfuncargvalue_is_deprecated(request): def test_getfuncargvalue_is_deprecated(request):
@ -191,29 +52,12 @@ def test_resultlog_is_deprecated(testdir):
result = testdir.runpytest("--result-log=%s" % testdir.tmpdir.join("result.log")) result = testdir.runpytest("--result-log=%s" % testdir.tmpdir.join("result.log"))
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"*--result-log is deprecated and scheduled for removal in pytest 4.0*", "*--result-log is deprecated and scheduled for removal in pytest 5.0*",
"*See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information*", "*See https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log for more information*",
] ]
) )
def test_metafunc_addcall_deprecated(testdir):
testdir.makepyfile(
"""
def pytest_generate_tests(metafunc):
metafunc.addcall({'i': 1})
metafunc.addcall({'i': 2})
def test_func(i):
pass
"""
)
res = testdir.runpytest("-s", SHOW_PYTEST_WARNINGS_ARG)
assert res.ret == 0
res.stdout.fnmatch_lines(
["*Metafunc.addcall is deprecated*", "*2 passed, 2 warnings*"]
)
def test_terminal_reporter_writer_attr(pytestconfig): def test_terminal_reporter_writer_attr(pytestconfig):
"""Check that TerminalReporter._tw is also available as 'writer' (#2984) """Check that TerminalReporter._tw is also available as 'writer' (#2984)
This attribute is planned to be deprecated in 3.4. This attribute is planned to be deprecated in 3.4.
@ -229,6 +73,7 @@ def test_terminal_reporter_writer_attr(pytestconfig):
@pytest.mark.parametrize("plugin", ["catchlog", "capturelog"]) @pytest.mark.parametrize("plugin", ["catchlog", "capturelog"])
@pytest.mark.filterwarnings("default")
def test_pytest_catchlog_deprecated(testdir, plugin): def test_pytest_catchlog_deprecated(testdir, plugin):
testdir.makepyfile( testdir.makepyfile(
""" """
@ -245,6 +90,12 @@ def test_pytest_catchlog_deprecated(testdir, plugin):
) )
def test_raises_message_argument_deprecated():
with pytest.warns(pytest.PytestDeprecationWarning):
with pytest.raises(RuntimeError, message="foobar"):
raise RuntimeError
def test_pytest_plugins_in_non_top_level_conftest_deprecated(testdir): def test_pytest_plugins_in_non_top_level_conftest_deprecated(testdir):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
@ -262,17 +113,15 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated(testdir):
""" """
) )
res = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG) res = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
assert res.ret == 0 assert res.ret == 2
msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0] msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0]
res.stdout.fnmatch_lines( res.stdout.fnmatch_lines(
"*subdirectory{sep}conftest.py:0: RemovedInPytest4Warning: {msg}*".format( ["*{msg}*".format(msg=msg), "*subdirectory{sep}conftest.py*".format(sep=os.sep)]
sep=os.sep, msg=msg
)
) )
@pytest.mark.parametrize("use_pyargs", [True, False]) @pytest.mark.parametrize("use_pyargs", [True, False])
def test_pytest_plugins_in_non_top_level_conftest_deprecated_pyargs( def test_pytest_plugins_in_non_top_level_conftest_unsupported_pyargs(
testdir, use_pyargs testdir, use_pyargs
): ):
"""When using --pyargs, do not emit the warning about non-top-level conftest warnings (#4039, #4044)""" """When using --pyargs, do not emit the warning about non-top-level conftest warnings (#4039, #4044)"""
@ -292,7 +141,7 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_pyargs(
args = ("--pyargs", "pkg") if use_pyargs else () args = ("--pyargs", "pkg") if use_pyargs else ()
args += (SHOW_PYTEST_WARNINGS_ARG,) args += (SHOW_PYTEST_WARNINGS_ARG,)
res = testdir.runpytest(*args) res = testdir.runpytest(*args)
assert res.ret == 0 assert res.ret == (0 if use_pyargs else 2)
msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0] msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0]
if use_pyargs: if use_pyargs:
assert msg not in res.stdout.str() assert msg not in res.stdout.str()
@ -300,7 +149,7 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_pyargs(
res.stdout.fnmatch_lines("*{msg}*".format(msg=msg)) res.stdout.fnmatch_lines("*{msg}*".format(msg=msg))
def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_top_level_conftest( def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_top_level_conftest(
testdir testdir
): ):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
@ -309,8 +158,6 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_top_level_confte
subdirectory.mkdir() subdirectory.mkdir()
testdir.makeconftest( testdir.makeconftest(
""" """
import warnings
warnings.filterwarnings('always', category=DeprecationWarning)
pytest_plugins=['capture'] pytest_plugins=['capture']
""" """
) )
@ -324,16 +171,14 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_top_level_confte
) )
res = testdir.runpytest_subprocess() res = testdir.runpytest_subprocess()
assert res.ret == 0 assert res.ret == 2
msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0] msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0]
res.stdout.fnmatch_lines( res.stdout.fnmatch_lines(
"*subdirectory{sep}conftest.py:0: RemovedInPytest4Warning: {msg}*".format( ["*{msg}*".format(msg=msg), "*subdirectory{sep}conftest.py*".format(sep=os.sep)]
sep=os.sep, msg=msg
)
) )
def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_false_positives( def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_false_positives(
testdir testdir
): ):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
@ -366,37 +211,6 @@ def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_false_positives(
assert msg not in res.stdout.str() assert msg not in res.stdout.str()
def test_call_fixture_function_deprecated():
"""Check if a warning is raised if a fixture function is called directly (#3661)"""
@pytest.fixture
def fix():
return 1
with pytest.deprecated_call():
assert fix() == 1
def test_pycollector_makeitem_is_deprecated():
from _pytest.python import PyCollector
from _pytest.warning_types import RemovedInPytest4Warning
class PyCollectorMock(PyCollector):
"""evil hack"""
def __init__(self):
self.called = False
def _makeitem(self, *k):
"""hack to disable the actual behaviour"""
self.called = True
collector = PyCollectorMock()
with pytest.warns(RemovedInPytest4Warning):
collector.makeitem("foo", "bar")
assert collector.called
def test_fixture_named_request(testdir): def test_fixture_named_request(testdir):
testdir.copy_example() testdir.copy_example()
result = testdir.runpytest() result = testdir.runpytest()

View File

@ -0,0 +1,14 @@
from dataclasses import dataclass
from dataclasses import field
def test_dataclasses():
@dataclass
class SimpleDataObject(object):
field_a: int = field()
field_b: int = field()
left = SimpleDataObject(1, "b")
right = SimpleDataObject(1, "c")
assert left == right

View File

@ -0,0 +1,14 @@
from dataclasses import dataclass
from dataclasses import field
def test_dataclasses_with_attribute_comparison_off():
@dataclass
class SimpleDataObject(object):
field_a: int = field()
field_b: int = field(compare=False)
left = SimpleDataObject(1, "b")
right = SimpleDataObject(1, "c")
assert left == right

View File

@ -0,0 +1,14 @@
from dataclasses import dataclass
from dataclasses import field
def test_dataclasses_verbose():
@dataclass
class SimpleDataObject(object):
field_a: int = field()
field_b: int = field()
left = SimpleDataObject(1, "b")
right = SimpleDataObject(1, "c")
assert left == right

View File

@ -0,0 +1,19 @@
from dataclasses import dataclass
from dataclasses import field
def test_comparing_two_different_data_classes():
@dataclass
class SimpleDataObjectOne(object):
field_a: int = field()
field_b: int = field()
@dataclass
class SimpleDataObjectTwo(object):
field_a: int = field()
field_b: int = field()
left = SimpleDataObjectOne(1, "b")
right = SimpleDataObjectTwo(1, "c")
assert left != right

View File

@ -3,4 +3,4 @@ import pytest
@pytest.fixture @pytest.fixture
def arg2(request): def arg2(request):
pytest.raises(Exception, "request.getfixturevalue('arg1')") pytest.raises(Exception, request.getfixturevalue, "arg1")

View File

@ -496,3 +496,14 @@ class TestApprox(object):
assert actual != approx(expected, rel=5e-8, abs=0) assert actual != approx(expected, rel=5e-8, abs=0)
assert approx(expected, rel=5e-7, abs=0) == actual assert approx(expected, rel=5e-7, abs=0) == actual
assert approx(expected, rel=5e-8, abs=0) != actual assert approx(expected, rel=5e-8, abs=0) != actual
def test_generic_sized_iterable_object(self):
class MySizedIterable(object):
def __iter__(self):
return iter([1, 2, 3, 4])
def __len__(self):
return 4
expected = MySizedIterable()
assert [1, 2, 3, 4] == approx(expected)

View File

@ -7,7 +7,6 @@ import _pytest._code
import pytest import pytest
from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.main import EXIT_NOTESTSCOLLECTED
from _pytest.nodes import Collector from _pytest.nodes import Collector
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
class TestModule(object): class TestModule(object):
@ -244,225 +243,7 @@ class TestClass(object):
@pytest.mark.filterwarnings( @pytest.mark.filterwarnings(
"ignore:usage of Generator.Function is deprecated, please use pytest.Function instead" "ignore:usage of Generator.Function is deprecated, please use pytest.Function instead"
) )
class TestGenerator(object):
def test_generative_functions(self, testdir):
modcol = testdir.getmodulecol(
"""
def func1(arg, arg2):
assert arg == arg2
def test_gen():
yield func1, 17, 3*5
yield func1, 42, 6*7
"""
)
colitems = modcol.collect()
assert len(colitems) == 1
gencol = colitems[0]
assert isinstance(gencol, pytest.Generator)
gencolitems = gencol.collect()
assert len(gencolitems) == 2
assert isinstance(gencolitems[0], pytest.Function)
assert isinstance(gencolitems[1], pytest.Function)
assert gencolitems[0].name == "[0]"
assert gencolitems[0].obj.__name__ == "func1"
def test_generative_methods(self, testdir):
modcol = testdir.getmodulecol(
"""
def func1(arg, arg2):
assert arg == arg2
class TestGenMethods(object):
def test_gen(self):
yield func1, 17, 3*5
yield func1, 42, 6*7
"""
)
gencol = modcol.collect()[0].collect()[0].collect()[0]
assert isinstance(gencol, pytest.Generator)
gencolitems = gencol.collect()
assert len(gencolitems) == 2
assert isinstance(gencolitems[0], pytest.Function)
assert isinstance(gencolitems[1], pytest.Function)
assert gencolitems[0].name == "[0]"
assert gencolitems[0].obj.__name__ == "func1"
def test_generative_functions_with_explicit_names(self, testdir):
modcol = testdir.getmodulecol(
"""
def func1(arg, arg2):
assert arg == arg2
def test_gen():
yield "seventeen", func1, 17, 3*5
yield "fortytwo", func1, 42, 6*7
"""
)
colitems = modcol.collect()
assert len(colitems) == 1
gencol = colitems[0]
assert isinstance(gencol, pytest.Generator)
gencolitems = gencol.collect()
assert len(gencolitems) == 2
assert isinstance(gencolitems[0], pytest.Function)
assert isinstance(gencolitems[1], pytest.Function)
assert gencolitems[0].name == "['seventeen']"
assert gencolitems[0].obj.__name__ == "func1"
assert gencolitems[1].name == "['fortytwo']"
assert gencolitems[1].obj.__name__ == "func1"
def test_generative_functions_unique_explicit_names(self, testdir):
# generative
modcol = testdir.getmodulecol(
"""
def func(): pass
def test_gen():
yield "name", func
yield "name", func
"""
)
colitems = modcol.collect()
assert len(colitems) == 1
gencol = colitems[0]
assert isinstance(gencol, pytest.Generator)
pytest.raises(ValueError, "gencol.collect()")
def test_generative_methods_with_explicit_names(self, testdir):
modcol = testdir.getmodulecol(
"""
def func1(arg, arg2):
assert arg == arg2
class TestGenMethods(object):
def test_gen(self):
yield "m1", func1, 17, 3*5
yield "m2", func1, 42, 6*7
"""
)
gencol = modcol.collect()[0].collect()[0].collect()[0]
assert isinstance(gencol, pytest.Generator)
gencolitems = gencol.collect()
assert len(gencolitems) == 2
assert isinstance(gencolitems[0], pytest.Function)
assert isinstance(gencolitems[1], pytest.Function)
assert gencolitems[0].name == "['m1']"
assert gencolitems[0].obj.__name__ == "func1"
assert gencolitems[1].name == "['m2']"
assert gencolitems[1].obj.__name__ == "func1"
def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir):
o = testdir.makepyfile(
"""
from __future__ import print_function
def test_generative_order_of_execution():
import py, pytest
test_list = []
expected_list = list(range(6))
def list_append(item):
test_list.append(item)
def assert_order_of_execution():
print('expected order', expected_list)
print('but got ', test_list)
assert test_list == expected_list
for i in expected_list:
yield list_append, i
yield assert_order_of_execution
"""
)
reprec = testdir.inline_run(o, SHOW_PYTEST_WARNINGS_ARG)
passed, skipped, failed = reprec.countoutcomes()
assert passed == 7
assert not skipped and not failed
def test_order_of_execution_generator_different_codeline(self, testdir):
o = testdir.makepyfile(
"""
from __future__ import print_function
def test_generative_tests_different_codeline():
import py, pytest
test_list = []
expected_list = list(range(3))
def list_append_2():
test_list.append(2)
def list_append_1():
test_list.append(1)
def list_append_0():
test_list.append(0)
def assert_order_of_execution():
print('expected order', expected_list)
print('but got ', test_list)
assert test_list == expected_list
yield list_append_0
yield list_append_1
yield list_append_2
yield assert_order_of_execution
"""
)
reprec = testdir.inline_run(o, SHOW_PYTEST_WARNINGS_ARG)
passed, skipped, failed = reprec.countoutcomes()
assert passed == 4
assert not skipped and not failed
def test_setupstate_is_preserved_134(self, testdir):
# yield-based tests are messy wrt to setupstate because
# during collection they already invoke setup functions
# and then again when they are run. For now, we want to make sure
# that the old 1.3.4 behaviour is preserved such that all
# yielded functions all share the same "self" instance that
# has been used during collection.
o = testdir.makepyfile(
"""
setuplist = []
class TestClass(object):
def setup_method(self, func):
#print "setup_method", self, func
setuplist.append(self)
self.init = 42
def teardown_method(self, func):
self.init = None
def test_func1(self):
pass
def test_func2(self):
yield self.func2
yield self.func2
def func2(self):
assert self.init
def test_setuplist():
# once for test_func2 during collection
# once for test_func1 during test run
# once for test_func2 during test run
#print setuplist
assert len(setuplist) == 3, len(setuplist)
assert setuplist[0] == setuplist[2], setuplist
assert setuplist[1] != setuplist[2], setuplist
"""
)
reprec = testdir.inline_run(o, "-v", SHOW_PYTEST_WARNINGS_ARG)
passed, skipped, failed = reprec.countoutcomes()
assert passed == 4
assert not skipped and not failed
class TestFunction(object): class TestFunction(object):
@pytest.fixture
def ignore_parametrized_marks_args(self):
"""Provides arguments to pytester.runpytest() to ignore the warning about marks being applied directly
to parameters.
"""
return ("-W", "ignore:Applying marks directly to parameters")
def test_getmodulecollector(self, testdir): def test_getmodulecollector(self, testdir):
item = testdir.getitem("def test_func(): pass") item = testdir.getitem("def test_func(): pass")
modcol = item.getparent(pytest.Module) modcol = item.getparent(pytest.Module)
@ -489,26 +270,34 @@ class TestFunction(object):
] ]
) )
def test_function_equality(self, testdir, tmpdir): @staticmethod
def make_function(testdir, **kwargs):
from _pytest.fixtures import FixtureManager from _pytest.fixtures import FixtureManager
config = testdir.parseconfigure() config = testdir.parseconfigure()
session = testdir.Session(config) session = testdir.Session(config)
session._fixturemanager = FixtureManager(session) session._fixturemanager = FixtureManager(session)
return pytest.Function(config=config, parent=session, **kwargs)
def test_function_equality(self, testdir, tmpdir):
def func1(): def func1():
pass pass
def func2(): def func2():
pass pass
f1 = pytest.Function( f1 = self.make_function(testdir, name="name", args=(1,), callobj=func1)
name="name", parent=session, config=config, args=(1,), callobj=func1
)
assert f1 == f1 assert f1 == f1
f2 = pytest.Function(name="name", config=config, callobj=func2, parent=session) f2 = self.make_function(testdir, name="name", callobj=func2)
assert f1 != f2 assert f1 != f2
def test_repr_produces_actual_test_id(self, testdir):
f = self.make_function(
testdir, name=r"test[\xe5]", callobj=self.test_repr_produces_actual_test_id
)
assert repr(f) == r"<Function test[\xe5]>"
def test_issue197_parametrize_emptyset(self, testdir): def test_issue197_parametrize_emptyset(self, testdir):
testdir.makepyfile( testdir.makepyfile(
""" """
@ -676,7 +465,6 @@ class TestFunction(object):
rec = testdir.inline_run() rec = testdir.inline_run()
rec.assertoutcome(passed=1) rec.assertoutcome(passed=1)
@pytest.mark.filterwarnings("ignore:Applying marks directly to parameters")
def test_parametrize_with_mark(self, testdir): def test_parametrize_with_mark(self, testdir):
items = testdir.getitems( items = testdir.getitems(
""" """
@ -684,7 +472,7 @@ class TestFunction(object):
@pytest.mark.foo @pytest.mark.foo
@pytest.mark.parametrize('arg', [ @pytest.mark.parametrize('arg', [
1, 1,
pytest.mark.bar(pytest.mark.baz(2)) pytest.param(2, marks=[pytest.mark.baz, pytest.mark.bar])
]) ])
def test_function(arg): def test_function(arg):
pass pass
@ -762,37 +550,37 @@ class TestFunction(object):
assert colitems[2].name == "test2[a-c]" assert colitems[2].name == "test2[a-c]"
assert colitems[3].name == "test2[b-c]" assert colitems[3].name == "test2[b-c]"
def test_parametrize_skipif(self, testdir, ignore_parametrized_marks_args): def test_parametrize_skipif(self, testdir):
testdir.makepyfile( testdir.makepyfile(
""" """
import pytest import pytest
m = pytest.mark.skipif('True') m = pytest.mark.skipif('True')
@pytest.mark.parametrize('x', [0, 1, m(2)]) @pytest.mark.parametrize('x', [0, 1, pytest.param(2, marks=m)])
def test_skip_if(x): def test_skip_if(x):
assert x < 2 assert x < 2
""" """
) )
result = testdir.runpytest(*ignore_parametrized_marks_args) result = testdir.runpytest()
result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *") result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *")
def test_parametrize_skip(self, testdir, ignore_parametrized_marks_args): def test_parametrize_skip(self, testdir):
testdir.makepyfile( testdir.makepyfile(
""" """
import pytest import pytest
m = pytest.mark.skip('') m = pytest.mark.skip('')
@pytest.mark.parametrize('x', [0, 1, m(2)]) @pytest.mark.parametrize('x', [0, 1, pytest.param(2, marks=m)])
def test_skip(x): def test_skip(x):
assert x < 2 assert x < 2
""" """
) )
result = testdir.runpytest(*ignore_parametrized_marks_args) result = testdir.runpytest()
result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *") result.stdout.fnmatch_lines("* 2 passed, 1 skipped in *")
def test_parametrize_skipif_no_skip(self, testdir, ignore_parametrized_marks_args): def test_parametrize_skipif_no_skip(self, testdir):
testdir.makepyfile( testdir.makepyfile(
""" """
import pytest import pytest
@ -804,40 +592,40 @@ class TestFunction(object):
assert x < 2 assert x < 2
""" """
) )
result = testdir.runpytest(*ignore_parametrized_marks_args) result = testdir.runpytest()
result.stdout.fnmatch_lines("* 1 failed, 2 passed in *") result.stdout.fnmatch_lines("* 1 failed, 2 passed in *")
def test_parametrize_xfail(self, testdir, ignore_parametrized_marks_args): def test_parametrize_xfail(self, testdir):
testdir.makepyfile( testdir.makepyfile(
""" """
import pytest import pytest
m = pytest.mark.xfail('True') m = pytest.mark.xfail('True')
@pytest.mark.parametrize('x', [0, 1, m(2)]) @pytest.mark.parametrize('x', [0, 1, pytest.param(2, marks=m)])
def test_xfail(x): def test_xfail(x):
assert x < 2 assert x < 2
""" """
) )
result = testdir.runpytest(*ignore_parametrized_marks_args) result = testdir.runpytest()
result.stdout.fnmatch_lines("* 2 passed, 1 xfailed in *") result.stdout.fnmatch_lines("* 2 passed, 1 xfailed in *")
def test_parametrize_passed(self, testdir, ignore_parametrized_marks_args): def test_parametrize_passed(self, testdir):
testdir.makepyfile( testdir.makepyfile(
""" """
import pytest import pytest
m = pytest.mark.xfail('True') m = pytest.mark.xfail('True')
@pytest.mark.parametrize('x', [0, 1, m(2)]) @pytest.mark.parametrize('x', [0, 1, pytest.param(2, marks=m)])
def test_xfail(x): def test_xfail(x):
pass pass
""" """
) )
result = testdir.runpytest(*ignore_parametrized_marks_args) result = testdir.runpytest()
result.stdout.fnmatch_lines("* 2 passed, 1 xpassed in *") result.stdout.fnmatch_lines("* 2 passed, 1 xpassed in *")
def test_parametrize_xfail_passed(self, testdir, ignore_parametrized_marks_args): def test_parametrize_xfail_passed(self, testdir):
testdir.makepyfile( testdir.makepyfile(
""" """
import pytest import pytest
@ -849,7 +637,7 @@ class TestFunction(object):
pass pass
""" """
) )
result = testdir.runpytest(*ignore_parametrized_marks_args) result = testdir.runpytest()
result.stdout.fnmatch_lines("* 3 passed in *") result.stdout.fnmatch_lines("* 3 passed in *")
def test_function_original_name(self, testdir): def test_function_original_name(self, testdir):
@ -1012,7 +800,7 @@ class TestConftestCustomization(object):
modcol = testdir.getmodulecol("def _hello(): pass") modcol = testdir.getmodulecol("def _hello(): pass")
values = [] values = []
monkeypatch.setattr( monkeypatch.setattr(
pytest.Module, "makeitem", lambda self, name, obj: values.append(name) pytest.Module, "_makeitem", lambda self, name, obj: values.append(name)
) )
values = modcol.collect() values = modcol.collect()
assert "_hello" not in values assert "_hello" not in values
@ -1095,7 +883,8 @@ def test_modulecol_roundtrip(testdir):
class TestTracebackCutting(object): class TestTracebackCutting(object):
def test_skip_simple(self): def test_skip_simple(self):
excinfo = pytest.raises(pytest.skip.Exception, 'pytest.skip("xxx")') with pytest.raises(pytest.skip.Exception) as excinfo:
pytest.skip("xxx")
assert excinfo.traceback[-1].frame.code.name == "skip" assert excinfo.traceback[-1].frame.code.name == "skip"
assert excinfo.traceback[-1].ishidden() assert excinfo.traceback[-1].ishidden()
@ -1262,39 +1051,6 @@ class TestReportInfo(object):
@pytest.mark.filterwarnings( @pytest.mark.filterwarnings(
"ignore:usage of Generator.Function is deprecated, please use pytest.Function instead" "ignore:usage of Generator.Function is deprecated, please use pytest.Function instead"
) )
def test_generator_reportinfo(self, testdir):
modcol = testdir.getmodulecol(
"""
# lineno 0
def test_gen():
def check(x):
assert x
yield check, 3
"""
)
gencol = testdir.collect_by_name(modcol, "test_gen")
fspath, lineno, modpath = gencol.reportinfo()
assert fspath == modcol.fspath
assert lineno == 1
assert modpath == "test_gen"
genitem = gencol.collect()[0]
fspath, lineno, modpath = genitem.reportinfo()
assert fspath == modcol.fspath
assert lineno == 2
assert modpath == "test_gen[0]"
"""
def test_func():
pass
def test_genfunc():
def check(x):
pass
yield check, 3
class TestClass(object):
def test_method(self):
pass
"""
def test_reportinfo_with_nasty_getattr(self, testdir): def test_reportinfo_with_nasty_getattr(self, testdir):
# https://github.com/pytest-dev/pytest/issues/1204 # https://github.com/pytest-dev/pytest/issues/1204
modcol = testdir.getmodulecol( modcol = testdir.getmodulecol(
@ -1364,54 +1120,6 @@ def test_customized_python_discovery_functions(testdir):
result.stdout.fnmatch_lines(["*1 passed*"]) result.stdout.fnmatch_lines(["*1 passed*"])
def test_collector_attributes(testdir):
testdir.makeconftest(
"""
import pytest
def pytest_pycollect_makeitem(collector):
assert collector.Function == pytest.Function
assert collector.Class == pytest.Class
assert collector.Instance == pytest.Instance
assert collector.Module == pytest.Module
"""
)
testdir.makepyfile(
"""
def test_hello():
pass
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(["*1 passed*"])
def test_customize_through_attributes(testdir):
testdir.makeconftest(
"""
import pytest
class MyFunction(pytest.Function):
pass
class MyInstance(pytest.Instance):
Function = MyFunction
class MyClass(pytest.Class):
Instance = MyInstance
def pytest_pycollect_makeitem(collector, name, obj):
if name.startswith("MyTestClass"):
return MyClass(name, parent=collector)
"""
)
testdir.makepyfile(
"""
class MyTestClass(object):
def test_hello(self):
pass
"""
)
result = testdir.runpytest("--collect-only", SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(["*MyClass*", "*MyFunction*test_hello*"])
def test_unorderable_types(testdir): def test_unorderable_types(testdir):
testdir.makepyfile( testdir.makepyfile(
""" """

View File

@ -627,25 +627,6 @@ class TestRequestBasic(object):
print(ss.stack) print(ss.stack)
assert teardownlist == [1] assert teardownlist == [1]
def test_mark_as_fixture_with_prefix_and_decorator_fails(self, testdir):
testdir.makeconftest(
"""
import pytest
@pytest.fixture
def pytest_funcarg__marked_with_prefix_and_decorator():
pass
"""
)
result = testdir.runpytest_subprocess()
assert result.ret != 0
result.stdout.fnmatch_lines(
[
"*AssertionError: fixtures cannot have*@pytest.fixture*",
"*pytest_funcarg__marked_with_prefix_and_decorator*",
]
)
def test_request_addfinalizer_failing_setup(self, testdir): def test_request_addfinalizer_failing_setup(self, testdir):
testdir.makepyfile( testdir.makepyfile(
""" """
@ -906,7 +887,8 @@ class TestRequestMarking(object):
assert "skipif" not in item1.keywords assert "skipif" not in item1.keywords
req1.applymarker(pytest.mark.skipif) req1.applymarker(pytest.mark.skipif)
assert "skipif" in item1.keywords assert "skipif" in item1.keywords
pytest.raises(ValueError, "req1.applymarker(42)") with pytest.raises(ValueError):
req1.applymarker(42)
def test_accesskeywords(self, testdir): def test_accesskeywords(self, testdir):
testdir.makepyfile( testdir.makepyfile(
@ -952,181 +934,6 @@ class TestRequestMarking(object):
reprec.assertoutcome(passed=2) reprec.assertoutcome(passed=2)
class TestRequestCachedSetup(object):
def test_request_cachedsetup_defaultmodule(self, testdir):
reprec = testdir.inline_runsource(
"""
mysetup = ["hello",].pop
import pytest
@pytest.fixture
def something(request):
return request.cached_setup(mysetup, scope="module")
def test_func1(something):
assert something == "hello"
class TestClass(object):
def test_func1a(self, something):
assert something == "hello"
""",
SHOW_PYTEST_WARNINGS_ARG,
)
reprec.assertoutcome(passed=2)
def test_request_cachedsetup_class(self, testdir):
reprec = testdir.inline_runsource(
"""
mysetup = ["hello", "hello2", "hello3"].pop
import pytest
@pytest.fixture
def something(request):
return request.cached_setup(mysetup, scope="class")
def test_func1(something):
assert something == "hello3"
def test_func2(something):
assert something == "hello2"
class TestClass(object):
def test_func1a(self, something):
assert something == "hello"
def test_func2b(self, something):
assert something == "hello"
""",
SHOW_PYTEST_WARNINGS_ARG,
)
reprec.assertoutcome(passed=4)
@pytest.mark.filterwarnings("ignore:cached_setup is deprecated")
def test_request_cachedsetup_extrakey(self, testdir):
item1 = testdir.getitem("def test_func(): pass")
req1 = fixtures.FixtureRequest(item1)
values = ["hello", "world"]
def setup():
return values.pop()
ret1 = req1.cached_setup(setup, extrakey=1)
ret2 = req1.cached_setup(setup, extrakey=2)
assert ret2 == "hello"
assert ret1 == "world"
ret1b = req1.cached_setup(setup, extrakey=1)
ret2b = req1.cached_setup(setup, extrakey=2)
assert ret1 == ret1b
assert ret2 == ret2b
@pytest.mark.filterwarnings("ignore:cached_setup is deprecated")
def test_request_cachedsetup_cache_deletion(self, testdir):
item1 = testdir.getitem("def test_func(): pass")
req1 = fixtures.FixtureRequest(item1)
values = []
def setup():
values.append("setup")
def teardown(val):
values.append("teardown")
req1.cached_setup(setup, teardown, scope="function")
assert values == ["setup"]
# artificial call of finalizer
setupstate = req1._pyfuncitem.session._setupstate
setupstate._callfinalizers(item1)
assert values == ["setup", "teardown"]
req1.cached_setup(setup, teardown, scope="function")
assert values == ["setup", "teardown", "setup"]
setupstate._callfinalizers(item1)
assert values == ["setup", "teardown", "setup", "teardown"]
def test_request_cached_setup_two_args(self, testdir):
testdir.makepyfile(
"""
import pytest
@pytest.fixture
def arg1(request):
return request.cached_setup(lambda: 42)
@pytest.fixture
def arg2(request):
return request.cached_setup(lambda: 17)
def test_two_different_setups(arg1, arg2):
assert arg1 != arg2
"""
)
result = testdir.runpytest("-v", SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(["*1 passed*"])
def test_request_cached_setup_getfixturevalue(self, testdir):
testdir.makepyfile(
"""
import pytest
@pytest.fixture
def arg1(request):
arg1 = request.getfixturevalue("arg2")
return request.cached_setup(lambda: arg1 + 1)
@pytest.fixture
def arg2(request):
return request.cached_setup(lambda: 10)
def test_two_funcarg(arg1):
assert arg1 == 11
"""
)
result = testdir.runpytest("-v", SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(["*1 passed*"])
def test_request_cached_setup_functional(self, testdir):
testdir.makepyfile(
test_0="""
import pytest
values = []
@pytest.fixture
def something(request):
val = request.cached_setup(fsetup, fteardown)
return val
def fsetup(mycache=[1]):
values.append(mycache.pop())
return values
def fteardown(something):
values.remove(something[0])
values.append(2)
def test_list_once(something):
assert something == [1]
def test_list_twice(something):
assert something == [1]
"""
)
testdir.makepyfile(
test_1="""
import test_0 # should have run already
def test_check_test0_has_teardown_correct():
assert test_0.values == [2]
"""
)
result = testdir.runpytest("-v", SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(["*3 passed*"])
def test_issue117_sessionscopeteardown(self, testdir):
testdir.makepyfile(
"""
import pytest
@pytest.fixture
def app(request):
app = request.cached_setup(
scope='session',
setup=lambda: 0,
teardown=lambda x: 3/x)
return app
def test_func(app):
pass
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
assert result.ret != 0
result.stdout.fnmatch_lines(["*3/x*", "*ZeroDivisionError*"])
class TestFixtureUsages(object): class TestFixtureUsages(object):
def test_noargfixturedec(self, testdir): def test_noargfixturedec(self, testdir):
testdir.makepyfile( testdir.makepyfile(
@ -1849,24 +1656,6 @@ class TestAutouseManagement(object):
reprec = testdir.inline_run("-s") reprec = testdir.inline_run("-s")
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
def test_autouse_honored_for_yield(self, testdir):
testdir.makepyfile(
"""
import pytest
@pytest.fixture(autouse=True)
def tst():
global x
x = 3
def test_gen():
def f(hello):
assert x == abs(hello)
yield f, 3
yield f, -3
"""
)
reprec = testdir.inline_run(SHOW_PYTEST_WARNINGS_ARG)
reprec.assertoutcome(passed=2)
def test_funcarg_and_setup(self, testdir): def test_funcarg_and_setup(self, testdir):
testdir.makepyfile( testdir.makepyfile(
""" """
@ -2314,15 +2103,7 @@ class TestFixtureMarker(object):
reprec = testdir.inline_run() reprec = testdir.inline_run()
reprec.assertoutcome(passed=4) reprec.assertoutcome(passed=4)
@pytest.mark.parametrize( def test_scope_mismatch_various(self, testdir):
"method",
[
'request.getfixturevalue("arg")',
'request.cached_setup(lambda: None, scope="function")',
],
ids=["getfixturevalue", "cached_setup"],
)
def test_scope_mismatch_various(self, testdir, method):
testdir.makeconftest( testdir.makeconftest(
""" """
import pytest import pytest
@ -2338,11 +2119,10 @@ class TestFixtureMarker(object):
import pytest import pytest
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
def arg(request): def arg(request):
%s request.getfixturevalue("arg")
def test_1(arg): def test_1(arg):
pass pass
""" """
% method
) )
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG) result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
assert result.ret != 0 assert result.ret != 0
@ -4070,3 +3850,14 @@ class TestScopeOrdering(object):
) )
reprec = testdir.inline_run() reprec = testdir.inline_run()
reprec.assertoutcome(passed=2) reprec.assertoutcome(passed=2)
def test_call_fixture_function_error():
"""Check if an error is raised if a fixture function is called directly (#4545)"""
@pytest.fixture
def fix():
return 1
with pytest.raises(pytest.fail.Exception):
assert fix() == 1

View File

@ -5,6 +5,7 @@ import textwrap
import attr import attr
import hypothesis import hypothesis
import six
from hypothesis import strategies from hypothesis import strategies
import pytest import pytest
@ -53,66 +54,6 @@ class TestMetafunc(object):
assert metafunc.function is func assert metafunc.function is func
assert metafunc.cls is None assert metafunc.cls is None
def test_addcall_no_args(self):
def func(arg1):
pass
metafunc = self.Metafunc(func)
metafunc.addcall()
assert len(metafunc._calls) == 1
call = metafunc._calls[0]
assert call.id == "0"
assert not hasattr(call, "param")
def test_addcall_id(self):
def func(arg1):
pass
metafunc = self.Metafunc(func)
pytest.raises(ValueError, "metafunc.addcall(id=None)")
metafunc.addcall(id=1)
pytest.raises(ValueError, "metafunc.addcall(id=1)")
pytest.raises(ValueError, "metafunc.addcall(id='1')")
metafunc.addcall(id=2)
assert len(metafunc._calls) == 2
assert metafunc._calls[0].id == "1"
assert metafunc._calls[1].id == "2"
def test_addcall_param(self):
def func(arg1):
pass
metafunc = self.Metafunc(func)
class obj(object):
pass
metafunc.addcall(param=obj)
metafunc.addcall(param=obj)
metafunc.addcall(param=1)
assert len(metafunc._calls) == 3
assert metafunc._calls[0].getparam("arg1") == obj
assert metafunc._calls[1].getparam("arg1") == obj
assert metafunc._calls[2].getparam("arg1") == 1
def test_addcall_funcargs(self):
def func(x):
pass
metafunc = self.Metafunc(func)
class obj(object):
pass
metafunc.addcall(funcargs={"x": 2})
metafunc.addcall(funcargs={"x": 3})
pytest.raises(pytest.fail.Exception, "metafunc.addcall({'xyz': 0})")
assert len(metafunc._calls) == 2
assert metafunc._calls[0].funcargs == {"x": 2}
assert metafunc._calls[1].funcargs == {"x": 3}
assert not hasattr(metafunc._calls[1], "param")
def test_parametrize_error(self): def test_parametrize_error(self):
def func(x, y): def func(x, y):
pass pass
@ -262,11 +203,8 @@ class TestMetafunc(object):
from _pytest.python import _idval from _pytest.python import _idval
escaped = _idval(value, "a", 6, None, item=None, config=None) escaped = _idval(value, "a", 6, None, item=None, config=None)
assert isinstance(escaped, str) assert isinstance(escaped, six.text_type)
if PY3: escaped.encode("ascii")
escaped.encode("ascii")
else:
escaped.decode("ascii")
def test_unicode_idval(self): def test_unicode_idval(self):
"""This tests that Unicode strings outside the ASCII character set get """This tests that Unicode strings outside the ASCII character set get
@ -382,6 +320,34 @@ class TestMetafunc(object):
"\\xc3\\xb4-other", "\\xc3\\xb4-other",
] ]
def test_idmaker_non_printable_characters(self):
from _pytest.python import idmaker
result = idmaker(
("s", "n"),
[
pytest.param("\x00", 1),
pytest.param("\x05", 2),
pytest.param(b"\x00", 3),
pytest.param(b"\x05", 4),
pytest.param("\t", 5),
pytest.param(b"\t", 6),
],
)
assert result == ["\\x00-1", "\\x05-2", "\\x00-3", "\\x05-4", "\\t-5", "\\t-6"]
def test_idmaker_manual_ids_must_be_printable(self):
from _pytest.python import idmaker
result = idmaker(
("s",),
[
pytest.param("x00", id="hello \x00"),
pytest.param("x05", id="hello \x05"),
],
)
assert result == ["hello \\x00", "hello \\x05"]
def test_idmaker_enum(self): def test_idmaker_enum(self):
from _pytest.python import idmaker from _pytest.python import idmaker
@ -427,7 +393,6 @@ class TestMetafunc(object):
) )
assert result == ["a-a0", "a-a1", "a-a2"] assert result == ["a-a0", "a-a1", "a-a2"]
@pytest.mark.filterwarnings("default")
def test_parametrize_ids_exception(self, testdir): def test_parametrize_ids_exception(self, testdir):
""" """
:param testdir: the instance of Testdir class, a temporary :param testdir: the instance of Testdir class, a temporary
@ -445,14 +410,11 @@ class TestMetafunc(object):
pass pass
""" """
) )
result = testdir.runpytest("--collect-only", SHOW_PYTEST_WARNINGS_ARG) result = testdir.runpytest()
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"<Module 'test_parametrize_ids_exception.py'>", "*test_foo: error raised while trying to determine id of parameter 'arg' at position 0",
" <Function 'test_foo[a]'>", "*Exception: bad ids",
" <Function 'test_foo[b]'>",
"*test_parametrize_ids_exception.py:6: *parameter arg at position 0*",
"*test_parametrize_ids_exception.py:6: *parameter arg at position 1*",
] ]
) )
@ -482,19 +444,6 @@ class TestMetafunc(object):
) )
assert result == ["a0", "a1", "b0", "c", "b1"] assert result == ["a0", "a1", "b0", "c", "b1"]
def test_addcall_and_parametrize(self):
def func(x, y):
pass
metafunc = self.Metafunc(func)
metafunc.addcall({"x": 1})
metafunc.parametrize("y", [2, 3])
assert len(metafunc._calls) == 2
assert metafunc._calls[0].funcargs == {"x": 1, "y": 2}
assert metafunc._calls[1].funcargs == {"x": 1, "y": 3}
assert metafunc._calls[0].id == "0-2"
assert metafunc._calls[1].id == "0-3"
@pytest.mark.issue714 @pytest.mark.issue714
def test_parametrize_indirect(self): def test_parametrize_indirect(self):
def func(x, y): def func(x, y):
@ -684,20 +633,6 @@ class TestMetafunc(object):
["*already takes an argument 'y' with a default value"] ["*already takes an argument 'y' with a default value"]
) )
def test_addcalls_and_parametrize_indirect(self):
def func(x, y):
pass
metafunc = self.Metafunc(func)
metafunc.addcall(param="123")
metafunc.parametrize("x", [1], indirect=True)
metafunc.parametrize("y", [2, 3], indirect=True)
assert len(metafunc._calls) == 2
assert metafunc._calls[0].funcargs == {}
assert metafunc._calls[1].funcargs == {}
assert metafunc._calls[0].params == dict(x=1, y=2)
assert metafunc._calls[1].params == dict(x=1, y=3)
def test_parametrize_functional(self, testdir): def test_parametrize_functional(self, testdir):
testdir.makepyfile( testdir.makepyfile(
""" """
@ -845,7 +780,7 @@ class TestMetafuncFunctional(object):
# assumes that generate/provide runs in the same process # assumes that generate/provide runs in the same process
import sys, pytest, six import sys, pytest, six
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
metafunc.addcall(param=metafunc) metafunc.parametrize('metafunc', [metafunc])
@pytest.fixture @pytest.fixture
def metafunc(request): def metafunc(request):
@ -870,43 +805,15 @@ class TestMetafuncFunctional(object):
result = testdir.runpytest(p, "-v", SHOW_PYTEST_WARNINGS_ARG) result = testdir.runpytest(p, "-v", SHOW_PYTEST_WARNINGS_ARG)
result.assert_outcomes(passed=2) result.assert_outcomes(passed=2)
def test_addcall_with_two_funcargs_generators(self, testdir):
testdir.makeconftest(
"""
def pytest_generate_tests(metafunc):
assert "arg1" in metafunc.fixturenames
metafunc.addcall(funcargs=dict(arg1=1, arg2=2))
"""
)
p = testdir.makepyfile(
"""
def pytest_generate_tests(metafunc):
metafunc.addcall(funcargs=dict(arg1=1, arg2=1))
class TestClass(object):
def test_myfunc(self, arg1, arg2):
assert arg1 == arg2
"""
)
result = testdir.runpytest("-v", p, SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
["*test_myfunc*0*PASS*", "*test_myfunc*1*FAIL*", "*1 failed, 1 passed*"]
)
def test_two_functions(self, testdir): def test_two_functions(self, testdir):
p = testdir.makepyfile( p = testdir.makepyfile(
""" """
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
metafunc.addcall(param=10) metafunc.parametrize('arg1', [10, 20], ids=['0', '1'])
metafunc.addcall(param=20)
import pytest
@pytest.fixture
def arg1(request):
return request.param
def test_func1(arg1): def test_func1(arg1):
assert arg1 == 10 assert arg1 == 10
def test_func2(arg1): def test_func2(arg1):
assert arg1 in (10, 20) assert arg1 in (10, 20)
""" """
@ -917,6 +824,7 @@ class TestMetafuncFunctional(object):
"*test_func1*0*PASS*", "*test_func1*0*PASS*",
"*test_func1*1*FAIL*", "*test_func1*1*FAIL*",
"*test_func2*PASS*", "*test_func2*PASS*",
"*test_func2*PASS*",
"*1 failed, 3 passed*", "*1 failed, 3 passed*",
] ]
) )
@ -935,47 +843,12 @@ class TestMetafuncFunctional(object):
result = testdir.runpytest(p) result = testdir.runpytest(p)
result.assert_outcomes(passed=1) result.assert_outcomes(passed=1)
def test_generate_plugin_and_module(self, testdir):
testdir.makeconftest(
"""
def pytest_generate_tests(metafunc):
assert "arg1" in metafunc.fixturenames
metafunc.addcall(id="world", param=(2,100))
"""
)
p = testdir.makepyfile(
"""
def pytest_generate_tests(metafunc):
metafunc.addcall(param=(1,1), id="hello")
import pytest
@pytest.fixture
def arg1(request):
return request.param[0]
@pytest.fixture
def arg2(request):
return request.param[1]
class TestClass(object):
def test_myfunc(self, arg1, arg2):
assert arg1 == arg2
"""
)
result = testdir.runpytest("-v", p, SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
[
"*test_myfunc*hello*PASS*",
"*test_myfunc*world*FAIL*",
"*1 failed, 1 passed*",
]
)
def test_generate_tests_in_class(self, testdir): def test_generate_tests_in_class(self, testdir):
p = testdir.makepyfile( p = testdir.makepyfile(
""" """
class TestClass(object): class TestClass(object):
def pytest_generate_tests(self, metafunc): def pytest_generate_tests(self, metafunc):
metafunc.addcall(funcargs={'hello': 'world'}, id="hello") metafunc.parametrize('hello', ['world'], ids=['hellow'])
def test_myfunc(self, hello): def test_myfunc(self, hello):
assert hello == "world" assert hello == "world"
@ -988,8 +861,7 @@ class TestMetafuncFunctional(object):
p = testdir.makepyfile( p = testdir.makepyfile(
""" """
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
metafunc.addcall({'arg1': 10}) metafunc.parametrize('arg1', [10, 20], ids=["0", "1"])
metafunc.addcall({'arg1': 20})
class TestClass(object): class TestClass(object):
def test_func(self, arg1): def test_func(self, arg1):
@ -1006,7 +878,7 @@ class TestMetafuncFunctional(object):
p = testdir.makepyfile( p = testdir.makepyfile(
""" """
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
metafunc.addcall({'arg1': 1}) metafunc.parametrize('arg1', [1])
class TestClass(object): class TestClass(object):
def test_method(self, arg1): def test_method(self, arg1):
@ -1500,7 +1372,6 @@ class TestMetafuncFunctionalAuto(object):
assert output.count("preparing foo-3") == 1 assert output.count("preparing foo-3") == 1
@pytest.mark.filterwarnings("ignore:Applying marks directly to parameters")
@pytest.mark.issue308 @pytest.mark.issue308
class TestMarkersWithParametrization(object): class TestMarkersWithParametrization(object):
def test_simple_mark(self, testdir): def test_simple_mark(self, testdir):
@ -1510,7 +1381,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.foo @pytest.mark.foo
@pytest.mark.parametrize(("n", "expected"), [ @pytest.mark.parametrize(("n", "expected"), [
(1, 2), (1, 2),
pytest.mark.bar((1, 3)), pytest.param(1, 3, marks=pytest.mark.bar),
(2, 3), (2, 3),
]) ])
def test_increment(n, expected): def test_increment(n, expected):
@ -1530,7 +1401,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.parametrize(("n", "expected"), [ @pytest.mark.parametrize(("n", "expected"), [
(1, 2), (1, 2),
pytest.mark.foo((2, 3)), pytest.param(2, 3, marks=pytest.mark.foo),
(3, 4), (3, 4),
]) ])
def test_increment(n, expected): def test_increment(n, expected):
@ -1570,7 +1441,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.parametrize(("n", "expected"), [ @pytest.mark.parametrize(("n", "expected"), [
(1, 2), (1, 2),
pytest.mark.xfail((1, 3)), pytest.param(1, 3, marks=pytest.mark.xfail),
(2, 3), (2, 3),
]) ])
def test_increment(n, expected): def test_increment(n, expected):
@ -1587,7 +1458,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.parametrize("n", [ @pytest.mark.parametrize("n", [
2, 2,
pytest.mark.xfail(3), pytest.param(3, marks=pytest.mark.xfail),
4, 4,
]) ])
def test_isEven(n): def test_isEven(n):
@ -1603,7 +1474,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.parametrize(("n", "expected"), [ @pytest.mark.parametrize(("n", "expected"), [
(1, 2), (1, 2),
pytest.mark.xfail("True")((1, 3)), pytest.param(1, 3, marks=pytest.mark.xfail("True")),
(2, 3), (2, 3),
]) ])
def test_increment(n, expected): def test_increment(n, expected):
@ -1619,7 +1490,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.parametrize(("n", "expected"), [ @pytest.mark.parametrize(("n", "expected"), [
(1, 2), (1, 2),
pytest.mark.xfail(reason="some bug")((1, 3)), pytest.param(1, 3, marks=pytest.mark.xfail(reason="some bug")),
(2, 3), (2, 3),
]) ])
def test_increment(n, expected): def test_increment(n, expected):
@ -1635,7 +1506,7 @@ class TestMarkersWithParametrization(object):
@pytest.mark.parametrize(("n", "expected"), [ @pytest.mark.parametrize(("n", "expected"), [
(1, 2), (1, 2),
pytest.mark.xfail("True", reason="some bug")((1, 3)), pytest.param(1, 3, marks=pytest.mark.xfail("True", reason="some bug")),
(2, 3), (2, 3),
]) ])
def test_increment(n, expected): def test_increment(n, expected):
@ -1650,9 +1521,11 @@ class TestMarkersWithParametrization(object):
s = """ s = """
import pytest import pytest
m = pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict})
@pytest.mark.parametrize(("n", "expected"), [ @pytest.mark.parametrize(("n", "expected"), [
(1, 2), (1, 2),
pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict})((2, 3)), pytest.param(2, 3, marks=m),
(3, 4), (3, 4),
]) ])
def test_increment(n, expected): def test_increment(n, expected):
@ -1676,7 +1549,7 @@ class TestMarkersWithParametrization(object):
failingTestData = [(1, 3), failingTestData = [(1, 3),
(2, 2)] (2, 2)]
testData = passingTestData + [pytest.mark.xfail(d) testData = passingTestData + [pytest.param(*d, marks=pytest.mark.xfail)
for d in failingTestData] for d in failingTestData]
metafunc.parametrize(("n", "expected"), testData) metafunc.parametrize(("n", "expected"), testData)

View File

@ -4,25 +4,32 @@ import six
import pytest import pytest
from _pytest.outcomes import Failed from _pytest.outcomes import Failed
from _pytest.warning_types import PytestDeprecationWarning
class TestRaises(object): class TestRaises(object):
def test_raises(self): def test_raises(self):
source = "int('qwe')" source = "int('qwe')"
excinfo = pytest.raises(ValueError, source) with pytest.warns(PytestDeprecationWarning):
excinfo = pytest.raises(ValueError, source)
code = excinfo.traceback[-1].frame.code code = excinfo.traceback[-1].frame.code
s = str(code.fullsource) s = str(code.fullsource)
assert s == source assert s == source
def test_raises_exec(self): def test_raises_exec(self):
pytest.raises(ValueError, "a,x = []") with pytest.warns(PytestDeprecationWarning) as warninfo:
pytest.raises(ValueError, "a,x = []")
assert warninfo[0].filename == __file__
def test_raises_exec_correct_filename(self): def test_raises_exec_correct_filename(self):
excinfo = pytest.raises(ValueError, 'int("s")') with pytest.warns(PytestDeprecationWarning):
assert __file__ in excinfo.traceback[-1].path excinfo = pytest.raises(ValueError, 'int("s")')
assert __file__ in excinfo.traceback[-1].path
def test_raises_syntax_error(self): def test_raises_syntax_error(self):
pytest.raises(SyntaxError, "qwe qwe qwe") with pytest.warns(PytestDeprecationWarning) as warninfo:
pytest.raises(SyntaxError, "qwe qwe qwe")
assert warninfo[0].filename == __file__
def test_raises_function(self): def test_raises_function(self):
pytest.raises(ValueError, int, "hello") pytest.raises(ValueError, int, "hello")
@ -119,8 +126,9 @@ class TestRaises(object):
def test_custom_raise_message(self): def test_custom_raise_message(self):
message = "TEST_MESSAGE" message = "TEST_MESSAGE"
try: try:
with pytest.raises(ValueError, message=message): with pytest.warns(PytestDeprecationWarning):
pass with pytest.raises(ValueError, message=message):
pass
except pytest.raises.Exception as e: except pytest.raises.Exception as e:
assert e.msg == message assert e.msg == message
else: else:

View File

@ -6,6 +6,7 @@ from __future__ import print_function
import sys import sys
import textwrap import textwrap
import attr
import py import py
import six import six
@ -549,6 +550,115 @@ class TestAssert_reprcompare(object):
assert msg assert msg
class TestAssert_reprcompare_dataclass(object):
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_dataclasses(self, testdir):
p = testdir.copy_example("dataclasses/test_compare_dataclasses.py")
result = testdir.runpytest(p)
result.assert_outcomes(failed=1, passed=0)
result.stdout.fnmatch_lines(
[
"*Omitting 1 identical items, use -vv to show*",
"*Differing attributes:*",
"*field_b: 'b' != 'c'*",
]
)
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_dataclasses_verbose(self, testdir):
p = testdir.copy_example("dataclasses/test_compare_dataclasses_verbose.py")
result = testdir.runpytest(p, "-vv")
result.assert_outcomes(failed=1, passed=0)
result.stdout.fnmatch_lines(
[
"*Matching attributes:*",
"*['field_a']*",
"*Differing attributes:*",
"*field_b: 'b' != 'c'*",
]
)
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_dataclasses_with_attribute_comparison_off(self, testdir):
p = testdir.copy_example(
"dataclasses/test_compare_dataclasses_field_comparison_off.py"
)
result = testdir.runpytest(p, "-vv")
result.assert_outcomes(failed=0, passed=1)
@pytest.mark.skipif(sys.version_info < (3, 7), reason="Dataclasses in Python3.7+")
def test_comparing_two_different_data_classes(self, testdir):
p = testdir.copy_example(
"dataclasses/test_compare_two_different_dataclasses.py"
)
result = testdir.runpytest(p, "-vv")
result.assert_outcomes(failed=0, passed=1)
class TestAssert_reprcompare_attrsclass(object):
def test_attrs(self):
@attr.s
class SimpleDataObject(object):
field_a = attr.ib()
field_b = attr.ib()
left = SimpleDataObject(1, "b")
right = SimpleDataObject(1, "c")
lines = callequal(left, right)
assert lines[1].startswith("Omitting 1 identical item")
assert "Matching attributes" not in lines
for line in lines[1:]:
assert "field_a" not in line
def test_attrs_verbose(self):
@attr.s
class SimpleDataObject(object):
field_a = attr.ib()
field_b = attr.ib()
left = SimpleDataObject(1, "b")
right = SimpleDataObject(1, "c")
lines = callequal(left, right, verbose=2)
assert lines[1].startswith("Matching attributes:")
assert "Omitting" not in lines[1]
assert lines[2] == "['field_a']"
def test_attrs_with_attribute_comparison_off(self):
@attr.s
class SimpleDataObject(object):
field_a = attr.ib()
field_b = attr.ib(cmp=False)
left = SimpleDataObject(1, "b")
right = SimpleDataObject(1, "b")
lines = callequal(left, right, verbose=2)
assert lines[1].startswith("Matching attributes:")
assert "Omitting" not in lines[1]
assert lines[2] == "['field_a']"
for line in lines[2:]:
assert "field_b" not in line
def test_comparing_two_different_attrs_classes(self):
@attr.s
class SimpleDataObjectOne(object):
field_a = attr.ib()
field_b = attr.ib()
@attr.s
class SimpleDataObjectTwo(object):
field_a = attr.ib()
field_b = attr.ib()
left = SimpleDataObjectOne(1, "b")
right = SimpleDataObjectTwo(1, "c")
lines = callequal(left, right)
assert lines is None
class TestFormatExplanation(object): class TestFormatExplanation(object):
def test_special_chars_full(self, testdir): def test_special_chars_full(self, testdir):
# Issue 453, for the bug this would raise IndexError # Issue 453, for the bug this would raise IndexError

View File

@ -823,7 +823,9 @@ def test_rewritten():
testdir.makepyfile(test_remember_rewritten_modules="") testdir.makepyfile(test_remember_rewritten_modules="")
warnings = [] warnings = []
hook = AssertionRewritingHook(pytestconfig) hook = AssertionRewritingHook(pytestconfig)
monkeypatch.setattr(hook.config, "warn", lambda code, msg: warnings.append(msg)) monkeypatch.setattr(
hook, "_warn_already_imported", lambda code, msg: warnings.append(msg)
)
hook.find_module("test_remember_rewritten_modules") hook.find_module("test_remember_rewritten_modules")
hook.load_module("test_remember_rewritten_modules") hook.load_module("test_remember_rewritten_modules")
hook.mark_rewrite("test_remember_rewritten_modules") hook.mark_rewrite("test_remember_rewritten_modules")

View File

@ -925,3 +925,15 @@ def test_does_not_create_boilerplate_in_existing_dirs(testdir):
assert os.path.isdir("v") # cache contents assert os.path.isdir("v") # cache contents
assert not os.path.exists(".gitignore") assert not os.path.exists(".gitignore")
assert not os.path.exists("README.md") assert not os.path.exists("README.md")
def test_cachedir_tag(testdir):
"""Ensure we automatically create CACHEDIR.TAG file in the pytest_cache directory (#4278)."""
from _pytest.cacheprovider import Cache
from _pytest.cacheprovider import CACHEDIR_TAG_CONTENT
config = testdir.parseconfig()
cache = Cache.for_config(config)
cache.set("foo", "bar")
cachedir_tag_path = cache._cachedir.joinpath("CACHEDIR.TAG")
assert cachedir_tag_path.read_bytes() == CACHEDIR_TAG_CONTENT

View File

@ -87,7 +87,7 @@ class TestCaptureManager(object):
try: try:
capman = CaptureManager("fd") capman = CaptureManager("fd")
capman.start_global_capturing() capman.start_global_capturing()
pytest.raises(AssertionError, "capman.start_global_capturing()") pytest.raises(AssertionError, capman.start_global_capturing)
capman.stop_global_capturing() capman.stop_global_capturing()
finally: finally:
capouter.stop_capturing() capouter.stop_capturing()
@ -832,10 +832,10 @@ class TestCaptureIO(object):
f = capture.CaptureIO() f = capture.CaptureIO()
if sys.version_info >= (3, 0): if sys.version_info >= (3, 0):
f.write("\u00f6") f.write("\u00f6")
pytest.raises(TypeError, "f.write(bytes('hello', 'UTF-8'))") pytest.raises(TypeError, f.write, b"hello")
else: else:
f.write(text_type("\u00f6", "UTF-8")) f.write(u"\u00f6")
f.write("hello") # bytes f.write(b"hello")
s = f.getvalue() s = f.getvalue()
f.close() f.close()
assert isinstance(s, text_type) assert isinstance(s, text_type)
@ -1183,7 +1183,7 @@ class TestStdCapture(object):
print("XXX which indicates an error in the underlying capturing") print("XXX which indicates an error in the underlying capturing")
print("XXX mechanisms") print("XXX mechanisms")
with self.getcapture(): with self.getcapture():
pytest.raises(IOError, "sys.stdin.read()") pytest.raises(IOError, sys.stdin.read)
class TestStdCaptureFD(TestStdCapture): class TestStdCaptureFD(TestStdCapture):

View File

@ -21,20 +21,6 @@ class TestCollector(object):
assert not issubclass(Collector, Item) assert not issubclass(Collector, Item)
assert not issubclass(Item, Collector) assert not issubclass(Item, Collector)
def test_compat_attributes(self, testdir, recwarn):
modcol = testdir.getmodulecol(
"""
def test_pass(): pass
def test_fail(): assert 0
"""
)
recwarn.clear()
assert modcol.Module == pytest.Module
assert modcol.Class == pytest.Class
assert modcol.Item == pytest.Item
assert modcol.File == pytest.File
assert modcol.Function == pytest.Function
def test_check_equality(self, testdir): def test_check_equality(self, testdir):
modcol = testdir.getmodulecol( modcol = testdir.getmodulecol(
""" """
@ -950,10 +936,10 @@ def test_collect_init_tests(testdir):
[ [
"collected 2 items", "collected 2 items",
"<Package *", "<Package *",
" <Module '__init__.py'>", " <Module __init__.py>",
" <Function 'test_init'>", " <Function test_init>",
" <Module 'test_foo.py'>", " <Module test_foo.py>",
" <Function 'test_foo'>", " <Function test_foo>",
] ]
) )
result = testdir.runpytest("./tests", "--collect-only") result = testdir.runpytest("./tests", "--collect-only")
@ -961,10 +947,10 @@ def test_collect_init_tests(testdir):
[ [
"collected 2 items", "collected 2 items",
"<Package *", "<Package *",
" <Module '__init__.py'>", " <Module __init__.py>",
" <Function 'test_init'>", " <Function test_init>",
" <Module 'test_foo.py'>", " <Module test_foo.py>",
" <Function 'test_foo'>", " <Function test_foo>",
] ]
) )
# Ignores duplicates with "." and pkginit (#4310). # Ignores duplicates with "." and pkginit (#4310).
@ -972,11 +958,11 @@ def test_collect_init_tests(testdir):
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"collected 2 items", "collected 2 items",
"<Package */tests'>", "<Package */tests>",
" <Module '__init__.py'>", " <Module __init__.py>",
" <Function 'test_init'>", " <Function test_init>",
" <Module 'test_foo.py'>", " <Module test_foo.py>",
" <Function 'test_foo'>", " <Function test_foo>",
] ]
) )
# Same as before, but different order. # Same as before, but different order.
@ -984,21 +970,21 @@ def test_collect_init_tests(testdir):
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"collected 2 items", "collected 2 items",
"<Package */tests'>", "<Package */tests>",
" <Module '__init__.py'>", " <Module __init__.py>",
" <Function 'test_init'>", " <Function test_init>",
" <Module 'test_foo.py'>", " <Module test_foo.py>",
" <Function 'test_foo'>", " <Function test_foo>",
] ]
) )
result = testdir.runpytest("./tests/test_foo.py", "--collect-only") result = testdir.runpytest("./tests/test_foo.py", "--collect-only")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
["<Package */tests'>", " <Module 'test_foo.py'>", " <Function 'test_foo'>"] ["<Package */tests>", " <Module test_foo.py>", " <Function test_foo>"]
) )
assert "test_init" not in result.stdout.str() assert "test_init" not in result.stdout.str()
result = testdir.runpytest("./tests/__init__.py", "--collect-only") result = testdir.runpytest("./tests/__init__.py", "--collect-only")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
["<Package */tests'>", " <Module '__init__.py'>", " <Function 'test_init'>"] ["<Package */tests>", " <Module __init__.py>", " <Function test_init>"]
) )
assert "test_foo" not in result.stdout.str() assert "test_foo" not in result.stdout.str()

View File

@ -12,7 +12,6 @@ from _pytest.config.findpaths import determine_setup
from _pytest.config.findpaths import get_common_ancestor from _pytest.config.findpaths import get_common_ancestor
from _pytest.config.findpaths import getcfg from _pytest.config.findpaths import getcfg
from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.main import EXIT_NOTESTSCOLLECTED
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
class TestParseIni(object): class TestParseIni(object):
@ -194,7 +193,7 @@ class TestConfigAPI(object):
config = testdir.parseconfig("--hello=this") config = testdir.parseconfig("--hello=this")
for x in ("hello", "--hello", "-X"): for x in ("hello", "--hello", "-X"):
assert config.getoption(x) == "this" assert config.getoption(x) == "this"
pytest.raises(ValueError, "config.getoption('qweqwe')") pytest.raises(ValueError, config.getoption, "qweqwe")
@pytest.mark.skipif("sys.version_info[0] < 3") @pytest.mark.skipif("sys.version_info[0] < 3")
def test_config_getoption_unicode(self, testdir): def test_config_getoption_unicode(self, testdir):
@ -211,7 +210,7 @@ class TestConfigAPI(object):
def test_config_getvalueorskip(self, testdir): def test_config_getvalueorskip(self, testdir):
config = testdir.parseconfig() config = testdir.parseconfig()
pytest.raises(pytest.skip.Exception, "config.getvalueorskip('hello')") pytest.raises(pytest.skip.Exception, config.getvalueorskip, "hello")
verbose = config.getvalueorskip("verbose") verbose = config.getvalueorskip("verbose")
assert verbose == config.option.verbose assert verbose == config.option.verbose
@ -726,7 +725,8 @@ def test_config_in_subdirectory_colon_command_line_issue2148(testdir):
def test_notify_exception(testdir, capfd): def test_notify_exception(testdir, capfd):
config = testdir.parseconfig() config = testdir.parseconfig()
excinfo = pytest.raises(ValueError, "raise ValueError(1)") with pytest.raises(ValueError) as excinfo:
raise ValueError(1)
config.notify_exception(excinfo) config.notify_exception(excinfo)
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert "ValueError" in err assert "ValueError" in err
@ -792,66 +792,6 @@ def test_collect_pytest_prefix_bug(pytestconfig):
assert pm.parse_hookimpl_opts(Dummy(), "pytest_something") is None assert pm.parse_hookimpl_opts(Dummy(), "pytest_something") is None
class TestLegacyWarning(object):
@pytest.mark.filterwarnings("default")
def test_warn_config(self, testdir):
testdir.makeconftest(
"""
values = []
def pytest_runtest_setup(item):
item.config.warn("C1", "hello")
def pytest_logwarning(code, message):
if message == "hello" and code == "C1":
values.append(1)
"""
)
testdir.makepyfile(
"""
def test_proper(pytestconfig):
import conftest
assert conftest.values == [1]
"""
)
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
["*hello", "*config.warn has been deprecated*", "*1 passed*"]
)
@pytest.mark.filterwarnings("default")
@pytest.mark.parametrize("use_kw", [True, False])
def test_warn_on_test_item_from_request(self, testdir, use_kw):
code_kw = "code=" if use_kw else ""
message_kw = "message=" if use_kw else ""
testdir.makepyfile(
"""
import pytest
@pytest.fixture
def fix(request):
request.node.warn({code_kw}"T1", {message_kw}"hello")
def test_hello(fix):
pass
""".format(
code_kw=code_kw, message_kw=message_kw
)
)
result = testdir.runpytest(
"--disable-pytest-warnings", SHOW_PYTEST_WARNINGS_ARG
)
assert "hello" not in result.stdout.str()
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(
"""
===*warnings summary*===
*test_warn_on_test_item_from_request.py::test_hello*
*hello*
*test_warn_on_test_item_from_request.py:7:*Node.warn(code, message) form has been deprecated*
"""
)
class TestRootdir(object): class TestRootdir(object):
def test_simple_noini(self, tmpdir): def test_simple_noini(self, tmpdir):
assert get_common_ancestor([tmpdir]) == tmpdir assert get_common_ancestor([tmpdir]) == tmpdir

View File

@ -153,6 +153,37 @@ class TestPython(object):
val = tnode["time"] val = tnode["time"]
assert round(float(val), 2) >= 0.03 assert round(float(val), 2) >= 0.03
@pytest.mark.parametrize("duration_report", ["call", "total"])
def test_junit_duration_report(self, testdir, monkeypatch, duration_report):
# mock LogXML.node_reporter so it always sets a known duration to each test report object
original_node_reporter = LogXML.node_reporter
def node_reporter_wrapper(s, report):
report.duration = 1.0
reporter = original_node_reporter(s, report)
return reporter
monkeypatch.setattr(LogXML, "node_reporter", node_reporter_wrapper)
testdir.makepyfile(
"""
def test_foo():
pass
"""
)
result, dom = runandparse(
testdir, "-o", "junit_duration_report={}".format(duration_report)
)
node = dom.find_first_by_tag("testsuite")
tnode = node.find_first_by_tag("testcase")
val = float(tnode["time"])
if duration_report == "total":
assert val == 3.0
else:
assert duration_report == "call"
assert val == 1.0
def test_setup_error(self, testdir): def test_setup_error(self, testdir):
testdir.makepyfile( testdir.makepyfile(
""" """

View File

@ -5,20 +5,19 @@ from __future__ import print_function
import os import os
import sys import sys
import six
import pytest
from _pytest.mark import EMPTY_PARAMETERSET_OPTION
from _pytest.mark import MarkGenerator as Mark
from _pytest.nodes import Collector
from _pytest.nodes import Node
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
try: try:
import mock import mock
except ImportError: except ImportError:
import unittest.mock as mock import unittest.mock as mock
import pytest
from _pytest.mark import (
MarkGenerator as Mark,
ParameterSet,
transfer_markers,
EMPTY_PARAMETERSET_OPTION,
)
from _pytest.nodes import Node, Collector
ignore_markinfo = pytest.mark.filterwarnings( ignore_markinfo = pytest.mark.filterwarnings(
"ignore:MarkInfo objects:pytest.RemovedInPytest4Warning" "ignore:MarkInfo objects:pytest.RemovedInPytest4Warning"
@ -26,12 +25,6 @@ ignore_markinfo = pytest.mark.filterwarnings(
class TestMark(object): class TestMark(object):
def test_markinfo_repr(self):
from _pytest.mark import MarkInfo, Mark
m = MarkInfo.for_mark(Mark("hello", (1, 2), {}))
repr(m)
@pytest.mark.parametrize("attr", ["mark", "param"]) @pytest.mark.parametrize("attr", ["mark", "param"])
@pytest.mark.parametrize("modulename", ["py.test", "pytest"]) @pytest.mark.parametrize("modulename", ["py.test", "pytest"])
def test_pytest_exists_in_namespace_all(self, attr, modulename): def test_pytest_exists_in_namespace_all(self, attr, modulename):
@ -57,105 +50,8 @@ class TestMark(object):
def test_pytest_mark_name_starts_with_underscore(self): def test_pytest_mark_name_starts_with_underscore(self):
mark = Mark() mark = Mark()
pytest.raises(AttributeError, getattr, mark, "_some_name") with pytest.raises(AttributeError):
mark._some_name
def test_pytest_mark_bare(self):
mark = Mark()
def f():
pass
mark.hello(f)
assert f.hello
def test_mark_legacy_ignore_fail(self):
def add_attribute(func):
func.foo = 1
return func
@pytest.mark.foo
@add_attribute
def test_fun():
pass
assert test_fun.foo == 1
assert test_fun.pytestmark
@ignore_markinfo
def test_pytest_mark_keywords(self):
mark = Mark()
def f():
pass
mark.world(x=3, y=4)(f)
assert f.world
assert f.world.kwargs["x"] == 3
assert f.world.kwargs["y"] == 4
@ignore_markinfo
def test_apply_multiple_and_merge(self):
mark = Mark()
def f():
pass
mark.world
mark.world(x=3)(f)
assert f.world.kwargs["x"] == 3
mark.world(y=4)(f)
assert f.world.kwargs["x"] == 3
assert f.world.kwargs["y"] == 4
mark.world(y=1)(f)
assert f.world.kwargs["y"] == 1
assert len(f.world.args) == 0
@ignore_markinfo
def test_pytest_mark_positional(self):
mark = Mark()
def f():
pass
mark.world("hello")(f)
assert f.world.args[0] == "hello"
mark.world("world")(f)
@ignore_markinfo
def test_pytest_mark_positional_func_and_keyword(self):
mark = Mark()
def f():
raise Exception
m = mark.world(f, omega="hello")
def g():
pass
assert m(g) == g
assert g.world.args[0] is f
assert g.world.kwargs["omega"] == "hello"
@ignore_markinfo
def test_pytest_mark_reuse(self):
mark = Mark()
def f():
pass
w = mark.some
w("hello", reason="123")(f)
assert f.some.args[0] == "hello"
assert f.some.kwargs["reason"] == "123"
def g():
pass
w("world", reason2="456")(g)
assert g.some.args[0] == "world"
assert "reason" not in g.some.kwargs
assert g.some.kwargs["reason2"] == "456"
def test_marked_class_run_twice(testdir, request): def test_marked_class_run_twice(testdir, request):
@ -476,8 +372,10 @@ def test_parametrized_collect_with_wrong_args(testdir):
result = testdir.runpytest(py_file) result = testdir.runpytest(py_file)
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
'E ValueError: In "parametrize" the number of values ((1, 2, 3)) ' 'test_parametrized_collect_with_wrong_args.py::test_func: in "parametrize" the number of names (2):',
"must be equal to the number of names (['foo', 'bar'])" " ['foo', 'bar']",
"must be equal to the number of values (3):",
" (1, 2, 3)",
] ]
) )
@ -503,116 +401,6 @@ def test_parametrized_with_kwargs(testdir):
class TestFunctional(object): class TestFunctional(object):
def test_mark_per_function(self, testdir):
p = testdir.makepyfile(
"""
import pytest
@pytest.mark.hello
def test_hello():
assert hasattr(test_hello, 'hello')
"""
)
result = testdir.runpytest(p)
result.stdout.fnmatch_lines(["*1 passed*"])
def test_mark_per_module(self, testdir):
item = testdir.getitem(
"""
import pytest
pytestmark = pytest.mark.hello
def test_func():
pass
"""
)
keywords = item.keywords
assert "hello" in keywords
def test_marklist_per_class(self, testdir):
item = testdir.getitem(
"""
import pytest
class TestClass(object):
pytestmark = [pytest.mark.hello, pytest.mark.world]
def test_func(self):
assert TestClass.test_func.hello
assert TestClass.test_func.world
"""
)
keywords = item.keywords
assert "hello" in keywords
def test_marklist_per_module(self, testdir):
item = testdir.getitem(
"""
import pytest
pytestmark = [pytest.mark.hello, pytest.mark.world]
class TestClass(object):
def test_func(self):
assert TestClass.test_func.hello
assert TestClass.test_func.world
"""
)
keywords = item.keywords
assert "hello" in keywords
assert "world" in keywords
def test_mark_per_class_decorator(self, testdir):
item = testdir.getitem(
"""
import pytest
@pytest.mark.hello
class TestClass(object):
def test_func(self):
assert TestClass.test_func.hello
"""
)
keywords = item.keywords
assert "hello" in keywords
def test_mark_per_class_decorator_plus_existing_dec(self, testdir):
item = testdir.getitem(
"""
import pytest
@pytest.mark.hello
class TestClass(object):
pytestmark = pytest.mark.world
def test_func(self):
assert TestClass.test_func.hello
assert TestClass.test_func.world
"""
)
keywords = item.keywords
assert "hello" in keywords
assert "world" in keywords
@ignore_markinfo
def test_merging_markers(self, testdir):
p = testdir.makepyfile(
"""
import pytest
pytestmark = pytest.mark.hello("pos1", x=1, y=2)
class TestClass(object):
# classlevel overrides module level
pytestmark = pytest.mark.hello(x=3)
@pytest.mark.hello("pos0", z=4)
def test_func(self):
pass
"""
)
items, rec = testdir.inline_genitems(p)
item, = items
keywords = item.keywords
marker = keywords["hello"]
assert marker.args == ("pos0", "pos1")
assert marker.kwargs == {"x": 1, "y": 2, "z": 4}
# test the new __iter__ interface
values = list(marker)
assert len(values) == 3
assert values[0].args == ("pos0",)
assert values[1].args == ()
assert values[2].args == ("pos1",)
def test_merging_markers_deep(self, testdir): def test_merging_markers_deep(self, testdir):
# issue 199 - propagate markers into nested classes # issue 199 - propagate markers into nested classes
p = testdir.makepyfile( p = testdir.makepyfile(
@ -675,11 +463,6 @@ class TestFunctional(object):
items, rec = testdir.inline_genitems(p) items, rec = testdir.inline_genitems(p)
base_item, sub_item, sub_item_other = items base_item, sub_item, sub_item_other = items
print(items, [x.nodeid for x in items]) print(items, [x.nodeid for x in items])
# legacy api smears
assert hasattr(base_item.obj, "b")
assert hasattr(sub_item_other.obj, "b")
assert hasattr(sub_item.obj, "b")
# new api seregates # new api seregates
assert not list(base_item.iter_markers(name="b")) assert not list(base_item.iter_markers(name="b"))
assert not list(sub_item_other.iter_markers(name="b")) assert not list(sub_item_other.iter_markers(name="b"))
@ -765,26 +548,6 @@ class TestFunctional(object):
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines(["keyword: *hello*"]) result.stdout.fnmatch_lines(["keyword: *hello*"])
@ignore_markinfo
def test_merging_markers_two_functions(self, testdir):
p = testdir.makepyfile(
"""
import pytest
@pytest.mark.hello("pos1", z=4)
@pytest.mark.hello("pos0", z=3)
def test_func():
pass
"""
)
items, rec = testdir.inline_genitems(p)
item, = items
keywords = item.keywords
marker = keywords["hello"]
values = list(marker)
assert len(values) == 2
assert values[0].args == ("pos0",)
assert values[1].args == ("pos1",)
def test_no_marker_match_on_unmarked_names(self, testdir): def test_no_marker_match_on_unmarked_names(self, testdir):
p = testdir.makepyfile( p = testdir.makepyfile(
""" """
@ -858,7 +621,7 @@ class TestFunctional(object):
assert "mark2" in request.keywords assert "mark2" in request.keywords
assert "mark3" in request.keywords assert "mark3" in request.keywords
assert 10 not in request.keywords assert 10 not in request.keywords
marker = request.node.get_marker("mark1") marker = request.node.get_closest_marker("mark1")
assert marker.name == "mark1" assert marker.name == "mark1"
assert marker.args == () assert marker.args == ()
assert marker.kwargs == {} assert marker.kwargs == {}
@ -874,15 +637,11 @@ class TestFunctional(object):
.. note:: this could be moved to ``testdir`` if proven to be useful .. note:: this could be moved to ``testdir`` if proven to be useful
to other modules. to other modules.
""" """
from _pytest.mark import MarkInfo
items = {x.name: x for x in items} items = {x.name: x for x in items}
for name, expected_markers in expected.items(): for name, expected_markers in expected.items():
markers = items[name].keywords._markers markers = {m.name for m in items[name].iter_markers()}
marker_names = { assert markers == set(expected_markers)
name for (name, v) in markers.items() if isinstance(v, MarkInfo)
}
assert marker_names == set(expected_markers)
@pytest.mark.issue1540 @pytest.mark.issue1540
@pytest.mark.filterwarnings("ignore") @pytest.mark.filterwarnings("ignore")
@ -1041,56 +800,6 @@ class TestKeywordSelection(object):
assert_test_is_not_selected("()") assert_test_is_not_selected("()")
@pytest.mark.parametrize(
"argval, expected",
[
(
pytest.mark.skip()((1, 2)),
ParameterSet(values=(1, 2), marks=[pytest.mark.skip], id=None),
),
(
pytest.mark.xfail(pytest.mark.skip()((1, 2))),
ParameterSet(
values=(1, 2), marks=[pytest.mark.xfail, pytest.mark.skip], id=None
),
),
],
)
@pytest.mark.filterwarnings("default")
def test_parameterset_extractfrom(argval, expected):
from _pytest.deprecated import MARK_PARAMETERSET_UNPACKING
warn_called = []
class DummyItem:
def warn(self, warning):
warn_called.append(warning)
extracted = ParameterSet.extract_from(argval, belonging_definition=DummyItem())
assert extracted == expected
assert warn_called == [MARK_PARAMETERSET_UNPACKING]
def test_legacy_transfer():
class FakeModule(object):
pytestmark = []
class FakeClass(object):
pytestmark = pytest.mark.nofun
@pytest.mark.fun
def fake_method(self):
pass
transfer_markers(fake_method, FakeClass, FakeModule)
# legacy marks transfer smeared
assert fake_method.nofun
assert fake_method.fun
# pristine marks dont transfer
assert fake_method.pytestmark == [pytest.mark.fun.mark]
class TestMarkDecorator(object): class TestMarkDecorator(object):
@pytest.mark.parametrize( @pytest.mark.parametrize(
"lhs, rhs, expected", "lhs, rhs, expected",
@ -1191,19 +900,12 @@ def test_mark_expressions_no_smear(testdir):
deselected_tests = dlist[0].items deselected_tests = dlist[0].items
assert len(deselected_tests) == 1 assert len(deselected_tests) == 1
# todo: fixed
# keywords smear - expected behaviour # keywords smear - expected behaviour
reprec_keywords = testdir.inline_run("-k", "FOO") # reprec_keywords = testdir.inline_run("-k", "FOO")
passed_k, skipped_k, failed_k = reprec_keywords.countoutcomes() # passed_k, skipped_k, failed_k = reprec_keywords.countoutcomes()
assert passed_k == 2 # assert passed_k == 2
assert skipped_k == failed_k == 0 # assert skipped_k == failed_k == 0
def test_addmarker_getmarker():
node = Node("Test", config=mock.Mock(), session=mock.Mock(), nodeid="Test")
node.add_marker(pytest.mark.a(1))
node.add_marker("b")
node.get_marker("a").combined
node.get_marker("b").combined
def test_addmarker_order(): def test_addmarker_order():
@ -1227,7 +929,7 @@ def test_markers_from_parametrize(testdir):
custom_mark = pytest.mark.custom_mark custom_mark = pytest.mark.custom_mark
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def trigger(request): def trigger(request):
custom_mark =request.node.get_marker('custom_mark') custom_mark = list(request.node.iter_markers('custom_mark'))
print("Custom mark %s" % custom_mark) print("Custom mark %s" % custom_mark)
@custom_mark("custom mark non parametrized") @custom_mark("custom mark non parametrized")
@ -1252,3 +954,18 @@ def test_markers_from_parametrize(testdir):
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG) result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
result.assert_outcomes(passed=4) result.assert_outcomes(passed=4)
def test_pytest_param_id_requires_string():
with pytest.raises(TypeError) as excinfo:
pytest.param(id=True)
msg, = excinfo.value.args
if six.PY2:
assert msg == "Expected id to be a string, got <type 'bool'>: True"
else:
assert msg == "Expected id to be a string, got <class 'bool'>: True"
@pytest.mark.parametrize("s", (None, "hello world"))
def test_pytest_param_id_allows_none_or_string(s):
assert pytest.param(id=s)

View File

@ -27,7 +27,7 @@ def test_setattr():
x = 1 x = 1
monkeypatch = MonkeyPatch() monkeypatch = MonkeyPatch()
pytest.raises(AttributeError, "monkeypatch.setattr(A, 'notexists', 2)") pytest.raises(AttributeError, monkeypatch.setattr, A, "notexists", 2)
monkeypatch.setattr(A, "y", 2, raising=False) monkeypatch.setattr(A, "y", 2, raising=False)
assert A.y == 2 assert A.y == 2
monkeypatch.undo() monkeypatch.undo()
@ -99,7 +99,7 @@ def test_delattr():
monkeypatch = MonkeyPatch() monkeypatch = MonkeyPatch()
monkeypatch.delattr(A, "x") monkeypatch.delattr(A, "x")
pytest.raises(AttributeError, "monkeypatch.delattr(A, 'y')") pytest.raises(AttributeError, monkeypatch.delattr, A, "y")
monkeypatch.delattr(A, "y", raising=False) monkeypatch.delattr(A, "y", raising=False)
monkeypatch.setattr(A, "x", 5, raising=False) monkeypatch.setattr(A, "x", 5, raising=False)
assert A.x == 5 assert A.x == 5
@ -156,7 +156,7 @@ def test_delitem():
monkeypatch.delitem(d, "x") monkeypatch.delitem(d, "x")
assert "x" not in d assert "x" not in d
monkeypatch.delitem(d, "y", raising=False) monkeypatch.delitem(d, "y", raising=False)
pytest.raises(KeyError, "monkeypatch.delitem(d, 'y')") pytest.raises(KeyError, monkeypatch.delitem, d, "y")
assert not d assert not d
monkeypatch.setitem(d, "y", 1700) monkeypatch.setitem(d, "y", 1700)
assert d["y"] == 1700 assert d["y"] == 1700
@ -182,7 +182,7 @@ def test_delenv():
name = "xyz1234" name = "xyz1234"
assert name not in os.environ assert name not in os.environ
monkeypatch = MonkeyPatch() monkeypatch = MonkeyPatch()
pytest.raises(KeyError, "monkeypatch.delenv(%r, raising=True)" % name) pytest.raises(KeyError, monkeypatch.delenv, name, raising=True)
monkeypatch.delenv(name, raising=False) monkeypatch.delenv(name, raising=False)
monkeypatch.undo() monkeypatch.undo()
os.environ[name] = "1" os.environ[name] = "1"

View File

@ -3,7 +3,6 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
import pytest import pytest
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
def setup_module(mod): def setup_module(mod):
@ -162,73 +161,6 @@ def test_nose_setup_partial(testdir):
result.stdout.fnmatch_lines(["*2 passed*"]) result.stdout.fnmatch_lines(["*2 passed*"])
def test_nose_test_generator_fixtures(testdir):
p = testdir.makepyfile(
"""
# taken from nose-0.11.1 unit_tests/test_generator_fixtures.py
from nose.tools import eq_
called = []
def outer_setup():
called.append('outer_setup')
def outer_teardown():
called.append('outer_teardown')
def inner_setup():
called.append('inner_setup')
def inner_teardown():
called.append('inner_teardown')
def test_gen():
called[:] = []
for i in range(0, 5):
yield check, i
def check(i):
expect = ['outer_setup']
for x in range(0, i):
expect.append('inner_setup')
expect.append('inner_teardown')
expect.append('inner_setup')
eq_(called, expect)
test_gen.setup = outer_setup
test_gen.teardown = outer_teardown
check.setup = inner_setup
check.teardown = inner_teardown
class TestClass(object):
def setup(self):
print("setup called in %s" % self)
self.called = ['setup']
def teardown(self):
print("teardown called in %s" % self)
eq_(self.called, ['setup'])
self.called.append('teardown')
def test(self):
print("test called in %s" % self)
for i in range(0, 5):
yield self.check, i
def check(self, i):
print("check called in %s" % self)
expect = ['setup']
#for x in range(0, i):
# expect.append('setup')
# expect.append('teardown')
#expect.append('setup')
eq_(self.called, expect)
"""
)
result = testdir.runpytest(p, "-p", "nose", SHOW_PYTEST_WARNINGS_ARG)
result.stdout.fnmatch_lines(["*10 passed*"])
def test_module_level_setup(testdir): def test_module_level_setup(testdir):
testdir.makepyfile( testdir.makepyfile(
""" """

View File

@ -24,6 +24,12 @@ class TestParser(object):
out, err = capsys.readouterr() out, err = capsys.readouterr()
assert err.find("error: unrecognized arguments") != -1 assert err.find("error: unrecognized arguments") != -1
def test_custom_prog(self, parser):
"""Custom prog can be set for `argparse.ArgumentParser`."""
assert parser._getparser().prog == os.path.basename(sys.argv[0])
parser.prog = "custom-prog"
assert parser._getparser().prog == "custom-prog"
def test_argument(self): def test_argument(self):
with pytest.raises(parseopt.ArgumentError): with pytest.raises(parseopt.ArgumentError):
# need a short or long option # need a short or long option
@ -100,12 +106,8 @@ class TestParser(object):
def test_group_shortopt_lowercase(self, parser): def test_group_shortopt_lowercase(self, parser):
group = parser.getgroup("hello") group = parser.getgroup("hello")
pytest.raises( with pytest.raises(ValueError):
ValueError,
"""
group.addoption("-x", action="store_true") group.addoption("-x", action="store_true")
""",
)
assert len(group.options) == 0 assert len(group.options) == 0
group._addoption("-x", action="store_true") group._addoption("-x", action="store_true")
assert len(group.options) == 1 assert len(group.options) == 1

View File

@ -8,7 +8,6 @@ import sys
import _pytest._code import _pytest._code
import pytest import pytest
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
try: try:
breakpoint breakpoint
@ -390,6 +389,28 @@ class TestPDB(object):
assert "hello17" in rest # out is captured assert "hello17" in rest # out is captured
self.flush(child) self.flush(child)
def test_pdb_set_trace_kwargs(self, testdir):
p1 = testdir.makepyfile(
"""
import pytest
def test_1():
i = 0
print("hello17")
pytest.set_trace(header="== my_header ==")
x = 3
"""
)
child = testdir.spawn_pytest(str(p1))
child.expect("== my_header ==")
assert "PDB set_trace" not in child.before.decode()
child.expect("Pdb")
child.sendeof()
rest = child.read().decode("utf-8")
assert "1 failed" in rest
assert "def test_1" in rest
assert "hello17" in rest # out is captured
self.flush(child)
def test_pdb_set_trace_interception(self, testdir): def test_pdb_set_trace_interception(self, testdir):
p1 = testdir.makepyfile( p1 = testdir.makepyfile(
""" """
@ -634,6 +655,12 @@ class TestPDB(object):
testdir.makepyfile( testdir.makepyfile(
custom_pdb=""" custom_pdb="""
class CustomPdb(object): class CustomPdb(object):
def __init__(self, *args, **kwargs):
skip = kwargs.pop("skip")
assert skip == ["foo.*"]
print("__init__")
super(CustomPdb, self).__init__(*args, **kwargs)
def set_trace(*args, **kwargs): def set_trace(*args, **kwargs):
print('custom set_trace>') print('custom set_trace>')
""" """
@ -643,12 +670,13 @@ class TestPDB(object):
import pytest import pytest
def test_foo(): def test_foo():
pytest.set_trace() pytest.set_trace(skip=['foo.*'])
""" """
) )
monkeypatch.setenv("PYTHONPATH", str(testdir.tmpdir)) monkeypatch.setenv("PYTHONPATH", str(testdir.tmpdir))
child = testdir.spawn_pytest("--pdbcls=custom_pdb:CustomPdb %s" % str(p1)) child = testdir.spawn_pytest("--pdbcls=custom_pdb:CustomPdb %s" % str(p1))
child.expect("__init__")
child.expect("custom set_trace>") child.expect("custom set_trace>")
self.flush(child) self.flush(child)
@ -809,27 +837,6 @@ class TestTraceOption:
assert "reading from stdin while output" not in rest assert "reading from stdin while output" not in rest
TestPDB.flush(child) TestPDB.flush(child)
def test_trace_against_yield_test(self, testdir):
p1 = testdir.makepyfile(
"""
def is_equal(a, b):
assert a == b
def test_1():
yield is_equal, 1, 1
"""
)
child = testdir.spawn_pytest(
"{} --trace {}".format(SHOW_PYTEST_WARNINGS_ARG, str(p1))
)
child.expect("is_equal")
child.expect("Pdb")
child.sendeof()
rest = child.read().decode("utf8")
assert "1 passed" in rest
assert "reading from stdin while output" not in rest
TestPDB.flush(child)
def test_trace_after_runpytest(testdir): def test_trace_after_runpytest(testdir):
"""Test that debugging's pytest_configure is re-entrant.""" """Test that debugging's pytest_configure is re-entrant."""

View File

@ -32,7 +32,7 @@ class TestPytestPluginInteractions(object):
""" """
import newhooks import newhooks
def pytest_addhooks(pluginmanager): def pytest_addhooks(pluginmanager):
pluginmanager.addhooks(newhooks) pluginmanager.add_hookspecs(newhooks)
def pytest_myhook(xyz): def pytest_myhook(xyz):
return xyz + 1 return xyz + 1
""" """
@ -52,44 +52,13 @@ class TestPytestPluginInteractions(object):
""" """
import sys import sys
def pytest_addhooks(pluginmanager): def pytest_addhooks(pluginmanager):
pluginmanager.addhooks(sys) pluginmanager.add_hookspecs(sys)
""" """
) )
res = testdir.runpytest() res = testdir.runpytest()
assert res.ret != 0 assert res.ret != 0
res.stderr.fnmatch_lines(["*did not find*sys*"]) res.stderr.fnmatch_lines(["*did not find*sys*"])
def test_namespace_early_from_import(self, testdir):
p = testdir.makepyfile(
"""
from pytest import Item
from pytest import Item as Item2
assert Item is Item2
"""
)
result = testdir.runpython(p)
assert result.ret == 0
@pytest.mark.filterwarnings("ignore:pytest_namespace is deprecated")
def test_do_ext_namespace(self, testdir):
testdir.makeconftest(
"""
def pytest_namespace():
return {'hello': 'world'}
"""
)
p = testdir.makepyfile(
"""
from pytest import hello
import pytest
def test_hello():
assert hello == "world"
assert 'hello' in pytest.__all__
"""
)
reprec = testdir.inline_run(p)
reprec.assertoutcome(passed=1)
def test_do_option_postinitialize(self, testdir): def test_do_option_postinitialize(self, testdir):
config = testdir.parseconfigure() config = testdir.parseconfigure()
assert not hasattr(config.option, "test123") assert not hasattr(config.option, "test123")
@ -172,34 +141,6 @@ class TestPytestPluginInteractions(object):
ihook_b = session.gethookproxy(testdir.tmpdir.join("tests")) ihook_b = session.gethookproxy(testdir.tmpdir.join("tests"))
assert ihook_a is not ihook_b assert ihook_a is not ihook_b
def test_warn_on_deprecated_addhooks(self, pytestpm):
warnings = []
class get_warnings(object):
def pytest_logwarning(self, code, fslocation, message, nodeid):
warnings.append(message)
class Plugin(object):
def pytest_testhook():
pass
pytestpm.register(get_warnings())
before = list(warnings)
pytestpm.addhooks(Plugin())
assert len(warnings) == len(before) + 1
assert "deprecated" in warnings[-1]
def test_namespace_has_default_and_env_plugins(testdir):
p = testdir.makepyfile(
"""
import pytest
pytest.mark
"""
)
result = testdir.runpython(p)
assert result.ret == 0
def test_default_markers(testdir): def test_default_markers(testdir):
result = testdir.runpytest("--markers") result = testdir.runpytest("--markers")
@ -238,7 +179,7 @@ class TestPytestPluginManager(object):
assert pm.is_registered(mod) assert pm.is_registered(mod)
values = pm.get_plugins() values = pm.get_plugins()
assert mod in values assert mod in values
pytest.raises(ValueError, "pm.register(mod)") pytest.raises(ValueError, pm.register, mod)
pytest.raises(ValueError, lambda: pm.register(mod)) pytest.raises(ValueError, lambda: pm.register(mod))
# assert not pm.is_registered(mod2) # assert not pm.is_registered(mod2)
assert pm.get_plugins() == values assert pm.get_plugins() == values
@ -282,11 +223,12 @@ class TestPytestPluginManager(object):
with pytest.raises(ImportError): with pytest.raises(ImportError):
pytestpm.consider_env() pytestpm.consider_env()
@pytest.mark.filterwarnings("always")
def test_plugin_skip(self, testdir, monkeypatch): def test_plugin_skip(self, testdir, monkeypatch):
p = testdir.makepyfile( p = testdir.makepyfile(
skipping1=""" skipping1="""
import pytest import pytest
pytest.skip("hello") pytest.skip("hello", allow_module_level=True)
""" """
) )
p.copy(p.dirpath("skipping2.py")) p.copy(p.dirpath("skipping2.py"))
@ -326,8 +268,8 @@ class TestPytestPluginManager(object):
result.stdout.fnmatch_lines(["*1 passed*"]) result.stdout.fnmatch_lines(["*1 passed*"])
def test_import_plugin_importname(self, testdir, pytestpm): def test_import_plugin_importname(self, testdir, pytestpm):
pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")') pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y")
pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwx.y")') pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwx.y")
testdir.syspathinsert() testdir.syspathinsert()
pluginname = "pytest_hello" pluginname = "pytest_hello"
@ -343,8 +285,8 @@ class TestPytestPluginManager(object):
assert plugin2 is plugin1 assert plugin2 is plugin1
def test_import_plugin_dotted_name(self, testdir, pytestpm): def test_import_plugin_dotted_name(self, testdir, pytestpm):
pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")') pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y")
pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwex.y")') pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwex.y")
testdir.syspathinsert() testdir.syspathinsert()
testdir.mkpydir("pkg").join("plug.py").write("x=3") testdir.mkpydir("pkg").join("plug.py").write("x=3")
@ -365,6 +307,12 @@ class TestPytestPluginManagerBootstrapming(object):
ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"]) ImportError, lambda: pytestpm.consider_preparse(["xyz", "-p", "hello123"])
) )
# Handles -p without space (#3532).
with pytest.raises(ImportError) as excinfo:
pytestpm.consider_preparse(["-phello123"])
assert '"hello123"' in excinfo.value.args[0]
pytestpm.consider_preparse(["-pno:hello123"])
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()

View File

@ -71,7 +71,7 @@ def test_make_hook_recorder(testdir):
recorder.unregister() recorder.unregister()
recorder.clear() recorder.clear()
recorder.hook.pytest_runtest_logreport(report=rep) recorder.hook.pytest_runtest_logreport(report=rep)
pytest.raises(ValueError, "recorder.getfailures()") pytest.raises(ValueError, recorder.getfailures)
def test_parseconfig(testdir): def test_parseconfig(testdir):
@ -168,13 +168,13 @@ def make_holder():
@pytest.mark.parametrize("holder", make_holder()) @pytest.mark.parametrize("holder", make_holder())
def test_hookrecorder_basic(holder): def test_hookrecorder_basic(holder):
pm = PytestPluginManager() pm = PytestPluginManager()
pm.addhooks(holder) pm.add_hookspecs(holder)
rec = HookRecorder(pm) rec = HookRecorder(pm)
pm.hook.pytest_xyz(arg=123) pm.hook.pytest_xyz(arg=123)
call = rec.popcall("pytest_xyz") call = rec.popcall("pytest_xyz")
assert call.arg == 123 assert call.arg == 123
assert call._name == "pytest_xyz" assert call._name == "pytest_xyz"
pytest.raises(pytest.fail.Exception, "rec.popcall('abc')") pytest.raises(pytest.fail.Exception, rec.popcall, "abc")
pm.hook.pytest_xyz_noarg() pm.hook.pytest_xyz_noarg()
call = rec.popcall("pytest_xyz_noarg") call = rec.popcall("pytest_xyz_noarg")
assert call._name == "pytest_xyz_noarg" assert call._name == "pytest_xyz_noarg"
@ -280,7 +280,7 @@ def test_assert_outcomes_after_pytest_error(testdir):
testdir.makepyfile("def test_foo(): assert True") testdir.makepyfile("def test_foo(): assert True")
result = testdir.runpytest("--unexpected-argument") result = testdir.runpytest("--unexpected-argument")
with pytest.raises(ValueError, message="Pytest terminal report not found"): with pytest.raises(ValueError, match="Pytest terminal report not found"):
result.assert_outcomes(passed=0) result.assert_outcomes(passed=0)

View File

@ -7,6 +7,7 @@ import warnings
import pytest import pytest
from _pytest.recwarn import WarningsRecorder from _pytest.recwarn import WarningsRecorder
from _pytest.warning_types import PytestDeprecationWarning
def test_recwarn_stacklevel(recwarn): def test_recwarn_stacklevel(recwarn):
@ -44,7 +45,7 @@ class TestWarningsRecorderChecker(object):
rec.clear() rec.clear()
assert len(rec.list) == 0 assert len(rec.list) == 0
assert values is rec.list assert values is rec.list
pytest.raises(AssertionError, "rec.pop()") pytest.raises(AssertionError, rec.pop)
@pytest.mark.issue(4243) @pytest.mark.issue(4243)
def test_warn_stacklevel(self): def test_warn_stacklevel(self):
@ -214,9 +215,17 @@ class TestWarns(object):
source1 = "warnings.warn('w1', RuntimeWarning)" source1 = "warnings.warn('w1', RuntimeWarning)"
source2 = "warnings.warn('w2', RuntimeWarning)" source2 = "warnings.warn('w2', RuntimeWarning)"
source3 = "warnings.warn('w3', RuntimeWarning)" source3 = "warnings.warn('w3', RuntimeWarning)"
pytest.warns(RuntimeWarning, source1) with pytest.warns(PytestDeprecationWarning) as warninfo: # yo dawg
pytest.raises(pytest.fail.Exception, lambda: pytest.warns(UserWarning, source2)) pytest.warns(RuntimeWarning, source1)
pytest.warns(RuntimeWarning, source3) pytest.raises(
pytest.fail.Exception, lambda: pytest.warns(UserWarning, source2)
)
pytest.warns(RuntimeWarning, source3)
assert len(warninfo) == 3
for w in warninfo:
assert w.filename == __file__
msg, = w.message.args
assert msg.startswith("warns(..., 'code(as_a_string)') is deprecated")
def test_function(self): def test_function(self):
pytest.warns( pytest.warns(

View File

@ -151,7 +151,7 @@ class TestWithFunctionIntegration(object):
try: try:
raise ValueError raise ValueError
except ValueError: except ValueError:
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo.from_current()
reslog = ResultLog(None, py.io.TextIO()) reslog = ResultLog(None, py.io.TextIO())
reslog.pytest_internalerror(excinfo.getrepr(style=style)) reslog.pytest_internalerror(excinfo.getrepr(style=style))
entry = reslog.logfile.getvalue() entry = reslog.logfile.getvalue()

View File

@ -487,13 +487,13 @@ def test_report_extra_parameters(reporttype):
def test_callinfo(): def test_callinfo():
ci = runner.CallInfo(lambda: 0, "123") ci = runner.CallInfo.from_call(lambda: 0, "123")
assert ci.when == "123" assert ci.when == "123"
assert ci.result == 0 assert ci.result == 0
assert "result" in repr(ci) assert "result" in repr(ci)
assert repr(ci) == "<CallInfo when='123' result: 0>" assert repr(ci) == "<CallInfo when='123' result: 0>"
ci = runner.CallInfo(lambda: 0 / 0, "123") ci = runner.CallInfo.from_call(lambda: 0 / 0, "123")
assert ci.when == "123" assert ci.when == "123"
assert not hasattr(ci, "result") assert not hasattr(ci, "result")
assert repr(ci) == "<CallInfo when='123' exception: division by zero>" assert repr(ci) == "<CallInfo when='123' exception: division by zero>"
@ -501,16 +501,6 @@ def test_callinfo():
assert "exc" in repr(ci) assert "exc" in repr(ci)
def test_callinfo_repr_while_running():
def repr_while_running():
f = sys._getframe().f_back
assert "func" in f.f_locals
assert repr(f.f_locals["self"]) == "<CallInfo when='when' result: '<NOTSET>'>"
ci = runner.CallInfo(repr_while_running, "when")
assert repr(ci) == "<CallInfo when='when' result: None>"
# design question: do we want general hooks in python files? # design question: do we want general hooks in python files?
# then something like the following functional tests makes sense # then something like the following functional tests makes sense
@ -561,20 +551,16 @@ def test_outcomeexception_passes_except_Exception():
def test_pytest_exit(): def test_pytest_exit():
try: with pytest.raises(pytest.exit.Exception) as excinfo:
pytest.exit("hello") pytest.exit("hello")
except pytest.exit.Exception: assert excinfo.errisinstance(pytest.exit.Exception)
excinfo = _pytest._code.ExceptionInfo()
assert excinfo.errisinstance(KeyboardInterrupt)
def test_pytest_fail(): def test_pytest_fail():
try: with pytest.raises(pytest.fail.Exception) as excinfo:
pytest.fail("hello") pytest.fail("hello")
except pytest.fail.Exception: s = excinfo.exconly(tryshort=True)
excinfo = _pytest._code.ExceptionInfo() assert s.startswith("Failed")
s = excinfo.exconly(tryshort=True)
assert s.startswith("Failed")
def test_pytest_exit_msg(testdir): def test_pytest_exit_msg(testdir):
@ -683,7 +669,7 @@ def test_exception_printing_skip():
try: try:
pytest.skip("hello") pytest.skip("hello")
except pytest.skip.Exception: except pytest.skip.Exception:
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo.from_current()
s = excinfo.exconly(tryshort=True) s = excinfo.exconly(tryshort=True)
assert s.startswith("Skipped") assert s.startswith("Skipped")
@ -704,21 +690,17 @@ def test_importorskip(monkeypatch):
# check that importorskip reports the actual call # check that importorskip reports the actual call
# in this test the test_runner.py file # in this test the test_runner.py file
assert path.purebasename == "test_runner" assert path.purebasename == "test_runner"
pytest.raises(SyntaxError, "pytest.importorskip('x y z')") pytest.raises(SyntaxError, pytest.importorskip, "x y z")
pytest.raises(SyntaxError, "pytest.importorskip('x=y')") pytest.raises(SyntaxError, pytest.importorskip, "x=y")
mod = types.ModuleType("hello123") mod = types.ModuleType("hello123")
mod.__version__ = "1.3" mod.__version__ = "1.3"
monkeypatch.setitem(sys.modules, "hello123", mod) monkeypatch.setitem(sys.modules, "hello123", mod)
pytest.raises( with pytest.raises(pytest.skip.Exception):
pytest.skip.Exception,
"""
pytest.importorskip("hello123", minversion="1.3.1") pytest.importorskip("hello123", minversion="1.3.1")
""",
)
mod2 = pytest.importorskip("hello123", minversion="1.3") mod2 = pytest.importorskip("hello123", minversion="1.3")
assert mod2 == mod assert mod2 == mod
except pytest.skip.Exception: except pytest.skip.Exception:
print(_pytest._code.ExceptionInfo()) print(_pytest._code.ExceptionInfo.from_current())
pytest.fail("spurious skip") pytest.fail("spurious skip")
@ -734,13 +716,10 @@ def test_importorskip_dev_module(monkeypatch):
monkeypatch.setitem(sys.modules, "mockmodule", mod) monkeypatch.setitem(sys.modules, "mockmodule", mod)
mod2 = pytest.importorskip("mockmodule", minversion="0.12.0") mod2 = pytest.importorskip("mockmodule", minversion="0.12.0")
assert mod2 == mod assert mod2 == mod
pytest.raises( with pytest.raises(pytest.skip.Exception):
pytest.skip.Exception, pytest.importorskip("mockmodule1", minversion="0.14.0")
"""
pytest.importorskip('mockmodule1', minversion='0.14.0')""",
)
except pytest.skip.Exception: except pytest.skip.Exception:
print(_pytest._code.ExceptionInfo()) print(_pytest._code.ExceptionInfo.from_current())
pytest.fail("spurious skip") pytest.fail("spurious skip")
@ -759,6 +738,22 @@ def test_importorskip_module_level(testdir):
result.stdout.fnmatch_lines(["*collected 0 items / 1 skipped*"]) result.stdout.fnmatch_lines(["*collected 0 items / 1 skipped*"])
def test_importorskip_custom_reason(testdir):
"""make sure custom reasons are used"""
testdir.makepyfile(
"""
import pytest
foobarbaz = pytest.importorskip("foobarbaz2", reason="just because")
def test_foo():
pass
"""
)
result = testdir.runpytest("-ra")
result.stdout.fnmatch_lines(["*just because*"])
result.stdout.fnmatch_lines(["*collected 0 items / 1 skipped*"])
def test_pytest_cmdline_main(testdir): def test_pytest_cmdline_main(testdir):
p = testdir.makepyfile( p = testdir.makepyfile(
""" """

View File

@ -7,7 +7,6 @@ from __future__ import division
from __future__ import print_function from __future__ import print_function
import pytest import pytest
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
def test_module_and_function_setup(testdir): def test_module_and_function_setup(testdir):
@ -170,64 +169,6 @@ def test_method_setup_failure_no_teardown(testdir):
reprec.assertoutcome(failed=1, passed=1) reprec.assertoutcome(failed=1, passed=1)
def test_method_generator_setup(testdir):
reprec = testdir.inline_runsource(
"""
class TestSetupTeardownOnInstance(object):
def setup_class(cls):
cls.classsetup = True
def setup_method(self, method):
self.methsetup = method
def test_generate(self):
assert self.classsetup
assert self.methsetup == self.test_generate
yield self.generated, 5
yield self.generated, 2
def generated(self, value):
assert self.classsetup
assert self.methsetup == self.test_generate
assert value == 5
""",
SHOW_PYTEST_WARNINGS_ARG,
)
reprec.assertoutcome(passed=1, failed=1)
def test_func_generator_setup(testdir):
reprec = testdir.inline_runsource(
"""
import sys
def setup_module(mod):
print("setup_module")
mod.x = []
def setup_function(fun):
print("setup_function")
x.append(1)
def teardown_function(fun):
print("teardown_function")
x.pop()
def test_one():
assert x == [1]
def check():
print("check")
sys.stderr.write("e\\n")
assert x == [1]
yield check
assert x == [1]
""",
SHOW_PYTEST_WARNINGS_ARG,
)
rep = reprec.matchreport("test_one", names="pytest_runtest_logreport")
assert rep.passed
def test_method_setup_uses_fresh_instances(testdir): def test_method_setup_uses_fresh_instances(testdir):
reprec = testdir.inline_runsource( reprec = testdir.inline_runsource(
""" """

View File

@ -4,7 +4,6 @@ from __future__ import print_function
import pytest import pytest
from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.main import EXIT_NOTESTSCOLLECTED
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
class SessionTests(object): class SessionTests(object):
@ -73,19 +72,6 @@ class SessionTests(object):
print(out) print(out)
pytest.fail("incorrect raises() output") pytest.fail("incorrect raises() output")
def test_generator_yields_None(self, testdir):
reprec = testdir.inline_runsource(
"""
def test_1():
yield None
""",
SHOW_PYTEST_WARNINGS_ARG,
)
failures = reprec.getfailedcollections()
out = failures[0].longrepr.reprcrash.message
i = out.find("TypeError")
assert i != -1
def test_syntax_error_module(self, testdir): def test_syntax_error_module(self, testdir):
reprec = testdir.inline_runsource("this is really not python") reprec = testdir.inline_runsource("this is really not python")
values = reprec.getfailedcollections() values = reprec.getfailedcollections()
@ -243,12 +229,8 @@ class TestNewSession(SessionTests):
def test_plugin_specify(testdir): def test_plugin_specify(testdir):
pytest.raises( with pytest.raises(ImportError):
ImportError, testdir.parseconfig("-p", "nqweotexistent")
"""
testdir.parseconfig("-p", "nqweotexistent")
""",
)
# pytest.raises(ImportError, # pytest.raises(ImportError,
# "config.do_configure(config)" # "config.do_configure(config)"
# ) # )

View File

@ -875,11 +875,22 @@ def test_reportchars_all(testdir):
pass pass
def test_4(): def test_4():
pytest.skip("four") pytest.skip("four")
@pytest.fixture
def fail():
assert 0
def test_5(fail):
pass
""" """
) )
result = testdir.runpytest("-ra") result = testdir.runpytest("-ra")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
["FAIL*test_1*", "SKIP*four*", "XFAIL*test_2*", "XPASS*test_3*"] [
"SKIP*four*",
"XFAIL*test_2*",
"XPASS*test_3*",
"ERROR*test_5*",
"FAIL*test_1*",
]
) )

View File

@ -20,7 +20,6 @@ from _pytest.terminal import build_summary_stats_line
from _pytest.terminal import getreportopt from _pytest.terminal import getreportopt
from _pytest.terminal import repr_pythonversion from _pytest.terminal import repr_pythonversion
from _pytest.terminal import TerminalReporter from _pytest.terminal import TerminalReporter
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
DistInfo = collections.namedtuple("DistInfo", ["project_name", "version"]) DistInfo = collections.namedtuple("DistInfo", ["project_name", "version"])
@ -105,7 +104,8 @@ class TestTerminal(object):
def test_internalerror(self, testdir, linecomp): def test_internalerror(self, testdir, linecomp):
modcol = testdir.getmodulecol("def test_one(): pass") modcol = testdir.getmodulecol("def test_one(): pass")
rep = TerminalReporter(modcol.config, file=linecomp.stringio) rep = TerminalReporter(modcol.config, file=linecomp.stringio)
excinfo = pytest.raises(ValueError, "raise ValueError('hello')") with pytest.raises(ValueError) as excinfo:
raise ValueError("hello")
rep.pytest_internalerror(excinfo.getrepr()) rep.pytest_internalerror(excinfo.getrepr())
linecomp.assert_contains_lines(["INTERNALERROR> *ValueError*hello*"]) linecomp.assert_contains_lines(["INTERNALERROR> *ValueError*hello*"])
@ -263,7 +263,7 @@ class TestCollectonly(object):
) )
result = testdir.runpytest("--collect-only") result = testdir.runpytest("--collect-only")
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
["<Module 'test_collectonly_basic.py'>", " <Function 'test_func'>"] ["<Module test_collectonly_basic.py>", " <Function test_func>"]
) )
def test_collectonly_skipped_module(self, testdir): def test_collectonly_skipped_module(self, testdir):
@ -276,6 +276,18 @@ class TestCollectonly(object):
result = testdir.runpytest("--collect-only", "-rs") result = testdir.runpytest("--collect-only", "-rs")
result.stdout.fnmatch_lines(["*ERROR collecting*"]) result.stdout.fnmatch_lines(["*ERROR collecting*"])
def test_collectonly_display_test_description(self, testdir):
testdir.makepyfile(
"""
def test_with_description():
\""" This test has a description.
\"""
assert True
"""
)
result = testdir.runpytest("--collect-only", "--verbose")
result.stdout.fnmatch_lines([" This test has a description."])
def test_collectonly_failed_module(self, testdir): def test_collectonly_failed_module(self, testdir):
testdir.makepyfile("""raise ValueError(0)""") testdir.makepyfile("""raise ValueError(0)""")
result = testdir.runpytest("--collect-only") result = testdir.runpytest("--collect-only")
@ -307,11 +319,10 @@ class TestCollectonly(object):
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"*<Module '*.py'>", "*<Module *.py>",
"* <Function 'test_func1'*>", "* <Function test_func1>",
"* <Class 'TestClass'>", "* <Class TestClass>",
# "* <Instance '()'>", "* <Function test_method>",
"* <Function 'test_method'*>",
] ]
) )
@ -540,7 +551,7 @@ class TestTerminalFunctional(object):
result.stdout.fnmatch_lines(["test_passes.py ..*", "* 2 pass*"]) result.stdout.fnmatch_lines(["test_passes.py ..*", "* 2 pass*"])
assert result.ret == 0 assert result.ret == 0
def test_header_trailer_info(self, testdir): def test_header_trailer_info(self, testdir, request):
testdir.makepyfile( testdir.makepyfile(
""" """
def test_passes(): def test_passes():
@ -564,7 +575,7 @@ class TestTerminalFunctional(object):
"=* 1 passed*in *.[0-9][0-9] seconds *=", "=* 1 passed*in *.[0-9][0-9] seconds *=",
] ]
) )
if pytest.config.pluginmanager.list_plugin_distinfo(): if request.config.pluginmanager.list_plugin_distinfo():
result.stdout.fnmatch_lines(["plugins: *"]) result.stdout.fnmatch_lines(["plugins: *"])
def test_showlocals(self, testdir): def test_showlocals(self, testdir):
@ -585,8 +596,9 @@ class TestTerminalFunctional(object):
] ]
) )
def test_verbose_reporting(self, testdir, pytestconfig): @pytest.fixture
p1 = testdir.makepyfile( def verbose_testfile(self, testdir):
return testdir.makepyfile(
""" """
import pytest import pytest
def test_fail(): def test_fail():
@ -602,22 +614,32 @@ class TestTerminalFunctional(object):
yield check, 0 yield check, 0
""" """
) )
result = testdir.runpytest(p1, "-v", SHOW_PYTEST_WARNINGS_ARG)
def test_verbose_reporting(self, verbose_testfile, testdir, pytestconfig):
result = testdir.runpytest(
verbose_testfile, "-v", "-Walways::pytest.PytestWarning"
)
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
[ [
"*test_verbose_reporting.py::test_fail *FAIL*", "*test_verbose_reporting.py::test_fail *FAIL*",
"*test_verbose_reporting.py::test_pass *PASS*", "*test_verbose_reporting.py::test_pass *PASS*",
"*test_verbose_reporting.py::TestClass::test_skip *SKIP*", "*test_verbose_reporting.py::TestClass::test_skip *SKIP*",
"*test_verbose_reporting.py::test_gen*0* *FAIL*", "*test_verbose_reporting.py::test_gen *xfail*",
] ]
) )
assert result.ret == 1 assert result.ret == 1
def test_verbose_reporting_xdist(self, verbose_testfile, testdir, pytestconfig):
if not pytestconfig.pluginmanager.get_plugin("xdist"): if not pytestconfig.pluginmanager.get_plugin("xdist"):
pytest.skip("xdist plugin not installed") pytest.skip("xdist plugin not installed")
result = testdir.runpytest(p1, "-v", "-n 1", SHOW_PYTEST_WARNINGS_ARG) result = testdir.runpytest(
result.stdout.fnmatch_lines(["*FAIL*test_verbose_reporting.py::test_fail*"]) verbose_testfile, "-v", "-n 1", "-Walways::pytest.PytestWarning"
)
result.stdout.fnmatch_lines(
["*FAIL*test_verbose_reporting_xdist.py::test_fail*"]
)
assert result.ret == 1 assert result.ret == 1
def test_quiet_reporting(self, testdir): def test_quiet_reporting(self, testdir):

View File

@ -308,9 +308,9 @@ def test_filterwarnings_mark_registration(testdir):
def test_warning_captured_hook(testdir): def test_warning_captured_hook(testdir):
testdir.makeconftest( testdir.makeconftest(
""" """
from _pytest.warnings import _issue_config_warning from _pytest.warnings import _issue_warning_captured
def pytest_configure(config): def pytest_configure(config):
_issue_config_warning(UserWarning("config warning"), config, stacklevel=2) _issue_warning_captured(UserWarning("config warning"), config.hook, stacklevel=2)
""" """
) )
testdir.makepyfile( testdir.makepyfile(
@ -623,3 +623,63 @@ def test_removed_in_pytest4_warning_as_error(testdir, change_default):
else: else:
assert change_default in ("ini", "cmdline") assert change_default in ("ini", "cmdline")
result.stdout.fnmatch_lines(["* 1 passed in *"]) result.stdout.fnmatch_lines(["* 1 passed in *"])
class TestAssertionWarnings:
@staticmethod
def assert_result_warns(result, msg):
result.stdout.fnmatch_lines(["*PytestWarning: %s*" % msg])
def test_tuple_warning(self, testdir):
testdir.makepyfile(
"""
def test_foo():
assert (1,2)
"""
)
result = testdir.runpytest()
self.assert_result_warns(
result, "assertion is always true, perhaps remove parentheses?"
)
@staticmethod
def create_file(testdir, return_none):
testdir.makepyfile(
"""
def foo(return_none):
if return_none:
return None
else:
return False
def test_foo():
assert foo({return_none})
""".format(
return_none=return_none
)
)
def test_none_function_warns(self, testdir):
self.create_file(testdir, True)
result = testdir.runpytest()
self.assert_result_warns(
result, 'asserting the value None, please use "assert is None"'
)
def test_assert_is_none_no_warn(self, testdir):
testdir.makepyfile(
"""
def foo():
return None
def test_foo():
assert foo() is None
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*1 passed in*"])
def test_false_function_no_warn(self, testdir):
self.create_file(testdir, False)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*1 failed in*"])

View File

@ -121,6 +121,7 @@ setenv=
setenv = {[testenv:py27-pluggymaster]setenv} setenv = {[testenv:py27-pluggymaster]setenv}
[testenv:docs] [testenv:docs]
basepython = python3
skipsdist = True skipsdist = True
usedevelop = True usedevelop = True
changedir = doc/en changedir = doc/en
@ -130,7 +131,7 @@ commands =
sphinx-build -W -b html . _build sphinx-build -W -b html . _build
[testenv:doctesting] [testenv:doctesting]
basepython = python basepython = python3
skipsdist = True skipsdist = True
deps = deps =
PyYAML PyYAML
@ -147,6 +148,7 @@ deps =
sphinx sphinx
PyYAML PyYAML
regendoc>=0.6.1 regendoc>=0.6.1
dataclasses
whitelist_externals = whitelist_externals =
rm rm
make make