commit
38adb23bd2
|
@ -44,3 +44,4 @@ coverage.xml
|
|||
.pydevproject
|
||||
.project
|
||||
.settings
|
||||
.vscode
|
||||
|
|
3
AUTHORS
3
AUTHORS
|
@ -12,11 +12,13 @@ Alan Velasco
|
|||
Alexander Johnson
|
||||
Alexei Kozlenok
|
||||
Allan Feldman
|
||||
Aly Sivji
|
||||
Anatoly Bubenkoff
|
||||
Anders Hovmöller
|
||||
Andras Tim
|
||||
Andrea Cimatoribus
|
||||
Andreas Zeidler
|
||||
Andrey Paramonov
|
||||
Andrzej Ostrowski
|
||||
Andy Freeland
|
||||
Anthon van der Neut
|
||||
|
@ -165,6 +167,7 @@ Miro Hrončok
|
|||
Nathaniel Waisbrot
|
||||
Ned Batchelder
|
||||
Neven Mundar
|
||||
Nicholas Devenish
|
||||
Niclas Olofsson
|
||||
Nicolas Delaby
|
||||
Oleg Pidsadnyi
|
||||
|
|
228
CHANGELOG.rst
228
CHANGELOG.rst
|
@ -18,6 +18,232 @@ with advance notice in the **Deprecations** section of releases.
|
|||
|
||||
.. 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)
|
||||
=========================
|
||||
|
||||
|
@ -1757,7 +1983,7 @@ Bug Fixes
|
|||
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
|
||||
<https://github.com/pytest-dev/pytest/issues/2641>`_)
|
||||
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Markers example documentation page updated to support latest pytest version.
|
|
@ -1 +0,0 @@
|
|||
Update cache documentation example to correctly show cache hit and miss.
|
|
@ -1 +0,0 @@
|
|||
Improved detailed summary report documentation.
|
|
@ -39,7 +39,7 @@ clean:
|
|||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
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:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
|
|
|
@ -6,6 +6,7 @@ Release announcements
|
|||
:maxdepth: 2
|
||||
|
||||
|
||||
release-4.1.0
|
||||
release-4.0.2
|
||||
release-4.0.1
|
||||
release-4.0.0
|
||||
|
|
|
@ -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
|
|
@ -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::
|
||||
|
||||
pytest.raises(ExpectedException, func, *args, **kwargs)
|
||||
pytest.raises(ExpectedException, "func(*args, **kwargs)")
|
||||
|
||||
both of which execute the specified function with args and kwargs and
|
||||
asserts that the given ``ExpectedException`` is raised. The reporter will
|
||||
which will execute the specified function with args and kwargs and
|
||||
assert that the given ``ExpectedException`` is raised. The reporter will
|
||||
provide you with helpful output in case of failures such as *no
|
||||
exception* or *wrong exception*.
|
||||
|
||||
|
|
|
@ -68,8 +68,6 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
|
|||
|
||||
def test_function(record_property):
|
||||
record_property("example_key", 1)
|
||||
record_xml_property
|
||||
(Deprecated) use record_property.
|
||||
record_xml_attribute
|
||||
Add extra xml attributes to the tag for the calling test.
|
||||
The fixture is callable with ``(name, value)``, with value being
|
||||
|
|
|
@ -215,7 +215,9 @@ If you run this command for the first time, you can see the print statement:
|
|||
> assert mydata == 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
|
||||
|
||||
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
|
||||
E assert 42 == 23
|
||||
|
||||
test_caching.py:14: AssertionError
|
||||
test_caching.py:17: AssertionError
|
||||
1 failed in 0.12 seconds
|
||||
|
||||
See the :ref:`cache-api` for more details.
|
||||
|
|
|
@ -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
|
||||
should be used instead.
|
||||
|
||||
.. contents::
|
||||
:depth: 3
|
||||
:local:
|
||||
|
||||
|
||||
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
|
||||
: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
|
||||
this warning::
|
||||
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.
|
||||
|
||||
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``
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. deprecated:: 3.9
|
||||
*Removed in version 4.0.*
|
||||
|
||||
``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
|
||||
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
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
``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
|
||||
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.
|
||||
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
|
||||
bug fixes and refactorings impossible.
|
||||
|
||||
Example of usage:
|
||||
This is just a matter of renaming the fixture as the API is the same:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
class MySymbol:
|
||||
def test_foo(record_xml_property):
|
||||
...
|
||||
|
||||
Change to:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo(record_property):
|
||||
...
|
||||
|
||||
|
||||
def pytest_namespace():
|
||||
return {"my_symbol": MySymbol()}
|
||||
Passing command-line string to ``pytest.main()``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
*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).
|
||||
|
||||
As a stopgap measure, plugin authors may still inject their names into pytest's namespace, usually during ``pytest_configure``:
|
||||
Passing a command-line string to ``pytest.main()`` is deprecated:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
pytest.main("-v -s")
|
||||
|
||||
Pass a list instead:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytest.main(["-v", "-s"])
|
||||
|
||||
|
||||
def pytest_configure():
|
||||
pytest.my_symbol = MySymbol()
|
||||
|
||||
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.
|
||||
|
||||
|
||||
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.
|
||||
|
||||
|
@ -175,116 +366,27 @@ In those cases just request the function directly in the dependent fixture:
|
|||
cell.make_full()
|
||||
return cell
|
||||
|
||||
``Node.get_marker``
|
||||
~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. 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:
|
||||
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:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_foo(record_xml_property):
|
||||
...
|
||||
|
||||
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):
|
||||
...
|
||||
def cell():
|
||||
return ...
|
||||
|
||||
|
||||
|
||||
Passing command-line string to ``pytest.main()``
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. 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.
|
||||
@pytest.fixture(name="cell")
|
||||
def cell_fixture():
|
||||
return cell()
|
||||
|
||||
|
||||
``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:
|
||||
|
||||
.. 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):
|
||||
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
|
||||
|
||||
def pytest_funcarg__data():
|
||||
return SomeData()
|
||||
class MySymbol:
|
||||
...
|
||||
|
||||
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
|
||||
|
||||
@pytest.fixture
|
||||
def data():
|
||||
return SomeData()
|
||||
import pytest
|
||||
|
||||
[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``)
|
||||
|
@ -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
|
||||
points also created broken entry points in wheels, so removing them also
|
||||
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)``.
|
||||
|
|
|
@ -98,6 +98,30 @@ class TestSpecialisedExplanations(object):
|
|||
text = "head " * 50 + "f" * 70 + "tail " * 20
|
||||
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():
|
||||
class Foo(object):
|
||||
|
@ -141,11 +165,11 @@ def globf(x):
|
|||
|
||||
class TestRaises(object):
|
||||
def test_raises(self):
|
||||
s = "qwe" # NOQA
|
||||
raises(TypeError, "int(s)")
|
||||
s = "qwe"
|
||||
raises(TypeError, int, s)
|
||||
|
||||
def test_raises_doesnt(self):
|
||||
raises(IOError, "int('3')")
|
||||
raises(IOError, int, "3")
|
||||
|
||||
def test_raise(self):
|
||||
raise ValueError("demo error")
|
||||
|
|
|
@ -9,5 +9,5 @@ def test_failure_demo_fails_properly(testdir):
|
|||
failure_demo.copy(target)
|
||||
failure_demo.copy(testdir.tmpdir.join(failure_demo.basename))
|
||||
result = testdir.runpytest(target, syspathinsert=True)
|
||||
result.stdout.fnmatch_lines(["*42 failed*"])
|
||||
result.stdout.fnmatch_lines(["*44 failed*"])
|
||||
assert result.ret != 0
|
||||
|
|
|
@ -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
|
||||
rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
|
||||
collected 2 items
|
||||
<Package '$REGENDOC_TMPDIR/nonpython'>
|
||||
<YamlFile 'test_simple.yml'>
|
||||
<YamlItem 'hello'>
|
||||
<YamlItem 'ok'>
|
||||
<Package $REGENDOC_TMPDIR/nonpython>
|
||||
<YamlFile test_simple.yml>
|
||||
<YamlItem hello>
|
||||
<YamlItem ok>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
|
|
@ -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
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 8 items
|
||||
<Module 'test_time.py'>
|
||||
<Function 'test_timedistance_v0[a0-b0-expected0]'>
|
||||
<Function 'test_timedistance_v0[a1-b1-expected1]'>
|
||||
<Function 'test_timedistance_v1[forward]'>
|
||||
<Function 'test_timedistance_v1[backward]'>
|
||||
<Function 'test_timedistance_v2[20011212-20011211-expected0]'>
|
||||
<Function 'test_timedistance_v2[20011211-20011212-expected1]'>
|
||||
<Function 'test_timedistance_v3[forward]'>
|
||||
<Function 'test_timedistance_v3[backward]'>
|
||||
<Module test_time.py>
|
||||
<Function test_timedistance_v0[a0-b0-expected0]>
|
||||
<Function test_timedistance_v0[a1-b1-expected1]>
|
||||
<Function test_timedistance_v1[forward]>
|
||||
<Function test_timedistance_v1[backward]>
|
||||
<Function test_timedistance_v2[20011212-20011211-expected0]>
|
||||
<Function test_timedistance_v2[20011211-20011212-expected1]>
|
||||
<Function test_timedistance_v3[forward]>
|
||||
<Function test_timedistance_v3[backward]>
|
||||
|
||||
======================= 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
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 4 items
|
||||
<Module 'test_scenarios.py'>
|
||||
<Class 'TestSampleWithScenarios'>
|
||||
<Function 'test_demo1[basic]'>
|
||||
<Function 'test_demo2[basic]'>
|
||||
<Function 'test_demo1[advanced]'>
|
||||
<Function 'test_demo2[advanced]'>
|
||||
<Module test_scenarios.py>
|
||||
<Class TestSampleWithScenarios>
|
||||
<Function test_demo1[basic]>
|
||||
<Function test_demo2[basic]>
|
||||
<Function test_demo1[advanced]>
|
||||
<Function test_demo2[advanced]>
|
||||
|
||||
======================= 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
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
<Module 'test_backends.py'>
|
||||
<Function 'test_db_initialized[d1]'>
|
||||
<Function 'test_db_initialized[d2]'>
|
||||
<Module test_backends.py>
|
||||
<Function test_db_initialized[d1]>
|
||||
<Function test_db_initialized[d2]>
|
||||
|
||||
======================= 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
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 item
|
||||
<Module 'test_indirect_list.py'>
|
||||
<Function 'test_indirect[a-b]'>
|
||||
<Module test_indirect_list.py>
|
||||
<Function test_indirect[a-b]>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
|
@ -388,7 +388,8 @@ parametrizer`_ but in a lot less code::
|
|||
assert 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
|
||||
argument sets to use for each test function. Let's run it:
|
||||
|
|
|
@ -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
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 2 items
|
||||
<Module 'check_myapp.py'>
|
||||
<Class 'CheckMyApp'>
|
||||
<Function 'simple_check'>
|
||||
<Function 'complex_check'>
|
||||
<Module check_myapp.py>
|
||||
<Class CheckMyApp>
|
||||
<Function simple_check>
|
||||
<Function complex_check>
|
||||
|
||||
======================= 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
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 3 items
|
||||
<Module 'CWD/pythoncollection.py'>
|
||||
<Function 'test_function'>
|
||||
<Class 'TestClass'>
|
||||
<Function 'test_method'>
|
||||
<Function 'test_anothermethod'>
|
||||
<Module CWD/pythoncollection.py>
|
||||
<Function test_function>
|
||||
<Class TestClass>
|
||||
<Function test_method>
|
||||
<Function test_anothermethod>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
|
|
|
@ -15,9 +15,9 @@ get on the terminal - we are working on that):
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR/assertion, inifile:
|
||||
collected 42 items
|
||||
collected 44 items
|
||||
|
||||
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [100%]
|
||||
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [100%]
|
||||
|
||||
================================= FAILURES =================================
|
||||
___________________________ test_generative[3-6] ___________________________
|
||||
|
@ -289,6 +289,48 @@ get on the terminal - we are working on that):
|
|||
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
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 ______________________________
|
||||
|
||||
def test_attribute():
|
||||
|
@ -300,7 +342,7 @@ get on the terminal - we are working on that):
|
|||
E assert 1 == 2
|
||||
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 __________________________
|
||||
|
||||
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 <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 __________________________
|
||||
|
||||
def test_attribute_failure():
|
||||
|
@ -325,7 +367,7 @@ get on the terminal - we are working on that):
|
|||
i = Foo()
|
||||
> assert i.b == 2
|
||||
|
||||
failure_demo.py:125:
|
||||
failure_demo.py:149:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
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")
|
||||
E Exception: Failed to get attrib
|
||||
|
||||
failure_demo.py:120: Exception
|
||||
failure_demo.py:144: Exception
|
||||
_________________________ 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 + 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 __________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
|
||||
def test_raises(self):
|
||||
s = "qwe" # NOQA
|
||||
> raises(TypeError, "int(s)")
|
||||
|
||||
failure_demo.py:145:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
> int(s)
|
||||
s = "qwe"
|
||||
> raises(TypeError, int, s)
|
||||
E ValueError: invalid literal for int() with base 10: 'qwe'
|
||||
|
||||
<0-codegen $REGENDOC_TMPDIR/assertion/failure_demo.py:145>:1: ValueError
|
||||
failure_demo.py:169: ValueError
|
||||
______________________ TestRaises.test_raises_doesnt _______________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
|
||||
def test_raises_doesnt(self):
|
||||
> raises(IOError, "int('3')")
|
||||
> raises(IOError, int, "3")
|
||||
E Failed: DID NOT RAISE <class 'OSError'>
|
||||
|
||||
failure_demo.py:148: Failed
|
||||
failure_demo.py:172: Failed
|
||||
__________________________ TestRaises.test_raise ___________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
|
@ -384,7 +421,7 @@ get on the terminal - we are working on that):
|
|||
> raise ValueError("demo error")
|
||||
E ValueError: demo error
|
||||
|
||||
failure_demo.py:151: ValueError
|
||||
failure_demo.py:175: ValueError
|
||||
________________________ TestRaises.test_tupleerror ________________________
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
|
@ -393,7 +430,7 @@ get on the terminal - we are working on that):
|
|||
> a, b = [1] # NOQA
|
||||
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 ______
|
||||
|
||||
self = <failure_demo.TestRaises object at 0xdeadbeef>
|
||||
|
@ -404,7 +441,7 @@ get on the terminal - we are working on that):
|
|||
> a, b = items.pop()
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:159: TypeError
|
||||
failure_demo.py:183: TypeError
|
||||
--------------------------- Captured stdout call ---------------------------
|
||||
items is [1, 2, 3]
|
||||
________________________ TestRaises.test_some_error ________________________
|
||||
|
@ -415,7 +452,7 @@ get on the terminal - we are working on that):
|
|||
> if namenotexi: # NOQA
|
||||
E NameError: name 'namenotexi' is not defined
|
||||
|
||||
failure_demo.py:162: NameError
|
||||
failure_demo.py:186: NameError
|
||||
____________________ 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
|
||||
> module.foo()
|
||||
|
||||
failure_demo.py:180:
|
||||
failure_demo.py:204:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
def foo():
|
||||
> assert 1 == 0
|
||||
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 _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
|
@ -451,7 +488,7 @@ get on the terminal - we are working on that):
|
|||
|
||||
> somefunc(f(), g())
|
||||
|
||||
failure_demo.py:191:
|
||||
failure_demo.py:215:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
failure_demo.py:13: in somefunc
|
||||
otherfunc(x, y)
|
||||
|
@ -473,7 +510,7 @@ get on the terminal - we are working on that):
|
|||
> a, b = items
|
||||
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 _____________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
|
@ -483,7 +520,7 @@ get on the terminal - we are working on that):
|
|||
> a, b = items
|
||||
E TypeError: 'int' object is not iterable
|
||||
|
||||
failure_demo.py:199: TypeError
|
||||
failure_demo.py:223: TypeError
|
||||
______________________ TestMoreErrors.test_startswith ______________________
|
||||
|
||||
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 <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 ___________________
|
||||
|
||||
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 + 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 ______________________
|
||||
|
||||
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 43 = globf(42)
|
||||
|
||||
failure_demo.py:216: AssertionError
|
||||
failure_demo.py:240: AssertionError
|
||||
_______________________ TestMoreErrors.test_instance _______________________
|
||||
|
||||
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 + where 42 = <failure_demo.TestMoreErrors object at 0xdeadbeef>.x
|
||||
|
||||
failure_demo.py:220: AssertionError
|
||||
failure_demo.py:244: AssertionError
|
||||
_______________________ TestMoreErrors.test_compare ________________________
|
||||
|
||||
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 + where 11 = globf(10)
|
||||
|
||||
failure_demo.py:223: AssertionError
|
||||
failure_demo.py:247: AssertionError
|
||||
_____________________ TestMoreErrors.test_try_finally ______________________
|
||||
|
||||
self = <failure_demo.TestMoreErrors object at 0xdeadbeef>
|
||||
|
@ -558,7 +595,7 @@ get on the terminal - we are working on that):
|
|||
> assert x == 0
|
||||
E assert 1 == 0
|
||||
|
||||
failure_demo.py:228: AssertionError
|
||||
failure_demo.py:252: AssertionError
|
||||
___________________ TestCustomAssertMsg.test_single_line ___________________
|
||||
|
||||
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 + 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 ____________________
|
||||
|
||||
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 + 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 ___________________
|
||||
|
||||
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 + where 1 = This is JSON\n{\n 'foo': 'bar'\n}.a
|
||||
|
||||
failure_demo.py:259: AssertionError
|
||||
======================== 42 failed in 0.12 seconds =========================
|
||||
failure_demo.py:283: AssertionError
|
||||
======================== 44 failed in 0.12 seconds =========================
|
||||
|
|
|
@ -598,7 +598,7 @@ We can run this:
|
|||
file $REGENDOC_TMPDIR/b/test_error.py, line 1
|
||||
def test_root(db): # no db here, will error out
|
||||
E fixture 'db' not found
|
||||
> available fixtures: cache, capfd, capfdbinary, caplog, capsys, capsysbinary, doctest_namespace, monkeypatch, pytestconfig, record_property, record_xml_attribute, 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.
|
||||
|
||||
$REGENDOC_TMPDIR/b/test_error.py:1
|
||||
|
|
|
@ -628,7 +628,7 @@ So let's just do another run:
|
|||
response, msg = smtp_connection.ehlo()
|
||||
assert response == 250
|
||||
> 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
|
||||
-------------------------- 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
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 10 items
|
||||
<Module 'test_anothersmtp.py'>
|
||||
<Function 'test_showhelo[smtp.gmail.com]'>
|
||||
<Function 'test_showhelo[mail.python.org]'>
|
||||
<Module 'test_ids.py'>
|
||||
<Function 'test_a[spam]'>
|
||||
<Function 'test_a[ham]'>
|
||||
<Function 'test_b[eggs]'>
|
||||
<Function 'test_b[1]'>
|
||||
<Module 'test_module.py'>
|
||||
<Function 'test_ehlo[smtp.gmail.com]'>
|
||||
<Function 'test_noop[smtp.gmail.com]'>
|
||||
<Function 'test_ehlo[mail.python.org]'>
|
||||
<Function 'test_noop[mail.python.org]'>
|
||||
<Module test_anothersmtp.py>
|
||||
<Function test_showhelo[smtp.gmail.com]>
|
||||
<Function test_showhelo[mail.python.org]>
|
||||
<Module test_ids.py>
|
||||
<Function test_a[spam]>
|
||||
<Function test_a[ham]>
|
||||
<Function test_b[eggs]>
|
||||
<Function test_b[1]>
|
||||
<Module test_module.py>
|
||||
<Function test_ehlo[smtp.gmail.com]>
|
||||
<Function test_noop[smtp.gmail.com]>
|
||||
<Function test_ehlo[mail.python.org]>
|
||||
<Function test_noop[mail.python.org]>
|
||||
|
||||
======================= no tests ran in 0.12 seconds =======================
|
||||
|
||||
|
|
|
@ -618,7 +618,6 @@ Session related reporting hooks:
|
|||
.. autofunction:: pytest_terminal_summary
|
||||
.. autofunction:: pytest_fixture_setup
|
||||
.. autofunction:: pytest_fixture_post_finalizer
|
||||
.. autofunction:: pytest_logwarning
|
||||
.. autofunction:: pytest_warning_captured
|
||||
|
||||
And here is the central hook for reporting about
|
||||
|
@ -725,13 +724,6 @@ MarkGenerator
|
|||
:members:
|
||||
|
||||
|
||||
MarkInfo
|
||||
~~~~~~~~
|
||||
|
||||
.. autoclass:: _pytest.mark.MarkInfo
|
||||
:members:
|
||||
|
||||
|
||||
Mark
|
||||
~~~~
|
||||
|
||||
|
|
183
doc/en/usage.rst
183
doc/en/usage.rst
|
@ -152,40 +152,77 @@ making it easy in large test suites to get a clear picture of all failures, skip
|
|||
|
||||
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
|
||||
|
||||
$ pytest -ra
|
||||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 7 items
|
||||
collected 6 items
|
||||
|
||||
test_examples.py ..FEsxX [100%]
|
||||
test_example.py .FEsxX [100%]
|
||||
|
||||
==================================== ERRORS ====================================
|
||||
_________________________ 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.
|
||||
================================== ERRORS ==================================
|
||||
_______________________ ERROR at setup of test_error _______________________
|
||||
|
||||
/Users/chainz/tmp/pytestratest/test_examples.py:17
|
||||
=================================== FAILURES ===================================
|
||||
__________________________________ test_fail ___________________________________
|
||||
@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_examples.py:14: AssertionError
|
||||
=========================== short test summary info ============================
|
||||
FAIL test_examples.py::test_fail
|
||||
ERROR test_examples.py::test_error
|
||||
SKIP [1] test_examples.py:21: Example
|
||||
XFAIL test_examples.py::test_xfail
|
||||
XPASS test_examples.py::test_xpass
|
||||
= 1 failed, 2 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.07 seconds =
|
||||
test_example.py:14: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
SKIP [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
|
||||
XFAIL test_example.py::test_xfail
|
||||
reason: xfailing this test
|
||||
XPASS test_example.py::test_xpass always xfail
|
||||
ERROR test_example.py::test_error
|
||||
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".
|
||||
|
||||
|
@ -208,22 +245,31 @@ More than one character can be used, so for example to only see failed and skipp
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
collected 6 items
|
||||
|
||||
test_examples.py Fs [100%]
|
||||
test_example.py .FEsxX [100%]
|
||||
|
||||
=================================== FAILURES ===================================
|
||||
__________________________________ test_fail ___________________________________
|
||||
================================== 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_examples.py:14: AssertionError
|
||||
=========================== short test summary info ============================
|
||||
FAIL test_examples.py::test_fail
|
||||
SKIP [1] test_examples.py:21: Example
|
||||
===================== 1 failed, 1 skipped in 0.09 seconds ======================
|
||||
test_example.py:14: AssertionError
|
||||
========================= short test summary info ==========================
|
||||
FAIL test_example.py::test_fail
|
||||
SKIP [1] $REGENDOC_TMPDIR/test_example.py:23: skipping this test
|
||||
1 failed, 1 passed, 1 skipped, 1 xfailed, 1 xpassed, 1 error in 0.12 seconds
|
||||
|
||||
Using ``p`` lists the passing tests, whilst ``P`` adds an extra section "PASSES" with those tests that passed but had
|
||||
captured output:
|
||||
|
@ -234,18 +280,34 @@ captured output:
|
|||
=========================== test session starts ============================
|
||||
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 2 items
|
||||
collected 6 items
|
||||
|
||||
test_examples.py .. [100%]
|
||||
=========================== short test summary info ============================
|
||||
PASSED test_examples.py::test_pass
|
||||
PASSED test_examples.py::test_pass_with_output
|
||||
test_example.py .FEsxX [100%]
|
||||
|
||||
==================================== PASSES ====================================
|
||||
____________________________ test_pass_with_output _____________________________
|
||||
----------------------------- Captured stdout call -----------------------------
|
||||
Passing test
|
||||
=========================== 2 passed in 0.04 seconds ===========================
|
||||
================================== 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
|
||||
========================= 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:
|
||||
|
||||
|
@ -354,6 +416,20 @@ To set the name of the root test suite xml item, you can configure the ``junit_s
|
|||
[pytest]
|
||||
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
|
||||
|
@ -543,14 +619,10 @@ Creating resultlog format files
|
|||
|
||||
.. 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
|
||||
`pytest-tap <https://pypi.org/project/pytest-tap/>`_ plugin which provides
|
||||
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>`_.
|
||||
See `the deprecation docs <https://docs.pytest.org/en/latest/deprecations.html#result-log-result-log>`__
|
||||
for more information.
|
||||
|
||||
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::
|
||||
|
||||
$ 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::
|
||||
|
||||
|
|
|
@ -391,44 +391,85 @@ co_equal = compile(
|
|||
)
|
||||
|
||||
|
||||
@attr.s(repr=False)
|
||||
class ExceptionInfo(object):
|
||||
""" wraps sys.exc_info() objects and offers
|
||||
help for navigating the traceback.
|
||||
"""
|
||||
|
||||
_striptext = ""
|
||||
_assert_start_repr = (
|
||||
"AssertionError(u'assert " if _PY2 else "AssertionError('assert "
|
||||
)
|
||||
|
||||
def __init__(self, tup=None, exprinfo=None):
|
||||
import _pytest._code
|
||||
_excinfo = attr.ib()
|
||||
_striptext = attr.ib(default="")
|
||||
_traceback = attr.ib(default=None)
|
||||
|
||||
if tup is None:
|
||||
@classmethod
|
||||
def from_current(cls, exprinfo=None):
|
||||
"""returns an ExceptionInfo matching the current traceback
|
||||
|
||||
.. warning::
|
||||
|
||||
Experimental API
|
||||
|
||||
|
||||
:param exprinfo: a text string helping to determine if we should
|
||||
strip ``AssertionError`` from the output, defaults
|
||||
to the exception message/``__str__()``
|
||||
"""
|
||||
tup = sys.exc_info()
|
||||
_striptext = ""
|
||||
if exprinfo is None and isinstance(tup[1], AssertionError):
|
||||
exprinfo = getattr(tup[1], "msg", None)
|
||||
if exprinfo is None:
|
||||
exprinfo = py.io.saferepr(tup[1])
|
||||
if exprinfo and exprinfo.startswith(self._assert_start_repr):
|
||||
self._striptext = "AssertionError: "
|
||||
self._excinfo = tup
|
||||
#: the exception class
|
||||
self.type = tup[0]
|
||||
#: the exception instance
|
||||
self.value = tup[1]
|
||||
#: the exception raw traceback
|
||||
self.tb = tup[2]
|
||||
#: the exception type name
|
||||
self.typename = self.type.__name__
|
||||
#: the exception traceback (_pytest._code.Traceback instance)
|
||||
self.traceback = _pytest._code.Traceback(self.tb, excinfo=ref(self))
|
||||
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):
|
||||
try:
|
||||
if self._excinfo is None:
|
||||
return "<ExceptionInfo for raises contextmanager>"
|
||||
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
|
||||
except AttributeError:
|
||||
return "<ExceptionInfo uninitialized>"
|
||||
|
||||
def exconly(self, tryshort=False):
|
||||
""" return the exception as a string
|
||||
|
@ -516,11 +557,9 @@ class ExceptionInfo(object):
|
|||
return fmt.repr_excinfo(self)
|
||||
|
||||
def __str__(self):
|
||||
try:
|
||||
entry = self.traceback[-1]
|
||||
except AttributeError:
|
||||
if self._excinfo is None:
|
||||
return repr(self)
|
||||
else:
|
||||
entry = self.traceback[-1]
|
||||
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
|
||||
return str(loc)
|
||||
|
||||
|
|
|
@ -51,6 +51,19 @@ else:
|
|||
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):
|
||||
"""PEP302 Import hook which rewrites asserts."""
|
||||
|
||||
|
@ -265,11 +278,11 @@ class AssertionRewritingHook(object):
|
|||
|
||||
def _warn_already_imported(self, name):
|
||||
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),
|
||||
self.config,
|
||||
self.config.hook,
|
||||
stacklevel=5,
|
||||
)
|
||||
|
||||
|
@ -828,6 +841,13 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
self.push_format_context()
|
||||
# Rewrite assert into a bunch of statements.
|
||||
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.
|
||||
body = self.on_failure
|
||||
negation = ast.UnaryOp(ast.Not(), top_condition)
|
||||
|
@ -858,6 +878,33 @@ class AssertionRewriter(ast.NodeVisitor):
|
|||
set_location(stmt, assert_.lineno, assert_.col_offset)
|
||||
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):
|
||||
# Display the repr of the name if it's a local variable or
|
||||
# _should_repr_global_name() thinks it's acceptable.
|
||||
|
|
|
@ -122,6 +122,12 @@ def assertrepr_compare(config, op, left, right):
|
|||
def isset(x):
|
||||
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):
|
||||
try:
|
||||
iter(obj)
|
||||
|
@ -142,6 +148,9 @@ def assertrepr_compare(config, op, left, right):
|
|||
explanation = _compare_eq_set(left, right, verbose)
|
||||
elif isdict(left) and isdict(right):
|
||||
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):
|
||||
expl = _compare_eq_iterable(left, right, verbose)
|
||||
if explanation is not None:
|
||||
|
@ -155,7 +164,7 @@ def assertrepr_compare(config, op, left, right):
|
|||
explanation = [
|
||||
u"(pytest_assertion plugin: representation of details failed. "
|
||||
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:
|
||||
|
@ -315,6 +324,38 @@ def _compare_eq_dict(left, right, verbose=False):
|
|||
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):
|
||||
index = text.find(term)
|
||||
head = text[:index]
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
||||
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
|
||||
class Cache(object):
|
||||
|
@ -52,12 +59,12 @@ class Cache(object):
|
|||
return resolve_from_str(config.getini("cache_dir"), config.rootdir)
|
||||
|
||||
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
|
||||
|
||||
_issue_config_warning(
|
||||
_issue_warning_captured(
|
||||
PytestWarning(fmt.format(**args) if args else fmt),
|
||||
self._config,
|
||||
self._config.hook,
|
||||
stacklevel=3,
|
||||
)
|
||||
|
||||
|
@ -140,6 +147,10 @@ class Cache(object):
|
|||
msg = u"# Created by pytest automatically.\n*"
|
||||
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):
|
||||
""" Plugin which implements the --lf (run last-failing) option """
|
||||
|
|
|
@ -773,9 +773,9 @@ def _py36_windowsconsoleio_workaround(stream):
|
|||
f.line_buffering,
|
||||
)
|
||||
|
||||
sys.__stdin__ = sys.stdin = _reopen_stdio(sys.stdin, "rb")
|
||||
sys.__stdout__ = sys.stdout = _reopen_stdio(sys.stdout, "wb")
|
||||
sys.__stderr__ = sys.stderr = _reopen_stdio(sys.stderr, "wb")
|
||||
sys.stdin = _reopen_stdio(sys.stdin, "rb")
|
||||
sys.stdout = _reopen_stdio(sys.stdout, "wb")
|
||||
sys.stderr = _reopen_stdio(sys.stderr, "wb")
|
||||
|
||||
|
||||
def _attempt_to_close_capture_file(f):
|
||||
|
|
|
@ -45,11 +45,11 @@ MODULE_NOT_FOUND_ERROR = "ModuleNotFoundError" if PY36 else "ImportError"
|
|||
|
||||
if _PY3:
|
||||
from collections.abc import MutableMapping as MappingMixin
|
||||
from collections.abc import Mapping, Sequence
|
||||
from collections.abc import Iterable, Mapping, Sequence, Sized
|
||||
else:
|
||||
# those raise DeprecationWarnings in Python >=3.7
|
||||
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):
|
||||
|
@ -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:
|
||||
STRING_TYPES = bytes, str
|
||||
UNICODE_TYPES = six.text_type
|
||||
|
@ -221,9 +233,10 @@ if _PY3:
|
|||
|
||||
"""
|
||||
if isinstance(val, bytes):
|
||||
return _bytes_to_ascii(val)
|
||||
ret = _bytes_to_ascii(val)
|
||||
else:
|
||||
return val.encode("unicode_escape").decode("ascii")
|
||||
ret = val.encode("unicode_escape").decode("ascii")
|
||||
return _translate_non_printable(ret)
|
||||
|
||||
|
||||
else:
|
||||
|
@ -241,11 +254,12 @@ else:
|
|||
"""
|
||||
if isinstance(val, bytes):
|
||||
try:
|
||||
return val.encode("ascii")
|
||||
ret = val.decode("ascii")
|
||||
except UnicodeDecodeError:
|
||||
return val.encode("string-escape")
|
||||
ret = val.encode("string-escape").decode("ascii")
|
||||
else:
|
||||
return val.encode("unicode-escape")
|
||||
ret = val.encode("unicode-escape").decode("ascii")
|
||||
return _translate_non_printable(ret)
|
||||
|
||||
|
||||
class _PytestWrapper(object):
|
||||
|
@ -375,7 +389,6 @@ else:
|
|||
COLLECT_FAKEMODULE_ATTRIBUTES = (
|
||||
"Collector",
|
||||
"Module",
|
||||
"Generator",
|
||||
"Function",
|
||||
"Instance",
|
||||
"Session",
|
||||
|
|
|
@ -26,11 +26,14 @@ from .exceptions import PrintHelp
|
|||
from .exceptions import UsageError
|
||||
from .findpaths import determine_setup
|
||||
from .findpaths import exists
|
||||
from _pytest import deprecated
|
||||
from _pytest._code import ExceptionInfo
|
||||
from _pytest._code import filter_traceback
|
||||
from _pytest.compat import lru_cache
|
||||
from _pytest.compat import safe_str
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.outcomes import Skipped
|
||||
from _pytest.warning_types import PytestWarning
|
||||
|
||||
hookimpl = HookimplMarker("pytest")
|
||||
hookspec = HookspecMarker("pytest")
|
||||
|
@ -173,12 +176,9 @@ def _prepareconfig(args=None, plugins=None):
|
|||
elif isinstance(args, py.path.local):
|
||||
args = [str(args)]
|
||||
elif not isinstance(args, (tuple, list)):
|
||||
if not isinstance(args, str):
|
||||
raise ValueError("not a string or argument list: %r" % (args,))
|
||||
args = shlex.split(args, posix=sys.platform != "win32")
|
||||
from _pytest import deprecated
|
||||
msg = "`args` parameter expected to be a list or tuple of strings, got: {!r} (type: {})"
|
||||
raise TypeError(msg.format(args, type(args)))
|
||||
|
||||
warning = deprecated.MAIN_STR_ARGS
|
||||
config = get_config()
|
||||
pluginmanager = config.pluginmanager
|
||||
try:
|
||||
|
@ -189,9 +189,9 @@ def _prepareconfig(args=None, plugins=None):
|
|||
else:
|
||||
pluginmanager.register(plugin)
|
||||
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(
|
||||
pluginmanager=pluginmanager, args=args
|
||||
)
|
||||
|
@ -245,14 +245,7 @@ class PytestPluginManager(PluginManager):
|
|||
Use :py:meth:`pluggy.PluginManager.add_hookspecs <PluginManager.add_hookspecs>`
|
||||
instead.
|
||||
"""
|
||||
warning = dict(
|
||||
code="I2",
|
||||
fslocation=_pytest._code.getfslineno(sys._getframe(1)),
|
||||
nodeid=None,
|
||||
message="use pluginmanager.add_hookspecs instead of "
|
||||
"deprecated addhooks() method.",
|
||||
)
|
||||
self._warn(warning)
|
||||
warnings.warn(deprecated.PLUGIN_MANAGER_ADDHOOKS, stacklevel=2)
|
||||
return self.add_hookspecs(module_or_class)
|
||||
|
||||
def parse_hookimpl_opts(self, plugin, name):
|
||||
|
@ -261,8 +254,8 @@ class PytestPluginManager(PluginManager):
|
|||
# (see issue #1073)
|
||||
if not name.startswith("pytest_"):
|
||||
return
|
||||
# ignore some historic special names which can not be hooks anyway
|
||||
if name == "pytest_plugins" or name.startswith("pytest_funcarg__"):
|
||||
# ignore names which can not be hooks
|
||||
if name == "pytest_plugins":
|
||||
return
|
||||
|
||||
method = getattr(plugin, name)
|
||||
|
@ -275,10 +268,14 @@ class PytestPluginManager(PluginManager):
|
|||
# collect unmarked hooks as long as they have the `pytest_' prefix
|
||||
if opts is None and name.startswith("pytest_"):
|
||||
opts = {}
|
||||
|
||||
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"):
|
||||
opts.setdefault(name, hasattr(method, name))
|
||||
|
||||
opts.setdefault(name, hasattr(method, name) or name in known_marks)
|
||||
return opts
|
||||
|
||||
def parse_hookspec_opts(self, module_or_class, name):
|
||||
|
@ -287,21 +284,29 @@ class PytestPluginManager(PluginManager):
|
|||
)
|
||||
if opts is None:
|
||||
method = getattr(module_or_class, name)
|
||||
|
||||
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 = {
|
||||
"firstresult": hasattr(method, "firstresult"),
|
||||
"historic": hasattr(method, "historic"),
|
||||
"firstresult": hasattr(method, "firstresult")
|
||||
or "firstresult" in known_marks,
|
||||
"historic": hasattr(method, "historic")
|
||||
or "historic" in known_marks,
|
||||
}
|
||||
return opts
|
||||
|
||||
def register(self, plugin, name=None):
|
||||
if name in ["pytest_catchlog", "pytest_capturelog"]:
|
||||
self._warn(
|
||||
warnings.warn(
|
||||
PytestWarning(
|
||||
"{} plugin has been merged into the core, "
|
||||
"please remove it from your requirements.".format(
|
||||
name.replace("_", "-")
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
ret = super(PytestPluginManager, self).register(plugin, name)
|
||||
if ret:
|
||||
|
@ -336,14 +341,6 @@ class PytestPluginManager(PluginManager):
|
|||
)
|
||||
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
|
||||
#
|
||||
|
@ -443,11 +440,11 @@ class PytestPluginManager(PluginManager):
|
|||
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
|
||||
)
|
||||
|
||||
warnings.warn_explicit(
|
||||
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST,
|
||||
category=None,
|
||||
filename=str(conftestpath),
|
||||
lineno=0,
|
||||
fail(
|
||||
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST.format(
|
||||
conftestpath, self._confcutdir
|
||||
),
|
||||
pytrace=False,
|
||||
)
|
||||
except Exception:
|
||||
raise ConftestImportFailure(conftestpath, sys.exc_info())
|
||||
|
@ -470,9 +467,20 @@ class PytestPluginManager(PluginManager):
|
|||
#
|
||||
|
||||
def consider_preparse(self, args):
|
||||
for opt1, opt2 in zip(args, args[1:]):
|
||||
if opt1 == "-p":
|
||||
self.consider_pluginarg(opt2)
|
||||
i = 0
|
||||
n = len(args)
|
||||
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):
|
||||
if arg.startswith("no:"):
|
||||
|
@ -507,7 +515,7 @@ class PytestPluginManager(PluginManager):
|
|||
# "terminal" or "capture". Those plugins are registered under their
|
||||
# basename for historic purposes but must be imported with the
|
||||
# _pytest prefix.
|
||||
assert isinstance(modname, (six.text_type, str)), (
|
||||
assert isinstance(modname, six.string_types), (
|
||||
"module name as text required, got %r" % modname
|
||||
)
|
||||
modname = str(modname)
|
||||
|
@ -531,7 +539,13 @@ class PytestPluginManager(PluginManager):
|
|||
six.reraise(new_exc_type, new_exc, sys.exc_info()[2])
|
||||
|
||||
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:
|
||||
mod = sys.modules[importspec]
|
||||
self.register(mod, modname)
|
||||
|
@ -606,16 +620,9 @@ class Config(object):
|
|||
self._override_ini = ()
|
||||
self._opt2dest = {}
|
||||
self._cleanup = []
|
||||
self._warn = self.pluginmanager._warn
|
||||
self.pluginmanager.register(self, "pytestconfig")
|
||||
self._configured = False
|
||||
|
||||
def do_setns(dic):
|
||||
import pytest
|
||||
|
||||
setns(pytest, dic)
|
||||
|
||||
self.hook.pytest_namespace.call_historic(do_setns, {})
|
||||
self.invocation_dir = py.path.local()
|
||||
self.hook.pytest_addoption.call_historic(kwargs=dict(parser=self._parser))
|
||||
|
||||
def add_cleanup(self, func):
|
||||
|
@ -637,36 +644,6 @@ class Config(object):
|
|||
fin = self._cleanup.pop()
|
||||
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):
|
||||
return self.pluginmanager.get_plugin("terminalreporter")._tw
|
||||
|
||||
|
@ -731,7 +708,6 @@ class Config(object):
|
|||
self.rootdir, self.inifile, self.inicfg = r
|
||||
self._parser.extra_info["rootdir"] = self.rootdir
|
||||
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("minversion", "minimally required pytest version")
|
||||
self._override_ini = ns.override_ini or ()
|
||||
|
@ -822,7 +798,15 @@ class Config(object):
|
|||
if ns.help or ns.version:
|
||||
# we don't want to prevent --help/--version to work
|
||||
# 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:
|
||||
raise
|
||||
|
||||
|
|
|
@ -18,6 +18,8 @@ class Parser(object):
|
|||
there's an error processing the command line arguments.
|
||||
"""
|
||||
|
||||
prog = None
|
||||
|
||||
def __init__(self, usage=None, processopt=None):
|
||||
self._anonymous = OptionGroup("custom options", parser=self)
|
||||
self._groups = []
|
||||
|
@ -82,7 +84,7 @@ class Parser(object):
|
|||
def _getparser(self):
|
||||
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]
|
||||
for group in groups:
|
||||
if group.options:
|
||||
|
@ -319,12 +321,13 @@ class OptionGroup(object):
|
|||
|
||||
|
||||
class MyOptionParser(argparse.ArgumentParser):
|
||||
def __init__(self, parser, extra_info=None):
|
||||
def __init__(self, parser, extra_info=None, prog=None):
|
||||
if not extra_info:
|
||||
extra_info = {}
|
||||
self._parser = parser
|
||||
argparse.ArgumentParser.__init__(
|
||||
self,
|
||||
prog=prog,
|
||||
usage=parser._usage,
|
||||
add_help=False,
|
||||
formatter_class=DropShorterLongHelpFormatter,
|
||||
|
|
|
@ -3,6 +3,7 @@ import os
|
|||
import py
|
||||
|
||||
from .exceptions import UsageError
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
|
||||
def exists(path, ignore=EnvironmentError):
|
||||
|
@ -34,15 +35,10 @@ def getcfg(args, config=None):
|
|||
iniconfig = py.iniconfig.IniConfig(p)
|
||||
if "pytest" in iniconfig.sections:
|
||||
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(
|
||||
RemovedInPytest4Warning(
|
||||
CFG_PYTEST_SECTION.format(filename=inibasename)
|
||||
),
|
||||
config=config,
|
||||
stacklevel=2,
|
||||
fail(
|
||||
CFG_PYTEST_SECTION.format(filename=inibasename),
|
||||
pytrace=False,
|
||||
)
|
||||
return base, p, iniconfig["pytest"]
|
||||
if (
|
||||
|
@ -112,40 +108,41 @@ def determine_setup(inifile, args, rootdir_cmd_arg=None, config=None):
|
|||
inicfg = iniconfig[section]
|
||||
if is_cfg_file and section == "pytest" and config is not None:
|
||||
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
|
||||
# the deprecation expires.
|
||||
_issue_config_warning(
|
||||
CFG_PYTEST_SECTION.format(filename=str(inifile)),
|
||||
config,
|
||||
stacklevel=2,
|
||||
fail(
|
||||
CFG_PYTEST_SECTION.format(filename=str(inifile)), pytrace=False
|
||||
)
|
||||
break
|
||||
except KeyError:
|
||||
inicfg = None
|
||||
if rootdir_cmd_arg is None:
|
||||
rootdir = get_common_ancestor(dirs)
|
||||
else:
|
||||
ancestor = get_common_ancestor(dirs)
|
||||
rootdir, inifile, inicfg = getcfg([ancestor], config=config)
|
||||
if rootdir is None:
|
||||
for rootdir in ancestor.parts(reverse=True):
|
||||
if rootdir.join("setup.py").exists():
|
||||
if rootdir is None and rootdir_cmd_arg is None:
|
||||
for possible_rootdir in ancestor.parts(reverse=True):
|
||||
if possible_rootdir.join("setup.py").exists():
|
||||
rootdir = possible_rootdir
|
||||
break
|
||||
else:
|
||||
if dirs != [ancestor]:
|
||||
rootdir, inifile, inicfg = getcfg(dirs, config=config)
|
||||
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] == "/"
|
||||
if is_fs_root:
|
||||
rootdir = ancestor
|
||||
if rootdir_cmd_arg:
|
||||
rootdir_abs_path = py.path.local(os.path.expandvars(rootdir_cmd_arg))
|
||||
if not os.path.isdir(str(rootdir_abs_path)):
|
||||
rootdir = py.path.local(os.path.expandvars(rootdir_cmd_arg))
|
||||
if not rootdir.isdir():
|
||||
raise UsageError(
|
||||
"Directory '{}' not found. Check your '--rootdir' option.".format(
|
||||
rootdir_abs_path
|
||||
rootdir
|
||||
)
|
||||
)
|
||||
rootdir = rootdir_abs_path
|
||||
return rootdir, inifile, inicfg or {}
|
||||
|
|
|
@ -77,18 +77,21 @@ class pytestPDB(object):
|
|||
_saved = []
|
||||
|
||||
@classmethod
|
||||
def set_trace(cls, set_break=True):
|
||||
""" invoke PDB set_trace debugging, dropping any IO capturing. """
|
||||
def _init_pdb(cls, *args, **kwargs):
|
||||
""" Initialize PDB debugging, dropping any IO capturing. """
|
||||
import _pytest.config
|
||||
|
||||
frame = sys._getframe().f_back
|
||||
if cls._pluginmanager is not None:
|
||||
capman = cls._pluginmanager.getplugin("capturemanager")
|
||||
if capman:
|
||||
capman.suspend_global_capture(in_=True)
|
||||
tw = _pytest.config.create_terminal_writer(cls._config)
|
||||
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)")
|
||||
else:
|
||||
tw.sep(">", "PDB set_trace")
|
||||
|
@ -129,12 +132,17 @@ class pytestPDB(object):
|
|||
self._pytest_capman.suspend_global_capture(in_=True)
|
||||
return ret
|
||||
|
||||
_pdb = _PdbWrapper()
|
||||
_pdb = _PdbWrapper(**kwargs)
|
||||
cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config, pdb=_pdb)
|
||||
else:
|
||||
_pdb = cls._pdb_cls()
|
||||
_pdb = cls._pdb_cls(**kwargs)
|
||||
return _pdb
|
||||
|
||||
if set_break:
|
||||
@classmethod
|
||||
def set_trace(cls, *args, **kwargs):
|
||||
"""Invoke debugging via ``Pdb.set_trace``, dropping any IO capturing."""
|
||||
frame = sys._getframe().f_back
|
||||
_pdb = cls._init_pdb(*args, **kwargs)
|
||||
_pdb.set_trace(frame)
|
||||
|
||||
|
||||
|
@ -161,9 +169,9 @@ class PdbTrace(object):
|
|||
|
||||
|
||||
def _test_pytest_function(pyfuncitem):
|
||||
pytestPDB.set_trace(set_break=False)
|
||||
_pdb = pytestPDB._init_pdb()
|
||||
testfunction = pyfuncitem.obj
|
||||
pyfuncitem.obj = pdb.runcall
|
||||
pyfuncitem.obj = _pdb.runcall
|
||||
if pyfuncitem._isyieldedfunction():
|
||||
arg_list = list(pyfuncitem._args)
|
||||
arg_list.insert(0, testfunction)
|
||||
|
|
|
@ -14,66 +14,38 @@ from __future__ import print_function
|
|||
|
||||
from _pytest.warning_types import PytestDeprecationWarning
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
from _pytest.warning_types import UnformattedWarning
|
||||
|
||||
|
||||
MAIN_STR_ARGS = RemovedInPytest4Warning(
|
||||
"passing a string to pytest.main() is deprecated, "
|
||||
"pass a list of arguments instead."
|
||||
)
|
||||
YIELD_TESTS = "yield tests were removed in pytest 4.0 - {name} will be ignored"
|
||||
|
||||
YIELD_TESTS = RemovedInPytest4Warning(
|
||||
"yield tests are deprecated, and scheduled to be removed in pytest 4.0"
|
||||
)
|
||||
|
||||
CACHED_SETUP = RemovedInPytest4Warning(
|
||||
"cached_setup is deprecated and will be removed in a future release. "
|
||||
"Use standard fixture functions instead."
|
||||
)
|
||||
|
||||
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_FUNCTION_CALL = (
|
||||
'Fixture "{name}" called directly. Fixtures are not meant to be called directly,\n'
|
||||
"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."
|
||||
)
|
||||
|
||||
FIXTURE_NAMED_REQUEST = PytestDeprecationWarning(
|
||||
"'request' is a reserved name for fixtures and will raise an error in future versions"
|
||||
)
|
||||
|
||||
CFG_PYTEST_SECTION = UnformattedWarning(
|
||||
RemovedInPytest4Warning,
|
||||
"[pytest] section in {filename} files is deprecated, use [tool:pytest] instead.",
|
||||
)
|
||||
CFG_PYTEST_SECTION = "[pytest] section in {filename} files is no longer supported, change to [tool:pytest] instead."
|
||||
|
||||
GETFUNCARGVALUE = RemovedInPytest4Warning(
|
||||
"getfuncargvalue is deprecated, use getfixturevalue"
|
||||
)
|
||||
|
||||
RESULT_LOG = RemovedInPytest4Warning(
|
||||
"--result-log is deprecated and scheduled for removal in pytest 4.0.\n"
|
||||
"See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information."
|
||||
RAISES_MESSAGE_PARAMETER = PytestDeprecationWarning(
|
||||
"The 'message' parameter is deprecated.\n"
|
||||
"(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(
|
||||
|
@ -82,42 +54,36 @@ MARK_INFO_ATTRIBUTE = RemovedInPytest4Warning(
|
|||
"Docs: https://docs.pytest.org/en/latest/mark.html#updating-code"
|
||||
)
|
||||
|
||||
MARK_PARAMETERSET_UNPACKING = RemovedInPytest4Warning(
|
||||
"Applying marks directly to parameters is deprecated,"
|
||||
" please use pytest.param(..., marks=...) instead.\n"
|
||||
"For more details, see: https://docs.pytest.org/en/latest/parametrize.html"
|
||||
RAISES_EXEC = PytestDeprecationWarning(
|
||||
"raises(..., '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"
|
||||
)
|
||||
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(
|
||||
"Node.warn(code, message) form has been deprecated, use Node.warn(warning_instance) instead."
|
||||
)
|
||||
|
||||
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, "
|
||||
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = (
|
||||
"Defining 'pytest_plugins' in a non-top-level conftest is no longer supported "
|
||||
"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_namespace is deprecated and will be removed soon"
|
||||
PYTEST_CONFIG_GLOBAL = PytestDeprecationWarning(
|
||||
"the `pytest.config` global is deprecated. Please use `request.config` "
|
||||
"or `pytest_configure` (if you're a pytest plugin) instead."
|
||||
)
|
||||
|
||||
PYTEST_ENSURETEMP = RemovedInPytest4Warning(
|
||||
"pytest/tmpdir_factory.ensuretemp is deprecated, \n"
|
||||
"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"
|
||||
)
|
||||
|
|
|
@ -38,8 +38,6 @@ from _pytest.deprecated import FIXTURE_NAMED_REQUEST
|
|||
from _pytest.outcomes import fail
|
||||
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)
|
||||
class PseudoFixtureDef(object):
|
||||
|
@ -469,43 +467,6 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
|||
if argname not in item.funcargs:
|
||||
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):
|
||||
""" Dynamically run a named fixture function.
|
||||
|
||||
|
@ -605,8 +566,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
|
|||
)
|
||||
fail(msg, pytrace=False)
|
||||
else:
|
||||
# indices might not be set if old-style metafunc.addcall() was used
|
||||
param_index = funcitem.callspec.indices.get(argname, 0)
|
||||
param_index = funcitem.callspec.indices[argname]
|
||||
# if a parametrize invocation set a scope it will override
|
||||
# the static scope defined with the fixture function
|
||||
paramscopenum = funcitem.callspec._arg2scopenum.get(argname)
|
||||
|
@ -982,34 +942,17 @@ def _ensure_immutable_ids(ids):
|
|||
return tuple(ids)
|
||||
|
||||
|
||||
def wrap_function_to_warning_if_called_directly(function, fixture_marker):
|
||||
"""Wrap the given fixture function so we can issue warnings about it being called directly, instead of
|
||||
used as an argument in a test function.
|
||||
def wrap_function_to_error_out_if_called_directly(function, fixture_marker):
|
||||
"""Wrap the given fixture function so we can raise an error about it being called directly,
|
||||
instead of used as an argument in a test function.
|
||||
"""
|
||||
is_yield_function = is_generator(function)
|
||||
warning = FIXTURE_FUNCTION_CALL.format(
|
||||
message = FIXTURE_FUNCTION_CALL.format(
|
||||
name=fixture_marker.name or function.__name__
|
||||
)
|
||||
|
||||
if is_yield_function:
|
||||
|
||||
@functools.wraps(function)
|
||||
@six.wraps(function)
|
||||
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
|
||||
fail(message, pytrace=False)
|
||||
|
||||
# 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)
|
||||
|
@ -1035,7 +978,7 @@ class FixtureFunctionMarker(object):
|
|||
"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__
|
||||
if name == "request":
|
||||
|
@ -1155,7 +1098,6 @@ class FixtureManager(object):
|
|||
by a lookup of their FuncFixtureInfo.
|
||||
"""
|
||||
|
||||
_argprefix = "pytest_funcarg__"
|
||||
FixtureLookupError = FixtureLookupError
|
||||
FixtureLookupErrorRepr = FixtureLookupErrorRepr
|
||||
|
||||
|
@ -1265,19 +1207,20 @@ class FixtureManager(object):
|
|||
if faclist:
|
||||
fixturedef = faclist[-1]
|
||||
if fixturedef.params is not None:
|
||||
parametrize_func = getattr(metafunc.function, "parametrize", None)
|
||||
if parametrize_func is not None:
|
||||
parametrize_func = parametrize_func.combined
|
||||
func_params = getattr(parametrize_func, "args", [[None]])
|
||||
func_kwargs = getattr(parametrize_func, "kwargs", {})
|
||||
# skip directly parametrized arguments
|
||||
if "argnames" in func_kwargs:
|
||||
argnames = parametrize_func.kwargs["argnames"]
|
||||
markers = list(metafunc.definition.iter_markers("parametrize"))
|
||||
for parametrize_mark in markers:
|
||||
if "argnames" in parametrize_mark.kwargs:
|
||||
argnames = parametrize_mark.kwargs["argnames"]
|
||||
else:
|
||||
argnames = func_params[0]
|
||||
argnames = parametrize_mark.args[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:
|
||||
argnames = [
|
||||
x.strip() for x in argnames.split(",") if x.strip()
|
||||
]
|
||||
if argname in argnames:
|
||||
break
|
||||
else:
|
||||
metafunc.parametrize(
|
||||
argname,
|
||||
fixturedef.params,
|
||||
|
@ -1293,8 +1236,6 @@ class FixtureManager(object):
|
|||
items[:] = reorder_items(items)
|
||||
|
||||
def parsefactories(self, node_or_obj, nodeid=NOTSET, unittest=False):
|
||||
from _pytest import deprecated
|
||||
|
||||
if nodeid is not NOTSET:
|
||||
holderobj = node_or_obj
|
||||
else:
|
||||
|
@ -1303,44 +1244,20 @@ class FixtureManager(object):
|
|||
if holderobj in self._holderobjseen:
|
||||
return
|
||||
|
||||
from _pytest.nodes import _CompatProperty
|
||||
|
||||
self._holderobjseen.add(holderobj)
|
||||
autousenames = []
|
||||
for name in dir(holderobj):
|
||||
# The attribute can be an arbitrary descriptor, so the attribute
|
||||
# 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)
|
||||
marker = getfixturemarker(obj)
|
||||
# fixture functions have a pytest_funcarg__ prefix (pre-2.3 style)
|
||||
# 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):
|
||||
if not isinstance(marker, FixtureFunctionMarker):
|
||||
# magic globals with __getattr__ might have got us a wrong
|
||||
# fixture attribute
|
||||
continue
|
||||
else:
|
||||
|
||||
if marker.name:
|
||||
name = marker.name
|
||||
assert not name.startswith(self._argprefix), FIXTURE_MSG.format(name)
|
||||
|
||||
# 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
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
""" hook specifications for pytest plugins, invoked from main.py and builtin plugins. """
|
||||
from pluggy import HookspecMarker
|
||||
|
||||
from .deprecated import PYTEST_NAMESPACE
|
||||
from _pytest.deprecated import PYTEST_LOGWARNING
|
||||
|
||||
|
||||
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)
|
||||
def pytest_plugin_registered(plugin, manager):
|
||||
""" 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):
|
||||
"""
|
||||
.. deprecated:: 3.8
|
||||
|
|
|
@ -263,16 +263,6 @@ def record_property(request):
|
|||
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
|
||||
def record_xml_attribute(request):
|
||||
"""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",
|
||||
default="no",
|
||||
) # 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):
|
||||
|
@ -334,6 +329,7 @@ def pytest_configure(config):
|
|||
config.option.junitprefix,
|
||||
config.getini("junit_suite_name"),
|
||||
config.getini("junit_logging"),
|
||||
config.getini("junit_duration_report"),
|
||||
)
|
||||
config.pluginmanager.register(config._xml)
|
||||
|
||||
|
@ -361,12 +357,20 @@ def mangle_test_address(address):
|
|||
|
||||
|
||||
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))
|
||||
self.logfile = os.path.normpath(os.path.abspath(logfile))
|
||||
self.prefix = prefix
|
||||
self.suite_name = suite_name
|
||||
self.logging = logging
|
||||
self.report_duration = report_duration
|
||||
self.stats = dict.fromkeys(["error", "passed", "failure", "skipped"], 0)
|
||||
self.node_reporters = {} # nodeid -> _NodeReporter
|
||||
self.node_reporters_ordered = []
|
||||
|
@ -500,6 +504,7 @@ class LogXML(object):
|
|||
"""accumulates total duration for nodeid from given report and updates
|
||||
the Junit.testcase with the new total if already created.
|
||||
"""
|
||||
if self.report_duration == "total" or report.when == self.report_duration:
|
||||
reporter = self.node_reporter(report)
|
||||
reporter.duration += getattr(report, "duration", 0.0)
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import functools
|
|||
import os
|
||||
import pkgutil
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import attr
|
||||
import py
|
||||
|
@ -18,6 +19,7 @@ from _pytest import nodes
|
|||
from _pytest.config import directory_arg
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.config import UsageError
|
||||
from _pytest.deprecated import PYTEST_CONFIG_GLOBAL
|
||||
from _pytest.outcomes import exit
|
||||
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):
|
||||
__import__("pytest").config = config # compatibility
|
||||
__import__("pytest").config = _ConfigDeprecated(config) # compatibility
|
||||
|
||||
|
||||
def wrap_session(config, doit):
|
||||
|
@ -187,8 +205,8 @@ def wrap_session(config, doit):
|
|||
raise
|
||||
except Failed:
|
||||
session.exitstatus = EXIT_TESTSFAILED
|
||||
except KeyboardInterrupt:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
except (KeyboardInterrupt, exit.Exception):
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
exitstatus = EXIT_INTERRUPTED
|
||||
if initstate <= 2 and isinstance(excinfo.value, exit.Exception):
|
||||
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)
|
||||
session.exitstatus = exitstatus
|
||||
except: # noqa
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
config.notify_exception(excinfo, config.option)
|
||||
session.exitstatus = EXIT_INTERNALERROR
|
||||
if excinfo.errisinstance(SystemExit):
|
||||
|
|
|
@ -11,19 +11,10 @@ from .structures import Mark
|
|||
from .structures import MARK_GEN
|
||||
from .structures import MarkDecorator
|
||||
from .structures import MarkGenerator
|
||||
from .structures import MarkInfo
|
||||
from .structures import ParameterSet
|
||||
from .structures import transfer_markers
|
||||
from _pytest.config import UsageError
|
||||
|
||||
__all__ = [
|
||||
"Mark",
|
||||
"MarkInfo",
|
||||
"MarkDecorator",
|
||||
"MarkGenerator",
|
||||
"transfer_markers",
|
||||
"get_empty_parameterset_mark",
|
||||
]
|
||||
__all__ = ["Mark", "MarkDecorator", "MarkGenerator", "get_empty_parameterset_mark"]
|
||||
|
||||
|
||||
def param(*values, **kw):
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
import inspect
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
from functools import reduce
|
||||
from operator import attrgetter
|
||||
|
||||
import attr
|
||||
from six.moves import map
|
||||
import six
|
||||
|
||||
from ..compat import ascii_escaped
|
||||
from ..compat import getfslineno
|
||||
from ..compat import MappingMixin
|
||||
from ..compat import NOTSET
|
||||
from ..deprecated import MARK_INFO_ATTRIBUTE
|
||||
from ..deprecated import MARK_PARAMETERSET_UNPACKING
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
|
||||
|
@ -70,46 +68,33 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
|
|||
else:
|
||||
assert isinstance(marks, (tuple, list, set))
|
||||
|
||||
def param_extract_id(id=None):
|
||||
return id
|
||||
|
||||
id_ = param_extract_id(**kw)
|
||||
id_ = kw.pop("id", None)
|
||||
if id_ is not None:
|
||||
if not isinstance(id_, six.string_types):
|
||||
raise TypeError(
|
||||
"Expected id to be a string, got {}: {!r}".format(type(id_), id_)
|
||||
)
|
||||
id_ = ascii_escaped(id_)
|
||||
return cls(values, marks, id_)
|
||||
|
||||
@classmethod
|
||||
def extract_from(cls, parameterset, belonging_definition, legacy_force_tuple=False):
|
||||
def extract_from(cls, parameterset, force_tuple=False):
|
||||
"""
|
||||
:param parameterset:
|
||||
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
|
||||
|
||||
:param legacy_force_tuple:
|
||||
:param force_tuple:
|
||||
enforce tuple wrapping so single argument tuple values
|
||||
don't get decomposed and break tests
|
||||
|
||||
:param belonging_definition: the item that we will be extracting the parameters from.
|
||||
"""
|
||||
|
||||
if isinstance(parameterset, cls):
|
||||
return parameterset
|
||||
if not isinstance(parameterset, MarkDecorator) and legacy_force_tuple:
|
||||
if force_tuple:
|
||||
return cls.param(parameterset)
|
||||
|
||||
newmarks = []
|
||||
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)
|
||||
else:
|
||||
return cls(parameterset, marks=[], id=None)
|
||||
|
||||
@classmethod
|
||||
def _for_parametrize(cls, argnames, argvalues, func, config, function_definition):
|
||||
|
@ -119,12 +104,7 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
|
|||
else:
|
||||
force_tuple = False
|
||||
parameters = [
|
||||
ParameterSet.extract_from(
|
||||
x,
|
||||
legacy_force_tuple=force_tuple,
|
||||
belonging_definition=function_definition,
|
||||
)
|
||||
for x in argvalues
|
||||
ParameterSet.extract_from(x, force_tuple=force_tuple) for x in argvalues
|
||||
]
|
||||
del argvalues
|
||||
|
||||
|
@ -132,11 +112,21 @@ class ParameterSet(namedtuple("ParameterSet", "values, marks, id")):
|
|||
# check all parameter sets have the correct number of values
|
||||
for param in parameters:
|
||||
if len(param.values) != len(argnames):
|
||||
raise ValueError(
|
||||
'In "parametrize" the number of values ({}) must be '
|
||||
"equal to the number of names ({})".format(
|
||||
param.values, argnames
|
||||
msg = (
|
||||
'{nodeid}: in "parametrize" the number of names ({names_len}):\n'
|
||||
" {names}\n"
|
||||
"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:
|
||||
# empty parameter set (likely computed at runtime): create a single
|
||||
|
@ -240,10 +230,6 @@ class MarkDecorator(object):
|
|||
func = args[0]
|
||||
is_class = inspect.isclass(func)
|
||||
if len(args) == 1 and (istestfunc(func) or is_class):
|
||||
if is_class:
|
||||
store_mark(func, self.mark)
|
||||
else:
|
||||
store_legacy_markinfo(func, self.mark)
|
||||
store_mark(func, self.mark)
|
||||
return func
|
||||
return self.with_args(*args, **kwargs)
|
||||
|
@ -266,7 +252,13 @@ def normalize_mark_list(mark_list):
|
|||
:type mark_list: List[Union[Mark, Markdecorator]]
|
||||
: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):
|
||||
|
@ -279,90 +271,6 @@ def store_mark(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):
|
||||
""" Factory for :class:`MarkDecorator` objects - exposed as
|
||||
a ``pytest.mark`` singleton instance. Example::
|
||||
|
|
|
@ -5,13 +5,11 @@ from __future__ import print_function
|
|||
import os
|
||||
import warnings
|
||||
|
||||
import attr
|
||||
import py
|
||||
import six
|
||||
|
||||
import _pytest._code
|
||||
from _pytest.compat import getfslineno
|
||||
from _pytest.mark.structures import MarkInfo
|
||||
from _pytest.mark.structures import NodeKeywords
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
|
@ -56,22 +54,6 @@ def ischildnode(baseid, nodeid):
|
|||
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):
|
||||
""" base class for Collector and Item the test collection tree.
|
||||
Collector subclasses have children, Items are terminal nodes."""
|
||||
|
@ -119,95 +101,10 @@ class Node(object):
|
|||
""" fspath sensitive hook proxy used to call pytest hooks"""
|
||||
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):
|
||||
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):
|
||||
"""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):
|
||||
def warn(self, warning):
|
||||
"""Issue a warning for this item.
|
||||
|
||||
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.
|
||||
|
||||
: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
|
||||
|
||||
|
@ -307,20 +210,6 @@ class Node(object):
|
|||
"""
|
||||
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):
|
||||
""" Return a set of all extra keywords in self and any parents."""
|
||||
extra_keywords = set()
|
||||
|
|
|
@ -23,20 +23,15 @@ def get_skip_exceptions():
|
|||
def pytest_runtest_makereport(item, call):
|
||||
if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
|
||||
# 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
|
||||
|
||||
|
||||
@hookimpl(trylast=True)
|
||||
def pytest_runtest_setup(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"):
|
||||
# call module level setup if there is no object level one
|
||||
call_optional(item.parent.obj, "setup")
|
||||
|
@ -53,11 +48,6 @@ def teardown_nose(item):
|
|||
# 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):
|
||||
# extra check needed since we do not do nose style setup/teardown
|
||||
# on direct unittest style classes
|
||||
|
|
|
@ -49,13 +49,13 @@ class Failed(OutcomeException):
|
|||
__module__ = "builtins"
|
||||
|
||||
|
||||
class Exit(KeyboardInterrupt):
|
||||
class Exit(SystemExit):
|
||||
""" raised for immediate program exits (no tracebacks/summaries)"""
|
||||
|
||||
def __init__(self, msg="unknown reason", returncode=None):
|
||||
self.msg = msg
|
||||
self.returncode = returncode
|
||||
KeyboardInterrupt.__init__(self, msg)
|
||||
SystemExit.__init__(self, msg)
|
||||
|
||||
|
||||
# exposed helper methods
|
||||
|
@ -63,7 +63,7 @@ class Exit(KeyboardInterrupt):
|
|||
|
||||
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 int returncode: return code to be used when exiting pytest.
|
||||
|
@ -137,10 +137,15 @@ def xfail(reason=""):
|
|||
xfail.Exception = XFailed
|
||||
|
||||
|
||||
def importorskip(modname, minversion=None):
|
||||
""" return imported module if it has at least "minversion" as its
|
||||
__version__ attribute. If no minversion is specified the a skip
|
||||
is only triggered if the module can not be imported.
|
||||
def importorskip(modname, minversion=None, reason=None):
|
||||
"""Imports and returns the requested module ``modname``, or skip the current test
|
||||
if the module cannot 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
|
||||
|
||||
|
@ -159,7 +164,9 @@ def importorskip(modname, minversion=None):
|
|||
# Do not raise chained exception here(#1485)
|
||||
should_skip = True
|
||||
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]
|
||||
if minversion is None:
|
||||
return mod
|
||||
|
|
|
@ -38,13 +38,12 @@ from _pytest.compat import safe_str
|
|||
from _pytest.compat import STRING_TYPES
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.main import FSHookProxy
|
||||
from _pytest.mark import MARK_GEN
|
||||
from _pytest.mark.structures import get_unpacked_marks
|
||||
from _pytest.mark.structures import normalize_mark_list
|
||||
from _pytest.mark.structures import transfer_markers
|
||||
from _pytest.outcomes import fail
|
||||
from _pytest.pathlib import parts
|
||||
from _pytest.warning_types import PytestWarning
|
||||
from _pytest.warning_types import RemovedInPytest4Warning
|
||||
|
||||
|
||||
def pyobj_property(name):
|
||||
|
@ -125,10 +124,10 @@ def pytest_generate_tests(metafunc):
|
|||
# those alternative spellings are common - raise a specific error to alert
|
||||
# the user
|
||||
alt_spellings = ["parameterize", "parametrise", "parameterise"]
|
||||
for attr in alt_spellings:
|
||||
if hasattr(metafunc.function, attr):
|
||||
for mark_name in alt_spellings:
|
||||
if metafunc.definition.get_closest_marker(mark_name):
|
||||
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"):
|
||||
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
|
||||
if safe_isclass(obj):
|
||||
if collector.istestclass(obj, name):
|
||||
Class = collector._getcustomclass("Class")
|
||||
outcome.force_result(Class(name, parent=collector))
|
||||
elif collector.istestfunction(obj, name):
|
||||
# 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):
|
||||
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:
|
||||
res = list(collector._genfunctions(name, obj))
|
||||
outcome.force_result(res)
|
||||
|
@ -375,10 +376,6 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
|||
values.sort(key=lambda item: item.reportinfo()[:2])
|
||||
return values
|
||||
|
||||
def makeitem(self, name, obj):
|
||||
warnings.warn(deprecated.COLLECTOR_MAKEITEM, stacklevel=2)
|
||||
self._makeitem(name, obj)
|
||||
|
||||
def _makeitem(self, name, obj):
|
||||
# assert self.ihook.fspath == self.fspath, self
|
||||
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
|
||||
clscol = self.getparent(Class)
|
||||
cls = clscol and clscol.obj or None
|
||||
transfer_markers(funcobj, cls, module)
|
||||
fm = self.session._fixturemanager
|
||||
|
||||
definition = FunctionDefinition(name=name, parent=self, callobj=funcobj)
|
||||
|
@ -408,7 +404,6 @@ class PyCollector(PyobjMixin, nodes.Collector):
|
|||
else:
|
||||
self.ihook.pytest_generate_tests(metafunc=metafunc)
|
||||
|
||||
Function = self._getcustomclass("Function")
|
||||
if not metafunc._calls:
|
||||
yield Function(name, parent=self, fixtureinfo=fixtureinfo)
|
||||
else:
|
||||
|
@ -450,7 +445,7 @@ class Module(nodes.File, PyCollector):
|
|||
mod = self.fspath.pyimport(ensuresyspath=importmode)
|
||||
except SyntaxError:
|
||||
raise self.CollectError(
|
||||
_pytest._code.ExceptionInfo().getrepr(style="short")
|
||||
_pytest._code.ExceptionInfo.from_current().getrepr(style="short")
|
||||
)
|
||||
except self.fspath.ImportMismatchError:
|
||||
e = sys.exc_info()[1]
|
||||
|
@ -466,7 +461,7 @@ class Module(nodes.File, PyCollector):
|
|||
except ImportError:
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
|
||||
exc_info = ExceptionInfo()
|
||||
exc_info = ExceptionInfo.from_current()
|
||||
if self.config.getoption("verbose") < 2:
|
||||
exc_info.traceback = exc_info.traceback.filter(filter_traceback)
|
||||
exc_repr = (
|
||||
|
@ -648,7 +643,7 @@ class Class(PyCollector):
|
|||
)
|
||||
)
|
||||
return []
|
||||
return [self._getcustomclass("Instance")(name="()", parent=self)]
|
||||
return [Instance(name="()", parent=self)]
|
||||
|
||||
def setup(self):
|
||||
setup_class = _get_xunit_func(self.obj, "setup_class")
|
||||
|
@ -739,51 +734,6 @@ class FunctionMixin(PyobjMixin):
|
|||
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):
|
||||
init = getattr(obj, "__init__", None)
|
||||
if init:
|
||||
|
@ -1065,48 +1015,6 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
|
|||
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):
|
||||
"""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)
|
||||
except Exception as e:
|
||||
# See issue https://github.com/pytest-dev/pytest/issues/2169
|
||||
msg = (
|
||||
"While trying to determine id of parameter {} at position "
|
||||
"{} the following exception was raised:\n".format(argname, idx)
|
||||
)
|
||||
msg = "{}: error raised while trying to determine id of parameter '{}' at position {}\n"
|
||||
msg = msg.format(item.nodeid, 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 += "This warning will be an error error in pytest-4.0."
|
||||
item.warn(RemovedInPytest4Warning(msg))
|
||||
six.raise_from(ValueError(msg), e)
|
||||
if s:
|
||||
return ascii_escaped(s)
|
||||
|
||||
|
@ -1326,8 +1232,7 @@ def _showfixtures_main(config, session):
|
|||
tw.line(" %s: no docstring available" % (loc,), red=True)
|
||||
|
||||
|
||||
def write_docstring(tw, doc):
|
||||
INDENT = " "
|
||||
def write_docstring(tw, doc, indent=" "):
|
||||
doc = doc.rstrip()
|
||||
if "\n" in doc:
|
||||
firstline, rest = doc.split("\n", 1)
|
||||
|
@ -1335,11 +1240,11 @@ def write_docstring(tw, doc):
|
|||
firstline, rest = doc, ""
|
||||
|
||||
if firstline.strip():
|
||||
tw.line(INDENT + firstline.strip())
|
||||
tw.line(indent + firstline.strip())
|
||||
|
||||
if rest:
|
||||
for line in dedent(rest).split("\n"):
|
||||
tw.write(INDENT + line + "\n")
|
||||
tw.write(indent + line + "\n")
|
||||
|
||||
|
||||
class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
|
||||
|
@ -1384,6 +1289,20 @@ class Function(FunctionMixin, nodes.Item, fixtures.FuncargnamesCompatAttr):
|
|||
if 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:
|
||||
fixtureinfo = self.session._fixturemanager.getfixtureinfo(
|
||||
self, self.obj, self.cls, funcargs=not self._isyieldedfunction()
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import math
|
||||
import pprint
|
||||
import sys
|
||||
import warnings
|
||||
from decimal import Decimal
|
||||
from numbers import Number
|
||||
|
||||
|
@ -10,9 +13,11 @@ from six.moves import filterfalse
|
|||
from six.moves import zip
|
||||
|
||||
import _pytest._code
|
||||
from _pytest import deprecated
|
||||
from _pytest.compat import isclass
|
||||
from _pytest.compat import Iterable
|
||||
from _pytest.compat import Mapping
|
||||
from _pytest.compat import Sequence
|
||||
from _pytest.compat import Sized
|
||||
from _pytest.compat import STRING_TYPES
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
|
@ -182,7 +187,7 @@ class ApproxMapping(ApproxBase):
|
|||
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
|
||||
numbers.
|
||||
|
@ -518,10 +523,14 @@ def approx(expected, rel=None, abs=None, nan_ok=False):
|
|||
cls = ApproxScalar
|
||||
elif isinstance(expected, Mapping):
|
||||
cls = ApproxMapping
|
||||
elif isinstance(expected, Sequence) and not isinstance(expected, STRING_TYPES):
|
||||
cls = ApproxSequence
|
||||
elif _is_numpy_array(expected):
|
||||
cls = ApproxNumpy
|
||||
elif (
|
||||
isinstance(expected, Iterable)
|
||||
and isinstance(expected, Sized)
|
||||
and not isinstance(expected, STRING_TYPES)
|
||||
):
|
||||
cls = ApproxSequencelike
|
||||
else:
|
||||
raise _non_numeric_type_error(expected, at=None)
|
||||
|
||||
|
@ -547,29 +556,47 @@ def _is_numpy_array(obj):
|
|||
def raises(expected_exception, *args, **kwargs):
|
||||
r"""
|
||||
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
|
||||
exception is not raised
|
||||
:arg match: if specified, asserts that the exception matches a text or regex
|
||||
:kwparam 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):
|
||||
... 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.
|
||||
|
||||
You can also 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")
|
||||
|
||||
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::
|
||||
|
||||
>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
|
||||
... pass
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
Failed: Expecting ZeroDivisionError
|
||||
``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::
|
||||
|
||||
|
@ -583,7 +610,7 @@ def raises(expected_exception, *args, **kwargs):
|
|||
>>> with raises(ValueError) as exc_info:
|
||||
... if value > 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
|
||||
scope)::
|
||||
|
@ -592,22 +619,9 @@ def raises(expected_exception, *args, **kwargs):
|
|||
... if value > 10:
|
||||
... raise ValueError("value must be <= 10")
|
||||
...
|
||||
>>> assert exc_info.type == ValueError
|
||||
>>> assert exc_info.type is ValueError
|
||||
|
||||
|
||||
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.
|
||||
**Legacy form**
|
||||
|
||||
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)
|
||||
<ExceptionInfo ...>
|
||||
|
||||
It is also possible to pass a string to be evaluated at runtime::
|
||||
|
||||
>>> 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`.
|
||||
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.
|
||||
|
||||
.. note::
|
||||
Similar to caught exception objects in Python, explicitly clearing
|
||||
|
@ -664,6 +669,7 @@ def raises(expected_exception, *args, **kwargs):
|
|||
if not args:
|
||||
if "message" in kwargs:
|
||||
message = kwargs.pop("message")
|
||||
warnings.warn(deprecated.RAISES_MESSAGE_PARAMETER, stacklevel=2)
|
||||
if "match" in kwargs:
|
||||
match_expr = kwargs.pop("match")
|
||||
if kwargs:
|
||||
|
@ -672,6 +678,7 @@ def raises(expected_exception, *args, **kwargs):
|
|||
raise TypeError(msg)
|
||||
return RaisesContext(expected_exception, message, match_expr)
|
||||
elif isinstance(args[0], str):
|
||||
warnings.warn(deprecated.RAISES_EXEC, stacklevel=2)
|
||||
code, = args
|
||||
assert isinstance(code, str)
|
||||
frame = sys._getframe(1)
|
||||
|
@ -684,13 +691,13 @@ def raises(expected_exception, *args, **kwargs):
|
|||
# XXX didn't mean f_globals == f_locals something special?
|
||||
# this is destroyed here ...
|
||||
except expected_exception:
|
||||
return _pytest._code.ExceptionInfo()
|
||||
return _pytest._code.ExceptionInfo.from_current()
|
||||
else:
|
||||
func = args[0]
|
||||
try:
|
||||
func(*args[1:], **kwargs)
|
||||
except expected_exception:
|
||||
return _pytest._code.ExceptionInfo()
|
||||
return _pytest._code.ExceptionInfo.from_current()
|
||||
fail(message)
|
||||
|
||||
|
||||
|
@ -705,7 +712,7 @@ class RaisesContext(object):
|
|||
self.excinfo = None
|
||||
|
||||
def __enter__(self):
|
||||
self.excinfo = object.__new__(_pytest._code.ExceptionInfo)
|
||||
self.excinfo = _pytest._code.ExceptionInfo.for_later()
|
||||
return self.excinfo
|
||||
|
||||
def __exit__(self, *tp):
|
||||
|
|
|
@ -11,6 +11,7 @@ import warnings
|
|||
import six
|
||||
|
||||
import _pytest._code
|
||||
from _pytest.deprecated import WARNS_EXEC
|
||||
from _pytest.fixtures import yield_fixture
|
||||
from _pytest.outcomes import fail
|
||||
|
||||
|
@ -89,6 +90,7 @@ def warns(expected_warning, *args, **kwargs):
|
|||
match_expr = kwargs.pop("match")
|
||||
return WarningsChecker(expected_warning, match_expr=match_expr)
|
||||
elif isinstance(args[0], str):
|
||||
warnings.warn(WARNS_EXEC, stacklevel=2)
|
||||
code, = args
|
||||
assert isinstance(code, str)
|
||||
frame = sys._getframe(1)
|
||||
|
|
|
@ -34,9 +34,9 @@ def pytest_configure(config):
|
|||
config.pluginmanager.register(config._resultlog)
|
||||
|
||||
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):
|
||||
|
|
|
@ -8,12 +8,14 @@ import os
|
|||
import sys
|
||||
from time import time
|
||||
|
||||
import attr
|
||||
import six
|
||||
|
||||
from .reports import CollectErrorRepr
|
||||
from .reports import CollectReport
|
||||
from .reports import TestReport
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest.outcomes import Exit
|
||||
from _pytest.outcomes import skip
|
||||
from _pytest.outcomes import Skipped
|
||||
from _pytest.outcomes import TEST_OUTCOME
|
||||
|
@ -189,43 +191,58 @@ def check_interactive_exception(call, report):
|
|||
def call_runtest_hook(item, when, **kwds):
|
||||
hookname = "pytest_runtest_" + when
|
||||
ihook = getattr(item.ihook, hookname)
|
||||
return CallInfo(
|
||||
lambda: ihook(item=item, **kwds),
|
||||
when=when,
|
||||
treat_keyboard_interrupt_as_exception=item.config.getvalue("usepdb"),
|
||||
reraise = (Exit,)
|
||||
if not item.config.getvalue("usepdb"):
|
||||
reraise += (KeyboardInterrupt,)
|
||||
return CallInfo.from_call(
|
||||
lambda: ihook(item=item, **kwds), when=when, reraise=reraise
|
||||
)
|
||||
|
||||
|
||||
@attr.s(repr=False)
|
||||
class CallInfo(object):
|
||||
""" Result/Exception info a function invocation. """
|
||||
|
||||
#: None or ExceptionInfo object.
|
||||
excinfo = None
|
||||
_result = attr.ib()
|
||||
# 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",
|
||||
#: "teardown", "memocollect"
|
||||
self.when = when
|
||||
self.start = time()
|
||||
start = time()
|
||||
excinfo = None
|
||||
try:
|
||||
self.result = func()
|
||||
except KeyboardInterrupt:
|
||||
if treat_keyboard_interrupt_as_exception:
|
||||
self.excinfo = ExceptionInfo()
|
||||
else:
|
||||
self.stop = time()
|
||||
raise
|
||||
result = func()
|
||||
except: # noqa
|
||||
self.excinfo = ExceptionInfo()
|
||||
self.stop = time()
|
||||
excinfo = ExceptionInfo.from_current()
|
||||
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):
|
||||
if self.excinfo:
|
||||
status = "exception: %s" % str(self.excinfo.value)
|
||||
if self.excinfo is not None:
|
||||
status = "exception"
|
||||
value = self.excinfo.value
|
||||
else:
|
||||
result = getattr(self, "result", "<NOTSET>")
|
||||
status = "result: %r" % (result,)
|
||||
return "<CallInfo when=%r %s>" % (self.when, status)
|
||||
# TODO: investigate unification
|
||||
value = repr(self._result)
|
||||
status = "result"
|
||||
return "<CallInfo when={when!r} {status}: {value}>".format(
|
||||
when=self.when, value=value, status=status
|
||||
)
|
||||
|
||||
|
||||
def pytest_runtest_makereport(item, call):
|
||||
|
@ -269,7 +286,7 @@ def pytest_runtest_makereport(item, call):
|
|||
|
||||
|
||||
def pytest_make_collect_report(collector):
|
||||
call = CallInfo(lambda: list(collector.collect()), "collect")
|
||||
call = CallInfo.from_call(lambda: list(collector.collect()), "collect")
|
||||
longrepr = None
|
||||
if not call.excinfo:
|
||||
outcome = "passed"
|
||||
|
|
|
@ -167,7 +167,7 @@ def getreportopt(config):
|
|||
if char not in reportopts and char != "a":
|
||||
reportopts += char
|
||||
elif char == "a":
|
||||
reportopts = "fEsxXw"
|
||||
reportopts = "sxXwEf"
|
||||
return reportopts
|
||||
|
||||
|
||||
|
@ -186,20 +186,17 @@ def pytest_report_teststatus(report):
|
|||
@attr.s
|
||||
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|None nodeid: node id that generated the warning (see ``get_location``).
|
||||
:ivar tuple|py.path.local fslocation:
|
||||
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()
|
||||
nodeid = attr.ib(default=None)
|
||||
fslocation = attr.ib(default=None)
|
||||
legacy = attr.ib(default=False)
|
||||
|
||||
def get_location(self, config):
|
||||
"""
|
||||
|
@ -329,13 +326,6 @@ class TerminalReporter(object):
|
|||
self.write_line("INTERNALERROR> " + line)
|
||||
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):
|
||||
# from _pytest.nodes import get_fslocation_from_item
|
||||
from _pytest.warnings import warning_record_to_str
|
||||
|
@ -621,6 +611,10 @@ class TerminalReporter(object):
|
|||
continue
|
||||
indent = (len(stack) - 1) * " "
|
||||
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)
|
||||
def pytest_sessionfinish(self, exitstatus):
|
||||
|
|
|
@ -14,8 +14,6 @@ from _pytest.outcomes import skip
|
|||
from _pytest.outcomes import xfail
|
||||
from _pytest.python import Class
|
||||
from _pytest.python import Function
|
||||
from _pytest.python import Module
|
||||
from _pytest.python import transfer_markers
|
||||
|
||||
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
|
@ -54,14 +52,12 @@ class UnitTestCase(Class):
|
|||
return
|
||||
self.session._fixturemanager.parsefactories(self, unittest=True)
|
||||
loader = TestLoader()
|
||||
module = self.getparent(Module).obj
|
||||
foundsomething = False
|
||||
for name in loader.getTestCaseNames(self.obj):
|
||||
x = getattr(self.obj, name)
|
||||
if not getattr(x, "__test__", True):
|
||||
continue
|
||||
funcobj = getimfunc(x)
|
||||
transfer_markers(funcobj, cls, module)
|
||||
yield TestCaseFunction(name, parent=self, callobj=funcobj)
|
||||
foundsomething = True
|
||||
|
||||
|
@ -115,6 +111,10 @@ class TestCaseFunction(Function):
|
|||
rawexcinfo = getattr(rawexcinfo, "_rawexcinfo", rawexcinfo)
|
||||
try:
|
||||
excinfo = _pytest._code.ExceptionInfo(rawexcinfo)
|
||||
# invoke the attributes to trigger storing the traceback
|
||||
# trial causes some issue there
|
||||
excinfo.value
|
||||
excinfo.traceback
|
||||
except TypeError:
|
||||
try:
|
||||
try:
|
||||
|
@ -136,7 +136,7 @@ class TestCaseFunction(Function):
|
|||
except KeyboardInterrupt:
|
||||
raise
|
||||
except fail.Exception:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
self.__dict__.setdefault("_excinfo", []).append(excinfo)
|
||||
|
||||
def addError(self, testcase, rawexcinfo):
|
||||
|
|
|
@ -160,19 +160,19 @@ def pytest_terminal_summary(terminalreporter):
|
|||
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:
|
||||
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.
|
||||
|
||||
:param warning: the warning instance.
|
||||
:param config:
|
||||
:param hook: the hook caller
|
||||
:param stacklevel: stacklevel forwarded to warnings.warn
|
||||
"""
|
||||
with warnings.catch_warnings(record=True) as records:
|
||||
warnings.simplefilter("always", type(warning))
|
||||
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)
|
||||
)
|
||||
|
|
|
@ -28,7 +28,6 @@ from _pytest.outcomes import skip
|
|||
from _pytest.outcomes import xfail
|
||||
from _pytest.python import Class
|
||||
from _pytest.python import Function
|
||||
from _pytest.python import Generator
|
||||
from _pytest.python import Instance
|
||||
from _pytest.python import Module
|
||||
from _pytest.python import Package
|
||||
|
@ -57,7 +56,6 @@ __all__ = [
|
|||
"fixture",
|
||||
"freeze_includes",
|
||||
"Function",
|
||||
"Generator",
|
||||
"hookimpl",
|
||||
"hookspec",
|
||||
"importorskip",
|
||||
|
|
|
@ -146,6 +146,7 @@ class TestGeneralUsage(object):
|
|||
assert result.ret
|
||||
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):
|
||||
"""Show a user-friendly traceback on conftest import failures (#486, #3332)"""
|
||||
testdir.makepyfile("")
|
||||
|
@ -299,7 +300,7 @@ class TestGeneralUsage(object):
|
|||
"""
|
||||
import pytest
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall({'x': 3}, id='hello-123')
|
||||
metafunc.parametrize('x', [3], ids=['hello-123'])
|
||||
def pytest_runtest_setup(item):
|
||||
print(item.keywords)
|
||||
if 'hello-123' in item.keywords:
|
||||
|
@ -316,8 +317,7 @@ class TestGeneralUsage(object):
|
|||
p = testdir.makepyfile(
|
||||
"""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall({'i': 1}, id="1")
|
||||
metafunc.addcall({'i': 2}, id="2")
|
||||
metafunc.parametrize('i', [1, 2], ids=["1", "2"])
|
||||
def test_func(i):
|
||||
pass
|
||||
"""
|
||||
|
@ -560,12 +560,11 @@ class TestInvocationVariants(object):
|
|||
def test_equivalence_pytest_pytest(self):
|
||||
assert pytest.main == py.test.cmdline.main
|
||||
|
||||
def test_invoke_with_string(self, capsys):
|
||||
retcode = pytest.main("-h")
|
||||
assert not retcode
|
||||
out, err = capsys.readouterr()
|
||||
assert "--help" in out
|
||||
pytest.raises(ValueError, lambda: pytest.main(0))
|
||||
def test_invoke_with_invalid_type(self, capsys):
|
||||
with pytest.raises(
|
||||
TypeError, match="expected to be a list or tuple of strings, got: '-h'"
|
||||
):
|
||||
pytest.main("-h")
|
||||
|
||||
def test_invoke_with_path(self, tmpdir, capsys):
|
||||
retcode = pytest.main(tmpdir)
|
||||
|
|
|
@ -37,7 +37,7 @@ def test_code_with_class():
|
|||
class A(object):
|
||||
pass
|
||||
|
||||
pytest.raises(TypeError, "_pytest._code.Code(A)")
|
||||
pytest.raises(TypeError, _pytest._code.Code, A)
|
||||
|
||||
|
||||
def x():
|
||||
|
@ -169,7 +169,7 @@ class TestExceptionInfo(object):
|
|||
else:
|
||||
assert False
|
||||
except AssertionError:
|
||||
exci = _pytest._code.ExceptionInfo()
|
||||
exci = _pytest._code.ExceptionInfo.from_current()
|
||||
assert exci.getrepr()
|
||||
|
||||
|
||||
|
@ -181,7 +181,7 @@ class TestTracebackEntry(object):
|
|||
else:
|
||||
assert False
|
||||
except AssertionError:
|
||||
exci = _pytest._code.ExceptionInfo()
|
||||
exci = _pytest._code.ExceptionInfo.from_current()
|
||||
entry = exci.traceback[0]
|
||||
source = entry.getsource()
|
||||
assert len(source) == 6
|
||||
|
|
|
@ -71,7 +71,7 @@ def test_excinfo_simple():
|
|||
try:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
info = _pytest._code.ExceptionInfo()
|
||||
info = _pytest._code.ExceptionInfo.from_current()
|
||||
assert info.type == ValueError
|
||||
|
||||
|
||||
|
@ -85,7 +85,7 @@ def test_excinfo_getstatement():
|
|||
try:
|
||||
f()
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
linenumbers = [
|
||||
_pytest._code.getrawcode(f).co_firstlineno - 1 + 4,
|
||||
_pytest._code.getrawcode(f).co_firstlineno - 1 + 1,
|
||||
|
@ -126,7 +126,7 @@ class TestTraceback_f_g_h(object):
|
|||
try:
|
||||
h()
|
||||
except ValueError:
|
||||
self.excinfo = _pytest._code.ExceptionInfo()
|
||||
self.excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
|
||||
def test_traceback_entries(self):
|
||||
tb = self.excinfo.traceback
|
||||
|
@ -163,7 +163,7 @@ class TestTraceback_f_g_h(object):
|
|||
try:
|
||||
exec(source.compile())
|
||||
except NameError:
|
||||
tb = _pytest._code.ExceptionInfo().traceback
|
||||
tb = _pytest._code.ExceptionInfo.from_current().traceback
|
||||
print(tb[-1].getsource())
|
||||
s = str(tb[-1].getsource())
|
||||
assert s.startswith("def xyz():\n try:")
|
||||
|
@ -180,7 +180,8 @@ class TestTraceback_f_g_h(object):
|
|||
|
||||
def test_traceback_cut_excludepath(self, testdir):
|
||||
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()
|
||||
newtraceback = excinfo.traceback.cut(excludepath=basedir)
|
||||
for x in newtraceback:
|
||||
|
@ -336,7 +337,8 @@ class TestTraceback_f_g_h(object):
|
|||
def test_excinfo_exconly():
|
||||
excinfo = pytest.raises(ValueError, h)
|
||||
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)
|
||||
assert msg.startswith("ValueError")
|
||||
assert msg.endswith("world")
|
||||
|
@ -356,6 +358,12 @@ def test_excinfo_str():
|
|||
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():
|
||||
excinfo = pytest.raises(ValueError, h)
|
||||
assert excinfo.errisinstance(ValueError)
|
||||
|
@ -365,7 +373,7 @@ def test_excinfo_no_sourcecode():
|
|||
try:
|
||||
exec("raise ValueError()")
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
s = str(excinfo.traceback[-1])
|
||||
assert s == " File '<string>':1 in <module>\n ???\n"
|
||||
|
||||
|
@ -390,7 +398,7 @@ def test_entrysource_Queue_example():
|
|||
try:
|
||||
queue.Queue().get(timeout=0.001)
|
||||
except queue.Empty:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
entry = excinfo.traceback[-1]
|
||||
source = entry.getsource()
|
||||
assert source is not None
|
||||
|
@ -402,7 +410,7 @@ def test_codepath_Queue_example():
|
|||
try:
|
||||
queue.Queue().get(timeout=0.001)
|
||||
except queue.Empty:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
entry = excinfo.traceback[-1]
|
||||
path = entry.path
|
||||
assert isinstance(path, py.path.local)
|
||||
|
@ -453,7 +461,7 @@ class TestFormattedExcinfo(object):
|
|||
except KeyboardInterrupt:
|
||||
raise
|
||||
except: # noqa
|
||||
return _pytest._code.ExceptionInfo()
|
||||
return _pytest._code.ExceptionInfo.from_current()
|
||||
assert 0, "did not raise"
|
||||
|
||||
def test_repr_source(self):
|
||||
|
@ -491,7 +499,7 @@ class TestFormattedExcinfo(object):
|
|||
try:
|
||||
exec(co)
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
repr = pr.repr_excinfo(excinfo)
|
||||
assert repr.reprtraceback.reprentries[1].lines[0] == "> ???"
|
||||
if sys.version_info[0] >= 3:
|
||||
|
@ -510,7 +518,7 @@ raise ValueError()
|
|||
try:
|
||||
exec(co)
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
repr = pr.repr_excinfo(excinfo)
|
||||
assert repr.reprtraceback.reprentries[1].lines[0] == "> ???"
|
||||
if sys.version_info[0] >= 3:
|
||||
|
@ -1340,7 +1348,7 @@ def test_repr_traceback_with_unicode(style, encoding):
|
|||
try:
|
||||
raise RuntimeError(msg)
|
||||
except RuntimeError:
|
||||
e_info = ExceptionInfo()
|
||||
e_info = ExceptionInfo.from_current()
|
||||
formatter = FormattedExcinfo(style=style)
|
||||
repr_traceback = formatter.repr_traceback(e_info)
|
||||
assert repr_traceback is not None
|
||||
|
|
|
@ -6,6 +6,7 @@ from __future__ import absolute_import
|
|||
from __future__ import division
|
||||
from __future__ import print_function
|
||||
|
||||
import ast
|
||||
import inspect
|
||||
import sys
|
||||
|
||||
|
@ -14,7 +15,6 @@ import six
|
|||
import _pytest._code
|
||||
import pytest
|
||||
from _pytest._code import Source
|
||||
from _pytest._code.source import ast
|
||||
|
||||
|
||||
astonly = pytest.mark.nothing
|
||||
|
@ -306,8 +306,6 @@ class TestSourceParsingAndCompiling(object):
|
|||
pytest.raises(SyntaxError, lambda: source.getstatementrange(0))
|
||||
|
||||
def test_compile_to_ast(self):
|
||||
import ast
|
||||
|
||||
source = Source("x = 4")
|
||||
mod = source.compile(flag=ast.PyCF_ONLY_AST)
|
||||
assert isinstance(mod, ast.Module)
|
||||
|
@ -317,10 +315,9 @@ class TestSourceParsingAndCompiling(object):
|
|||
co = self.source.compile()
|
||||
six.exec_(co, globals())
|
||||
f(7)
|
||||
excinfo = pytest.raises(AssertionError, "f(6)")
|
||||
excinfo = pytest.raises(AssertionError, f, 6)
|
||||
frame = excinfo.traceback[-1].frame
|
||||
stmt = frame.code.fullsource.getstatement(frame.lineno)
|
||||
# print "block", str(block)
|
||||
assert str(stmt).strip().startswith("assert")
|
||||
|
||||
@pytest.mark.parametrize("name", ["", None, "my"])
|
||||
|
@ -361,17 +358,13 @@ def test_getline_finally():
|
|||
def c():
|
||||
pass
|
||||
|
||||
excinfo = pytest.raises(
|
||||
TypeError,
|
||||
"""
|
||||
with pytest.raises(TypeError) as excinfo:
|
||||
teardown = None
|
||||
try:
|
||||
c(1)
|
||||
finally:
|
||||
if teardown:
|
||||
teardown()
|
||||
""",
|
||||
)
|
||||
source = excinfo.traceback[-1].statement
|
||||
assert str(source).strip() == "c(1)"
|
||||
|
||||
|
|
|
@ -10,122 +10,7 @@ from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
|
|||
pytestmark = pytest.mark.pytester_example_path("deprecated")
|
||||
|
||||
|
||||
def test_yield_tests_deprecation(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):
|
||||
def test_pytest_setup_cfg_unsupported(testdir):
|
||||
testdir.makefile(
|
||||
".cfg",
|
||||
setup="""
|
||||
|
@ -133,14 +18,11 @@ def test_pytest_setup_cfg_deprecated(testdir):
|
|||
addopts = --verbose
|
||||
""",
|
||||
)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
["*pytest*section in setup.cfg files is deprecated*use*tool:pytest*instead*"]
|
||||
)
|
||||
with pytest.raises(pytest.fail.Exception):
|
||||
testdir.runpytest()
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("default")
|
||||
def test_pytest_custom_cfg_deprecated(testdir):
|
||||
def test_pytest_custom_cfg_unsupported(testdir):
|
||||
testdir.makefile(
|
||||
".cfg",
|
||||
custom="""
|
||||
|
@ -148,29 +30,8 @@ def test_pytest_custom_cfg_deprecated(testdir):
|
|||
addopts = --verbose
|
||||
""",
|
||||
)
|
||||
result = testdir.runpytest("-c", "custom.cfg")
|
||||
result.stdout.fnmatch_lines(
|
||||
["*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
|
||||
with pytest.raises(pytest.fail.Exception):
|
||||
testdir.runpytest("-c", "custom.cfg")
|
||||
|
||||
|
||||
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.stdout.fnmatch_lines(
|
||||
[
|
||||
"*--result-log is deprecated and scheduled for removal in pytest 4.0*",
|
||||
"*See https://docs.pytest.org/en/latest/usage.html#creating-resultlog-format-files for more information*",
|
||||
"*--result-log is deprecated and scheduled for removal in pytest 5.0*",
|
||||
"*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):
|
||||
"""Check that TerminalReporter._tw is also available as 'writer' (#2984)
|
||||
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.filterwarnings("default")
|
||||
def test_pytest_catchlog_deprecated(testdir, plugin):
|
||||
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):
|
||||
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)
|
||||
assert res.ret == 0
|
||||
assert res.ret == 2
|
||||
msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0]
|
||||
res.stdout.fnmatch_lines(
|
||||
"*subdirectory{sep}conftest.py:0: RemovedInPytest4Warning: {msg}*".format(
|
||||
sep=os.sep, msg=msg
|
||||
)
|
||||
["*{msg}*".format(msg=msg), "*subdirectory{sep}conftest.py*".format(sep=os.sep)]
|
||||
)
|
||||
|
||||
|
||||
@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
|
||||
):
|
||||
"""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 += (SHOW_PYTEST_WARNINGS_ARG,)
|
||||
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]
|
||||
if use_pyargs:
|
||||
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))
|
||||
|
||||
|
||||
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
|
||||
):
|
||||
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()
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
import warnings
|
||||
warnings.filterwarnings('always', category=DeprecationWarning)
|
||||
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()
|
||||
assert res.ret == 0
|
||||
assert res.ret == 2
|
||||
msg = str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0]
|
||||
res.stdout.fnmatch_lines(
|
||||
"*subdirectory{sep}conftest.py:0: RemovedInPytest4Warning: {msg}*".format(
|
||||
sep=os.sep, msg=msg
|
||||
)
|
||||
["*{msg}*".format(msg=msg), "*subdirectory{sep}conftest.py*".format(sep=os.sep)]
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
):
|
||||
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()
|
||||
|
||||
|
||||
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):
|
||||
testdir.copy_example()
|
||||
result = testdir.runpytest()
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -3,4 +3,4 @@ import pytest
|
|||
|
||||
@pytest.fixture
|
||||
def arg2(request):
|
||||
pytest.raises(Exception, "request.getfixturevalue('arg1')")
|
||||
pytest.raises(Exception, request.getfixturevalue, "arg1")
|
||||
|
|
|
@ -496,3 +496,14 @@ class TestApprox(object):
|
|||
assert actual != approx(expected, rel=5e-8, abs=0)
|
||||
assert approx(expected, rel=5e-7, 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)
|
||||
|
|
|
@ -7,7 +7,6 @@ import _pytest._code
|
|||
import pytest
|
||||
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
||||
from _pytest.nodes import Collector
|
||||
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
|
||||
|
||||
|
||||
class TestModule(object):
|
||||
|
@ -244,225 +243,7 @@ class TestClass(object):
|
|||
@pytest.mark.filterwarnings(
|
||||
"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):
|
||||
@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):
|
||||
item = testdir.getitem("def test_func(): pass")
|
||||
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
|
||||
|
||||
config = testdir.parseconfigure()
|
||||
session = testdir.Session(config)
|
||||
session._fixturemanager = FixtureManager(session)
|
||||
|
||||
return pytest.Function(config=config, parent=session, **kwargs)
|
||||
|
||||
def test_function_equality(self, testdir, tmpdir):
|
||||
def func1():
|
||||
pass
|
||||
|
||||
def func2():
|
||||
pass
|
||||
|
||||
f1 = pytest.Function(
|
||||
name="name", parent=session, config=config, args=(1,), callobj=func1
|
||||
)
|
||||
f1 = self.make_function(testdir, name="name", args=(1,), callobj=func1)
|
||||
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
|
||||
|
||||
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):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
|
@ -676,7 +465,6 @@ class TestFunction(object):
|
|||
rec = testdir.inline_run()
|
||||
rec.assertoutcome(passed=1)
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:Applying marks directly to parameters")
|
||||
def test_parametrize_with_mark(self, testdir):
|
||||
items = testdir.getitems(
|
||||
"""
|
||||
|
@ -684,7 +472,7 @@ class TestFunction(object):
|
|||
@pytest.mark.foo
|
||||
@pytest.mark.parametrize('arg', [
|
||||
1,
|
||||
pytest.mark.bar(pytest.mark.baz(2))
|
||||
pytest.param(2, marks=[pytest.mark.baz, pytest.mark.bar])
|
||||
])
|
||||
def test_function(arg):
|
||||
pass
|
||||
|
@ -762,37 +550,37 @@ class TestFunction(object):
|
|||
assert colitems[2].name == "test2[a-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(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
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):
|
||||
assert x < 2
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest(*ignore_parametrized_marks_args)
|
||||
result = testdir.runpytest()
|
||||
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(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
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):
|
||||
assert x < 2
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest(*ignore_parametrized_marks_args)
|
||||
result = testdir.runpytest()
|
||||
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(
|
||||
"""
|
||||
import pytest
|
||||
|
@ -804,40 +592,40 @@ class TestFunction(object):
|
|||
assert x < 2
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest(*ignore_parametrized_marks_args)
|
||||
result = testdir.runpytest()
|
||||
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(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
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):
|
||||
assert x < 2
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest(*ignore_parametrized_marks_args)
|
||||
result = testdir.runpytest()
|
||||
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(
|
||||
"""
|
||||
import pytest
|
||||
|
||||
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):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest(*ignore_parametrized_marks_args)
|
||||
result = testdir.runpytest()
|
||||
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(
|
||||
"""
|
||||
import pytest
|
||||
|
@ -849,7 +637,7 @@ class TestFunction(object):
|
|||
pass
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest(*ignore_parametrized_marks_args)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines("* 3 passed in *")
|
||||
|
||||
def test_function_original_name(self, testdir):
|
||||
|
@ -1012,7 +800,7 @@ class TestConftestCustomization(object):
|
|||
modcol = testdir.getmodulecol("def _hello(): pass")
|
||||
values = []
|
||||
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()
|
||||
assert "_hello" not in values
|
||||
|
@ -1095,7 +883,8 @@ def test_modulecol_roundtrip(testdir):
|
|||
|
||||
class TestTracebackCutting(object):
|
||||
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].ishidden()
|
||||
|
||||
|
@ -1262,39 +1051,6 @@ class TestReportInfo(object):
|
|||
@pytest.mark.filterwarnings(
|
||||
"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):
|
||||
# https://github.com/pytest-dev/pytest/issues/1204
|
||||
modcol = testdir.getmodulecol(
|
||||
|
@ -1364,54 +1120,6 @@ def test_customized_python_discovery_functions(testdir):
|
|||
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):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
|
|
|
@ -627,25 +627,6 @@ class TestRequestBasic(object):
|
|||
print(ss.stack)
|
||||
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):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
|
@ -906,7 +887,8 @@ class TestRequestMarking(object):
|
|||
assert "skipif" not in item1.keywords
|
||||
req1.applymarker(pytest.mark.skipif)
|
||||
assert "skipif" in item1.keywords
|
||||
pytest.raises(ValueError, "req1.applymarker(42)")
|
||||
with pytest.raises(ValueError):
|
||||
req1.applymarker(42)
|
||||
|
||||
def test_accesskeywords(self, testdir):
|
||||
testdir.makepyfile(
|
||||
|
@ -952,181 +934,6 @@ class TestRequestMarking(object):
|
|||
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):
|
||||
def test_noargfixturedec(self, testdir):
|
||||
testdir.makepyfile(
|
||||
|
@ -1849,24 +1656,6 @@ class TestAutouseManagement(object):
|
|||
reprec = testdir.inline_run("-s")
|
||||
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):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
|
@ -2314,15 +2103,7 @@ class TestFixtureMarker(object):
|
|||
reprec = testdir.inline_run()
|
||||
reprec.assertoutcome(passed=4)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"method",
|
||||
[
|
||||
'request.getfixturevalue("arg")',
|
||||
'request.cached_setup(lambda: None, scope="function")',
|
||||
],
|
||||
ids=["getfixturevalue", "cached_setup"],
|
||||
)
|
||||
def test_scope_mismatch_various(self, testdir, method):
|
||||
def test_scope_mismatch_various(self, testdir):
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
import pytest
|
||||
|
@ -2338,11 +2119,10 @@ class TestFixtureMarker(object):
|
|||
import pytest
|
||||
@pytest.fixture(scope="session")
|
||||
def arg(request):
|
||||
%s
|
||||
request.getfixturevalue("arg")
|
||||
def test_1(arg):
|
||||
pass
|
||||
"""
|
||||
% method
|
||||
)
|
||||
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
|
||||
assert result.ret != 0
|
||||
|
@ -4070,3 +3850,14 @@ class TestScopeOrdering(object):
|
|||
)
|
||||
reprec = testdir.inline_run()
|
||||
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
|
||||
|
|
|
@ -5,6 +5,7 @@ import textwrap
|
|||
|
||||
import attr
|
||||
import hypothesis
|
||||
import six
|
||||
from hypothesis import strategies
|
||||
|
||||
import pytest
|
||||
|
@ -53,66 +54,6 @@ class TestMetafunc(object):
|
|||
assert metafunc.function is func
|
||||
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 func(x, y):
|
||||
pass
|
||||
|
@ -262,11 +203,8 @@ class TestMetafunc(object):
|
|||
from _pytest.python import _idval
|
||||
|
||||
escaped = _idval(value, "a", 6, None, item=None, config=None)
|
||||
assert isinstance(escaped, str)
|
||||
if PY3:
|
||||
assert isinstance(escaped, six.text_type)
|
||||
escaped.encode("ascii")
|
||||
else:
|
||||
escaped.decode("ascii")
|
||||
|
||||
def test_unicode_idval(self):
|
||||
"""This tests that Unicode strings outside the ASCII character set get
|
||||
|
@ -382,6 +320,34 @@ class TestMetafunc(object):
|
|||
"\\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):
|
||||
from _pytest.python import idmaker
|
||||
|
||||
|
@ -427,7 +393,6 @@ class TestMetafunc(object):
|
|||
)
|
||||
assert result == ["a-a0", "a-a1", "a-a2"]
|
||||
|
||||
@pytest.mark.filterwarnings("default")
|
||||
def test_parametrize_ids_exception(self, testdir):
|
||||
"""
|
||||
:param testdir: the instance of Testdir class, a temporary
|
||||
|
@ -445,14 +410,11 @@ class TestMetafunc(object):
|
|||
pass
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("--collect-only", SHOW_PYTEST_WARNINGS_ARG)
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"<Module 'test_parametrize_ids_exception.py'>",
|
||||
" <Function 'test_foo[a]'>",
|
||||
" <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*",
|
||||
"*test_foo: error raised while trying to determine id of parameter 'arg' at position 0",
|
||||
"*Exception: bad ids",
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -482,19 +444,6 @@ class TestMetafunc(object):
|
|||
)
|
||||
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
|
||||
def test_parametrize_indirect(self):
|
||||
def func(x, y):
|
||||
|
@ -684,20 +633,6 @@ class TestMetafunc(object):
|
|||
["*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):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
|
@ -845,7 +780,7 @@ class TestMetafuncFunctional(object):
|
|||
# assumes that generate/provide runs in the same process
|
||||
import sys, pytest, six
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(param=metafunc)
|
||||
metafunc.parametrize('metafunc', [metafunc])
|
||||
|
||||
@pytest.fixture
|
||||
def metafunc(request):
|
||||
|
@ -870,43 +805,15 @@ class TestMetafuncFunctional(object):
|
|||
result = testdir.runpytest(p, "-v", SHOW_PYTEST_WARNINGS_ARG)
|
||||
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):
|
||||
p = testdir.makepyfile(
|
||||
"""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall(param=10)
|
||||
metafunc.addcall(param=20)
|
||||
|
||||
import pytest
|
||||
@pytest.fixture
|
||||
def arg1(request):
|
||||
return request.param
|
||||
metafunc.parametrize('arg1', [10, 20], ids=['0', '1'])
|
||||
|
||||
def test_func1(arg1):
|
||||
assert arg1 == 10
|
||||
|
||||
def test_func2(arg1):
|
||||
assert arg1 in (10, 20)
|
||||
"""
|
||||
|
@ -917,6 +824,7 @@ class TestMetafuncFunctional(object):
|
|||
"*test_func1*0*PASS*",
|
||||
"*test_func1*1*FAIL*",
|
||||
"*test_func2*PASS*",
|
||||
"*test_func2*PASS*",
|
||||
"*1 failed, 3 passed*",
|
||||
]
|
||||
)
|
||||
|
@ -935,47 +843,12 @@ class TestMetafuncFunctional(object):
|
|||
result = testdir.runpytest(p)
|
||||
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):
|
||||
p = testdir.makepyfile(
|
||||
"""
|
||||
class TestClass(object):
|
||||
def pytest_generate_tests(self, metafunc):
|
||||
metafunc.addcall(funcargs={'hello': 'world'}, id="hello")
|
||||
metafunc.parametrize('hello', ['world'], ids=['hellow'])
|
||||
|
||||
def test_myfunc(self, hello):
|
||||
assert hello == "world"
|
||||
|
@ -988,8 +861,7 @@ class TestMetafuncFunctional(object):
|
|||
p = testdir.makepyfile(
|
||||
"""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall({'arg1': 10})
|
||||
metafunc.addcall({'arg1': 20})
|
||||
metafunc.parametrize('arg1', [10, 20], ids=["0", "1"])
|
||||
|
||||
class TestClass(object):
|
||||
def test_func(self, arg1):
|
||||
|
@ -1006,7 +878,7 @@ class TestMetafuncFunctional(object):
|
|||
p = testdir.makepyfile(
|
||||
"""
|
||||
def pytest_generate_tests(metafunc):
|
||||
metafunc.addcall({'arg1': 1})
|
||||
metafunc.parametrize('arg1', [1])
|
||||
|
||||
class TestClass(object):
|
||||
def test_method(self, arg1):
|
||||
|
@ -1500,7 +1372,6 @@ class TestMetafuncFunctionalAuto(object):
|
|||
assert output.count("preparing foo-3") == 1
|
||||
|
||||
|
||||
@pytest.mark.filterwarnings("ignore:Applying marks directly to parameters")
|
||||
@pytest.mark.issue308
|
||||
class TestMarkersWithParametrization(object):
|
||||
def test_simple_mark(self, testdir):
|
||||
|
@ -1510,7 +1381,7 @@ class TestMarkersWithParametrization(object):
|
|||
@pytest.mark.foo
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(1, 2),
|
||||
pytest.mark.bar((1, 3)),
|
||||
pytest.param(1, 3, marks=pytest.mark.bar),
|
||||
(2, 3),
|
||||
])
|
||||
def test_increment(n, expected):
|
||||
|
@ -1530,7 +1401,7 @@ class TestMarkersWithParametrization(object):
|
|||
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(1, 2),
|
||||
pytest.mark.foo((2, 3)),
|
||||
pytest.param(2, 3, marks=pytest.mark.foo),
|
||||
(3, 4),
|
||||
])
|
||||
def test_increment(n, expected):
|
||||
|
@ -1570,7 +1441,7 @@ class TestMarkersWithParametrization(object):
|
|||
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(1, 2),
|
||||
pytest.mark.xfail((1, 3)),
|
||||
pytest.param(1, 3, marks=pytest.mark.xfail),
|
||||
(2, 3),
|
||||
])
|
||||
def test_increment(n, expected):
|
||||
|
@ -1587,7 +1458,7 @@ class TestMarkersWithParametrization(object):
|
|||
|
||||
@pytest.mark.parametrize("n", [
|
||||
2,
|
||||
pytest.mark.xfail(3),
|
||||
pytest.param(3, marks=pytest.mark.xfail),
|
||||
4,
|
||||
])
|
||||
def test_isEven(n):
|
||||
|
@ -1603,7 +1474,7 @@ class TestMarkersWithParametrization(object):
|
|||
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(1, 2),
|
||||
pytest.mark.xfail("True")((1, 3)),
|
||||
pytest.param(1, 3, marks=pytest.mark.xfail("True")),
|
||||
(2, 3),
|
||||
])
|
||||
def test_increment(n, expected):
|
||||
|
@ -1619,7 +1490,7 @@ class TestMarkersWithParametrization(object):
|
|||
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(1, 2),
|
||||
pytest.mark.xfail(reason="some bug")((1, 3)),
|
||||
pytest.param(1, 3, marks=pytest.mark.xfail(reason="some bug")),
|
||||
(2, 3),
|
||||
])
|
||||
def test_increment(n, expected):
|
||||
|
@ -1635,7 +1506,7 @@ class TestMarkersWithParametrization(object):
|
|||
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(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),
|
||||
])
|
||||
def test_increment(n, expected):
|
||||
|
@ -1650,9 +1521,11 @@ class TestMarkersWithParametrization(object):
|
|||
s = """
|
||||
import pytest
|
||||
|
||||
m = pytest.mark.xfail("sys.version_info > (0, 0, 0)", reason="some bug", strict={strict})
|
||||
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(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),
|
||||
])
|
||||
def test_increment(n, expected):
|
||||
|
@ -1676,7 +1549,7 @@ class TestMarkersWithParametrization(object):
|
|||
failingTestData = [(1, 3),
|
||||
(2, 2)]
|
||||
|
||||
testData = passingTestData + [pytest.mark.xfail(d)
|
||||
testData = passingTestData + [pytest.param(*d, marks=pytest.mark.xfail)
|
||||
for d in failingTestData]
|
||||
metafunc.parametrize(("n", "expected"), testData)
|
||||
|
||||
|
|
|
@ -4,25 +4,32 @@ import six
|
|||
|
||||
import pytest
|
||||
from _pytest.outcomes import Failed
|
||||
from _pytest.warning_types import PytestDeprecationWarning
|
||||
|
||||
|
||||
class TestRaises(object):
|
||||
def test_raises(self):
|
||||
source = "int('qwe')"
|
||||
with pytest.warns(PytestDeprecationWarning):
|
||||
excinfo = pytest.raises(ValueError, source)
|
||||
code = excinfo.traceback[-1].frame.code
|
||||
s = str(code.fullsource)
|
||||
assert s == source
|
||||
|
||||
def test_raises_exec(self):
|
||||
with pytest.warns(PytestDeprecationWarning) as warninfo:
|
||||
pytest.raises(ValueError, "a,x = []")
|
||||
assert warninfo[0].filename == __file__
|
||||
|
||||
def test_raises_exec_correct_filename(self):
|
||||
with pytest.warns(PytestDeprecationWarning):
|
||||
excinfo = pytest.raises(ValueError, 'int("s")')
|
||||
assert __file__ in excinfo.traceback[-1].path
|
||||
|
||||
def test_raises_syntax_error(self):
|
||||
with pytest.warns(PytestDeprecationWarning) as warninfo:
|
||||
pytest.raises(SyntaxError, "qwe qwe qwe")
|
||||
assert warninfo[0].filename == __file__
|
||||
|
||||
def test_raises_function(self):
|
||||
pytest.raises(ValueError, int, "hello")
|
||||
|
@ -119,6 +126,7 @@ class TestRaises(object):
|
|||
def test_custom_raise_message(self):
|
||||
message = "TEST_MESSAGE"
|
||||
try:
|
||||
with pytest.warns(PytestDeprecationWarning):
|
||||
with pytest.raises(ValueError, message=message):
|
||||
pass
|
||||
except pytest.raises.Exception as e:
|
||||
|
|
|
@ -6,6 +6,7 @@ from __future__ import print_function
|
|||
import sys
|
||||
import textwrap
|
||||
|
||||
import attr
|
||||
import py
|
||||
import six
|
||||
|
||||
|
@ -549,6 +550,115 @@ class TestAssert_reprcompare(object):
|
|||
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):
|
||||
def test_special_chars_full(self, testdir):
|
||||
# Issue 453, for the bug this would raise IndexError
|
||||
|
|
|
@ -823,7 +823,9 @@ def test_rewritten():
|
|||
testdir.makepyfile(test_remember_rewritten_modules="")
|
||||
warnings = []
|
||||
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.load_module("test_remember_rewritten_modules")
|
||||
hook.mark_rewrite("test_remember_rewritten_modules")
|
||||
|
|
|
@ -925,3 +925,15 @@ def test_does_not_create_boilerplate_in_existing_dirs(testdir):
|
|||
assert os.path.isdir("v") # cache contents
|
||||
assert not os.path.exists(".gitignore")
|
||||
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
|
||||
|
|
|
@ -87,7 +87,7 @@ class TestCaptureManager(object):
|
|||
try:
|
||||
capman = CaptureManager("fd")
|
||||
capman.start_global_capturing()
|
||||
pytest.raises(AssertionError, "capman.start_global_capturing()")
|
||||
pytest.raises(AssertionError, capman.start_global_capturing)
|
||||
capman.stop_global_capturing()
|
||||
finally:
|
||||
capouter.stop_capturing()
|
||||
|
@ -832,10 +832,10 @@ class TestCaptureIO(object):
|
|||
f = capture.CaptureIO()
|
||||
if sys.version_info >= (3, 0):
|
||||
f.write("\u00f6")
|
||||
pytest.raises(TypeError, "f.write(bytes('hello', 'UTF-8'))")
|
||||
pytest.raises(TypeError, f.write, b"hello")
|
||||
else:
|
||||
f.write(text_type("\u00f6", "UTF-8"))
|
||||
f.write("hello") # bytes
|
||||
f.write(u"\u00f6")
|
||||
f.write(b"hello")
|
||||
s = f.getvalue()
|
||||
f.close()
|
||||
assert isinstance(s, text_type)
|
||||
|
@ -1183,7 +1183,7 @@ class TestStdCapture(object):
|
|||
print("XXX which indicates an error in the underlying capturing")
|
||||
print("XXX mechanisms")
|
||||
with self.getcapture():
|
||||
pytest.raises(IOError, "sys.stdin.read()")
|
||||
pytest.raises(IOError, sys.stdin.read)
|
||||
|
||||
|
||||
class TestStdCaptureFD(TestStdCapture):
|
||||
|
|
|
@ -21,20 +21,6 @@ class TestCollector(object):
|
|||
assert not issubclass(Collector, Item)
|
||||
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):
|
||||
modcol = testdir.getmodulecol(
|
||||
"""
|
||||
|
@ -950,10 +936,10 @@ def test_collect_init_tests(testdir):
|
|||
[
|
||||
"collected 2 items",
|
||||
"<Package *",
|
||||
" <Module '__init__.py'>",
|
||||
" <Function 'test_init'>",
|
||||
" <Module 'test_foo.py'>",
|
||||
" <Function 'test_foo'>",
|
||||
" <Module __init__.py>",
|
||||
" <Function test_init>",
|
||||
" <Module test_foo.py>",
|
||||
" <Function test_foo>",
|
||||
]
|
||||
)
|
||||
result = testdir.runpytest("./tests", "--collect-only")
|
||||
|
@ -961,10 +947,10 @@ def test_collect_init_tests(testdir):
|
|||
[
|
||||
"collected 2 items",
|
||||
"<Package *",
|
||||
" <Module '__init__.py'>",
|
||||
" <Function 'test_init'>",
|
||||
" <Module 'test_foo.py'>",
|
||||
" <Function 'test_foo'>",
|
||||
" <Module __init__.py>",
|
||||
" <Function test_init>",
|
||||
" <Module test_foo.py>",
|
||||
" <Function test_foo>",
|
||||
]
|
||||
)
|
||||
# Ignores duplicates with "." and pkginit (#4310).
|
||||
|
@ -972,11 +958,11 @@ def test_collect_init_tests(testdir):
|
|||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"collected 2 items",
|
||||
"<Package */tests'>",
|
||||
" <Module '__init__.py'>",
|
||||
" <Function 'test_init'>",
|
||||
" <Module 'test_foo.py'>",
|
||||
" <Function 'test_foo'>",
|
||||
"<Package */tests>",
|
||||
" <Module __init__.py>",
|
||||
" <Function test_init>",
|
||||
" <Module test_foo.py>",
|
||||
" <Function test_foo>",
|
||||
]
|
||||
)
|
||||
# Same as before, but different order.
|
||||
|
@ -984,21 +970,21 @@ def test_collect_init_tests(testdir):
|
|||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"collected 2 items",
|
||||
"<Package */tests'>",
|
||||
" <Module '__init__.py'>",
|
||||
" <Function 'test_init'>",
|
||||
" <Module 'test_foo.py'>",
|
||||
" <Function 'test_foo'>",
|
||||
"<Package */tests>",
|
||||
" <Module __init__.py>",
|
||||
" <Function test_init>",
|
||||
" <Module test_foo.py>",
|
||||
" <Function test_foo>",
|
||||
]
|
||||
)
|
||||
result = testdir.runpytest("./tests/test_foo.py", "--collect-only")
|
||||
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()
|
||||
result = testdir.runpytest("./tests/__init__.py", "--collect-only")
|
||||
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()
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ from _pytest.config.findpaths import determine_setup
|
|||
from _pytest.config.findpaths import get_common_ancestor
|
||||
from _pytest.config.findpaths import getcfg
|
||||
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
||||
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
|
||||
|
||||
|
||||
class TestParseIni(object):
|
||||
|
@ -194,7 +193,7 @@ class TestConfigAPI(object):
|
|||
config = testdir.parseconfig("--hello=this")
|
||||
for x in ("hello", "--hello", "-X"):
|
||||
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")
|
||||
def test_config_getoption_unicode(self, testdir):
|
||||
|
@ -211,7 +210,7 @@ class TestConfigAPI(object):
|
|||
|
||||
def test_config_getvalueorskip(self, testdir):
|
||||
config = testdir.parseconfig()
|
||||
pytest.raises(pytest.skip.Exception, "config.getvalueorskip('hello')")
|
||||
pytest.raises(pytest.skip.Exception, config.getvalueorskip, "hello")
|
||||
verbose = config.getvalueorskip("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):
|
||||
config = testdir.parseconfig()
|
||||
excinfo = pytest.raises(ValueError, "raise ValueError(1)")
|
||||
with pytest.raises(ValueError) as excinfo:
|
||||
raise ValueError(1)
|
||||
config.notify_exception(excinfo)
|
||||
out, err = capfd.readouterr()
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
def test_simple_noini(self, tmpdir):
|
||||
assert get_common_ancestor([tmpdir]) == tmpdir
|
||||
|
|
|
@ -153,6 +153,37 @@ class TestPython(object):
|
|||
val = tnode["time"]
|
||||
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):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
|
|
|
@ -5,20 +5,19 @@ from __future__ import print_function
|
|||
import os
|
||||
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
|
||||
|
||||
try:
|
||||
import mock
|
||||
except ImportError:
|
||||
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 objects:pytest.RemovedInPytest4Warning"
|
||||
|
@ -26,12 +25,6 @@ ignore_markinfo = pytest.mark.filterwarnings(
|
|||
|
||||
|
||||
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("modulename", ["py.test", "pytest"])
|
||||
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):
|
||||
mark = Mark()
|
||||
pytest.raises(AttributeError, getattr, 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"
|
||||
with pytest.raises(AttributeError):
|
||||
mark._some_name
|
||||
|
||||
|
||||
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.stdout.fnmatch_lines(
|
||||
[
|
||||
'E ValueError: In "parametrize" the number of values ((1, 2, 3)) '
|
||||
"must be equal to the number of names (['foo', 'bar'])"
|
||||
'test_parametrized_collect_with_wrong_args.py::test_func: in "parametrize" the number of names (2):',
|
||||
" ['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):
|
||||
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):
|
||||
# issue 199 - propagate markers into nested classes
|
||||
p = testdir.makepyfile(
|
||||
|
@ -675,11 +463,6 @@ class TestFunctional(object):
|
|||
items, rec = testdir.inline_genitems(p)
|
||||
base_item, sub_item, sub_item_other = 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
|
||||
assert not list(base_item.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.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):
|
||||
p = testdir.makepyfile(
|
||||
"""
|
||||
|
@ -858,7 +621,7 @@ class TestFunctional(object):
|
|||
assert "mark2" in request.keywords
|
||||
assert "mark3" 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.args == ()
|
||||
assert marker.kwargs == {}
|
||||
|
@ -874,15 +637,11 @@ class TestFunctional(object):
|
|||
.. note:: this could be moved to ``testdir`` if proven to be useful
|
||||
to other modules.
|
||||
"""
|
||||
from _pytest.mark import MarkInfo
|
||||
|
||||
items = {x.name: x for x in items}
|
||||
for name, expected_markers in expected.items():
|
||||
markers = items[name].keywords._markers
|
||||
marker_names = {
|
||||
name for (name, v) in markers.items() if isinstance(v, MarkInfo)
|
||||
}
|
||||
assert marker_names == set(expected_markers)
|
||||
markers = {m.name for m in items[name].iter_markers()}
|
||||
assert markers == set(expected_markers)
|
||||
|
||||
@pytest.mark.issue1540
|
||||
@pytest.mark.filterwarnings("ignore")
|
||||
|
@ -1041,56 +800,6 @@ class TestKeywordSelection(object):
|
|||
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):
|
||||
@pytest.mark.parametrize(
|
||||
"lhs, rhs, expected",
|
||||
|
@ -1191,19 +900,12 @@ def test_mark_expressions_no_smear(testdir):
|
|||
deselected_tests = dlist[0].items
|
||||
assert len(deselected_tests) == 1
|
||||
|
||||
# todo: fixed
|
||||
# keywords smear - expected behaviour
|
||||
reprec_keywords = testdir.inline_run("-k", "FOO")
|
||||
passed_k, skipped_k, failed_k = reprec_keywords.countoutcomes()
|
||||
assert passed_k == 2
|
||||
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
|
||||
# reprec_keywords = testdir.inline_run("-k", "FOO")
|
||||
# passed_k, skipped_k, failed_k = reprec_keywords.countoutcomes()
|
||||
# assert passed_k == 2
|
||||
# assert skipped_k == failed_k == 0
|
||||
|
||||
|
||||
def test_addmarker_order():
|
||||
|
@ -1227,7 +929,7 @@ def test_markers_from_parametrize(testdir):
|
|||
custom_mark = pytest.mark.custom_mark
|
||||
@pytest.fixture(autouse=True)
|
||||
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)
|
||||
|
||||
@custom_mark("custom mark non parametrized")
|
||||
|
@ -1252,3 +954,18 @@ def test_markers_from_parametrize(testdir):
|
|||
|
||||
result = testdir.runpytest(SHOW_PYTEST_WARNINGS_ARG)
|
||||
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)
|
||||
|
|
|
@ -27,7 +27,7 @@ def test_setattr():
|
|||
x = 1
|
||||
|
||||
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)
|
||||
assert A.y == 2
|
||||
monkeypatch.undo()
|
||||
|
@ -99,7 +99,7 @@ def test_delattr():
|
|||
|
||||
monkeypatch = MonkeyPatch()
|
||||
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.setattr(A, "x", 5, raising=False)
|
||||
assert A.x == 5
|
||||
|
@ -156,7 +156,7 @@ def test_delitem():
|
|||
monkeypatch.delitem(d, "x")
|
||||
assert "x" not in d
|
||||
monkeypatch.delitem(d, "y", raising=False)
|
||||
pytest.raises(KeyError, "monkeypatch.delitem(d, 'y')")
|
||||
pytest.raises(KeyError, monkeypatch.delitem, d, "y")
|
||||
assert not d
|
||||
monkeypatch.setitem(d, "y", 1700)
|
||||
assert d["y"] == 1700
|
||||
|
@ -182,7 +182,7 @@ def test_delenv():
|
|||
name = "xyz1234"
|
||||
assert name not in os.environ
|
||||
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.undo()
|
||||
os.environ[name] = "1"
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import division
|
|||
from __future__ import print_function
|
||||
|
||||
import pytest
|
||||
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
|
||||
|
||||
|
||||
def setup_module(mod):
|
||||
|
@ -162,73 +161,6 @@ def test_nose_setup_partial(testdir):
|
|||
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):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
|
|
|
@ -24,6 +24,12 @@ class TestParser(object):
|
|||
out, err = capsys.readouterr()
|
||||
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):
|
||||
with pytest.raises(parseopt.ArgumentError):
|
||||
# need a short or long option
|
||||
|
@ -100,12 +106,8 @@ class TestParser(object):
|
|||
|
||||
def test_group_shortopt_lowercase(self, parser):
|
||||
group = parser.getgroup("hello")
|
||||
pytest.raises(
|
||||
ValueError,
|
||||
"""
|
||||
with pytest.raises(ValueError):
|
||||
group.addoption("-x", action="store_true")
|
||||
""",
|
||||
)
|
||||
assert len(group.options) == 0
|
||||
group._addoption("-x", action="store_true")
|
||||
assert len(group.options) == 1
|
||||
|
|
|
@ -8,7 +8,6 @@ import sys
|
|||
|
||||
import _pytest._code
|
||||
import pytest
|
||||
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
|
||||
|
||||
try:
|
||||
breakpoint
|
||||
|
@ -390,6 +389,28 @@ class TestPDB(object):
|
|||
assert "hello17" in rest # out is captured
|
||||
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):
|
||||
p1 = testdir.makepyfile(
|
||||
"""
|
||||
|
@ -634,6 +655,12 @@ class TestPDB(object):
|
|||
testdir.makepyfile(
|
||||
custom_pdb="""
|
||||
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):
|
||||
print('custom set_trace>')
|
||||
"""
|
||||
|
@ -643,12 +670,13 @@ class TestPDB(object):
|
|||
import pytest
|
||||
|
||||
def test_foo():
|
||||
pytest.set_trace()
|
||||
pytest.set_trace(skip=['foo.*'])
|
||||
"""
|
||||
)
|
||||
monkeypatch.setenv("PYTHONPATH", str(testdir.tmpdir))
|
||||
child = testdir.spawn_pytest("--pdbcls=custom_pdb:CustomPdb %s" % str(p1))
|
||||
|
||||
child.expect("__init__")
|
||||
child.expect("custom set_trace>")
|
||||
self.flush(child)
|
||||
|
||||
|
@ -809,27 +837,6 @@ class TestTraceOption:
|
|||
assert "reading from stdin while output" not in rest
|
||||
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):
|
||||
"""Test that debugging's pytest_configure is re-entrant."""
|
||||
|
|
|
@ -32,7 +32,7 @@ class TestPytestPluginInteractions(object):
|
|||
"""
|
||||
import newhooks
|
||||
def pytest_addhooks(pluginmanager):
|
||||
pluginmanager.addhooks(newhooks)
|
||||
pluginmanager.add_hookspecs(newhooks)
|
||||
def pytest_myhook(xyz):
|
||||
return xyz + 1
|
||||
"""
|
||||
|
@ -52,44 +52,13 @@ class TestPytestPluginInteractions(object):
|
|||
"""
|
||||
import sys
|
||||
def pytest_addhooks(pluginmanager):
|
||||
pluginmanager.addhooks(sys)
|
||||
pluginmanager.add_hookspecs(sys)
|
||||
"""
|
||||
)
|
||||
res = testdir.runpytest()
|
||||
assert res.ret != 0
|
||||
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):
|
||||
config = testdir.parseconfigure()
|
||||
assert not hasattr(config.option, "test123")
|
||||
|
@ -172,34 +141,6 @@ class TestPytestPluginInteractions(object):
|
|||
ihook_b = session.gethookproxy(testdir.tmpdir.join("tests"))
|
||||
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):
|
||||
result = testdir.runpytest("--markers")
|
||||
|
@ -238,7 +179,7 @@ class TestPytestPluginManager(object):
|
|||
assert pm.is_registered(mod)
|
||||
values = pm.get_plugins()
|
||||
assert mod in values
|
||||
pytest.raises(ValueError, "pm.register(mod)")
|
||||
pytest.raises(ValueError, pm.register, mod)
|
||||
pytest.raises(ValueError, lambda: pm.register(mod))
|
||||
# assert not pm.is_registered(mod2)
|
||||
assert pm.get_plugins() == values
|
||||
|
@ -282,11 +223,12 @@ class TestPytestPluginManager(object):
|
|||
with pytest.raises(ImportError):
|
||||
pytestpm.consider_env()
|
||||
|
||||
@pytest.mark.filterwarnings("always")
|
||||
def test_plugin_skip(self, testdir, monkeypatch):
|
||||
p = testdir.makepyfile(
|
||||
skipping1="""
|
||||
import pytest
|
||||
pytest.skip("hello")
|
||||
pytest.skip("hello", allow_module_level=True)
|
||||
"""
|
||||
)
|
||||
p.copy(p.dirpath("skipping2.py"))
|
||||
|
@ -326,8 +268,8 @@ class TestPytestPluginManager(object):
|
|||
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||
|
||||
def test_import_plugin_importname(self, testdir, pytestpm):
|
||||
pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
|
||||
pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwx.y")')
|
||||
pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y")
|
||||
pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwx.y")
|
||||
|
||||
testdir.syspathinsert()
|
||||
pluginname = "pytest_hello"
|
||||
|
@ -343,8 +285,8 @@ class TestPytestPluginManager(object):
|
|||
assert plugin2 is plugin1
|
||||
|
||||
def test_import_plugin_dotted_name(self, testdir, pytestpm):
|
||||
pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
|
||||
pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwex.y")')
|
||||
pytest.raises(ImportError, pytestpm.import_plugin, "qweqwex.y")
|
||||
pytest.raises(ImportError, pytestpm.import_plugin, "pytest_qweqwex.y")
|
||||
|
||||
testdir.syspathinsert()
|
||||
testdir.mkpydir("pkg").join("plug.py").write("x=3")
|
||||
|
@ -365,6 +307,12 @@ class TestPytestPluginManagerBootstrapming(object):
|
|||
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):
|
||||
pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
|
||||
l1 = pytestpm.get_plugins()
|
||||
|
|
|
@ -71,7 +71,7 @@ def test_make_hook_recorder(testdir):
|
|||
recorder.unregister()
|
||||
recorder.clear()
|
||||
recorder.hook.pytest_runtest_logreport(report=rep)
|
||||
pytest.raises(ValueError, "recorder.getfailures()")
|
||||
pytest.raises(ValueError, recorder.getfailures)
|
||||
|
||||
|
||||
def test_parseconfig(testdir):
|
||||
|
@ -168,13 +168,13 @@ def make_holder():
|
|||
@pytest.mark.parametrize("holder", make_holder())
|
||||
def test_hookrecorder_basic(holder):
|
||||
pm = PytestPluginManager()
|
||||
pm.addhooks(holder)
|
||||
pm.add_hookspecs(holder)
|
||||
rec = HookRecorder(pm)
|
||||
pm.hook.pytest_xyz(arg=123)
|
||||
call = rec.popcall("pytest_xyz")
|
||||
assert call.arg == 123
|
||||
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()
|
||||
call = rec.popcall("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")
|
||||
|
||||
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)
|
||||
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import warnings
|
|||
|
||||
import pytest
|
||||
from _pytest.recwarn import WarningsRecorder
|
||||
from _pytest.warning_types import PytestDeprecationWarning
|
||||
|
||||
|
||||
def test_recwarn_stacklevel(recwarn):
|
||||
|
@ -44,7 +45,7 @@ class TestWarningsRecorderChecker(object):
|
|||
rec.clear()
|
||||
assert len(rec.list) == 0
|
||||
assert values is rec.list
|
||||
pytest.raises(AssertionError, "rec.pop()")
|
||||
pytest.raises(AssertionError, rec.pop)
|
||||
|
||||
@pytest.mark.issue(4243)
|
||||
def test_warn_stacklevel(self):
|
||||
|
@ -214,9 +215,17 @@ class TestWarns(object):
|
|||
source1 = "warnings.warn('w1', RuntimeWarning)"
|
||||
source2 = "warnings.warn('w2', RuntimeWarning)"
|
||||
source3 = "warnings.warn('w3', RuntimeWarning)"
|
||||
with pytest.warns(PytestDeprecationWarning) as warninfo: # yo dawg
|
||||
pytest.warns(RuntimeWarning, source1)
|
||||
pytest.raises(pytest.fail.Exception, lambda: pytest.warns(UserWarning, source2))
|
||||
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):
|
||||
pytest.warns(
|
||||
|
|
|
@ -151,7 +151,7 @@ class TestWithFunctionIntegration(object):
|
|||
try:
|
||||
raise ValueError
|
||||
except ValueError:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
reslog = ResultLog(None, py.io.TextIO())
|
||||
reslog.pytest_internalerror(excinfo.getrepr(style=style))
|
||||
entry = reslog.logfile.getvalue()
|
||||
|
|
|
@ -487,13 +487,13 @@ def test_report_extra_parameters(reporttype):
|
|||
|
||||
|
||||
def test_callinfo():
|
||||
ci = runner.CallInfo(lambda: 0, "123")
|
||||
ci = runner.CallInfo.from_call(lambda: 0, "123")
|
||||
assert ci.when == "123"
|
||||
assert ci.result == 0
|
||||
assert "result" in repr(ci)
|
||||
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 not hasattr(ci, "result")
|
||||
assert repr(ci) == "<CallInfo when='123' exception: division by zero>"
|
||||
|
@ -501,16 +501,6 @@ def test_callinfo():
|
|||
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?
|
||||
# then something like the following functional tests makes sense
|
||||
|
||||
|
@ -561,18 +551,14 @@ def test_outcomeexception_passes_except_Exception():
|
|||
|
||||
|
||||
def test_pytest_exit():
|
||||
try:
|
||||
with pytest.raises(pytest.exit.Exception) as excinfo:
|
||||
pytest.exit("hello")
|
||||
except pytest.exit.Exception:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
assert excinfo.errisinstance(KeyboardInterrupt)
|
||||
assert excinfo.errisinstance(pytest.exit.Exception)
|
||||
|
||||
|
||||
def test_pytest_fail():
|
||||
try:
|
||||
with pytest.raises(pytest.fail.Exception) as excinfo:
|
||||
pytest.fail("hello")
|
||||
except pytest.fail.Exception:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
s = excinfo.exconly(tryshort=True)
|
||||
assert s.startswith("Failed")
|
||||
|
||||
|
@ -683,7 +669,7 @@ def test_exception_printing_skip():
|
|||
try:
|
||||
pytest.skip("hello")
|
||||
except pytest.skip.Exception:
|
||||
excinfo = _pytest._code.ExceptionInfo()
|
||||
excinfo = _pytest._code.ExceptionInfo.from_current()
|
||||
s = excinfo.exconly(tryshort=True)
|
||||
assert s.startswith("Skipped")
|
||||
|
||||
|
@ -704,21 +690,17 @@ def test_importorskip(monkeypatch):
|
|||
# check that importorskip reports the actual call
|
||||
# in this test the test_runner.py file
|
||||
assert path.purebasename == "test_runner"
|
||||
pytest.raises(SyntaxError, "pytest.importorskip('x y z')")
|
||||
pytest.raises(SyntaxError, "pytest.importorskip('x=y')")
|
||||
pytest.raises(SyntaxError, pytest.importorskip, "x y z")
|
||||
pytest.raises(SyntaxError, pytest.importorskip, "x=y")
|
||||
mod = types.ModuleType("hello123")
|
||||
mod.__version__ = "1.3"
|
||||
monkeypatch.setitem(sys.modules, "hello123", mod)
|
||||
pytest.raises(
|
||||
pytest.skip.Exception,
|
||||
"""
|
||||
with pytest.raises(pytest.skip.Exception):
|
||||
pytest.importorskip("hello123", minversion="1.3.1")
|
||||
""",
|
||||
)
|
||||
mod2 = pytest.importorskip("hello123", minversion="1.3")
|
||||
assert mod2 == mod
|
||||
except pytest.skip.Exception:
|
||||
print(_pytest._code.ExceptionInfo())
|
||||
print(_pytest._code.ExceptionInfo.from_current())
|
||||
pytest.fail("spurious skip")
|
||||
|
||||
|
||||
|
@ -734,13 +716,10 @@ def test_importorskip_dev_module(monkeypatch):
|
|||
monkeypatch.setitem(sys.modules, "mockmodule", mod)
|
||||
mod2 = pytest.importorskip("mockmodule", minversion="0.12.0")
|
||||
assert mod2 == mod
|
||||
pytest.raises(
|
||||
pytest.skip.Exception,
|
||||
"""
|
||||
pytest.importorskip('mockmodule1', minversion='0.14.0')""",
|
||||
)
|
||||
with pytest.raises(pytest.skip.Exception):
|
||||
pytest.importorskip("mockmodule1", minversion="0.14.0")
|
||||
except pytest.skip.Exception:
|
||||
print(_pytest._code.ExceptionInfo())
|
||||
print(_pytest._code.ExceptionInfo.from_current())
|
||||
pytest.fail("spurious skip")
|
||||
|
||||
|
||||
|
@ -759,6 +738,22 @@ def test_importorskip_module_level(testdir):
|
|||
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):
|
||||
p = testdir.makepyfile(
|
||||
"""
|
||||
|
|
|
@ -7,7 +7,6 @@ from __future__ import division
|
|||
from __future__ import print_function
|
||||
|
||||
import pytest
|
||||
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
reprec = testdir.inline_runsource(
|
||||
"""
|
||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import print_function
|
|||
|
||||
import pytest
|
||||
from _pytest.main import EXIT_NOTESTSCOLLECTED
|
||||
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
|
||||
|
||||
|
||||
class SessionTests(object):
|
||||
|
@ -73,19 +72,6 @@ class SessionTests(object):
|
|||
print(out)
|
||||
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):
|
||||
reprec = testdir.inline_runsource("this is really not python")
|
||||
values = reprec.getfailedcollections()
|
||||
|
@ -243,12 +229,8 @@ class TestNewSession(SessionTests):
|
|||
|
||||
|
||||
def test_plugin_specify(testdir):
|
||||
pytest.raises(
|
||||
ImportError,
|
||||
"""
|
||||
with pytest.raises(ImportError):
|
||||
testdir.parseconfig("-p", "nqweotexistent")
|
||||
""",
|
||||
)
|
||||
# pytest.raises(ImportError,
|
||||
# "config.do_configure(config)"
|
||||
# )
|
||||
|
|
|
@ -875,11 +875,22 @@ def test_reportchars_all(testdir):
|
|||
pass
|
||||
def test_4():
|
||||
pytest.skip("four")
|
||||
@pytest.fixture
|
||||
def fail():
|
||||
assert 0
|
||||
def test_5(fail):
|
||||
pass
|
||||
"""
|
||||
)
|
||||
result = testdir.runpytest("-ra")
|
||||
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*",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ from _pytest.terminal import build_summary_stats_line
|
|||
from _pytest.terminal import getreportopt
|
||||
from _pytest.terminal import repr_pythonversion
|
||||
from _pytest.terminal import TerminalReporter
|
||||
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
|
||||
|
||||
DistInfo = collections.namedtuple("DistInfo", ["project_name", "version"])
|
||||
|
||||
|
@ -105,7 +104,8 @@ class TestTerminal(object):
|
|||
def test_internalerror(self, testdir, linecomp):
|
||||
modcol = testdir.getmodulecol("def test_one(): pass")
|
||||
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())
|
||||
linecomp.assert_contains_lines(["INTERNALERROR> *ValueError*hello*"])
|
||||
|
||||
|
@ -263,7 +263,7 @@ class TestCollectonly(object):
|
|||
)
|
||||
result = testdir.runpytest("--collect-only")
|
||||
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):
|
||||
|
@ -276,6 +276,18 @@ class TestCollectonly(object):
|
|||
result = testdir.runpytest("--collect-only", "-rs")
|
||||
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):
|
||||
testdir.makepyfile("""raise ValueError(0)""")
|
||||
result = testdir.runpytest("--collect-only")
|
||||
|
@ -307,11 +319,10 @@ class TestCollectonly(object):
|
|||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(
|
||||
[
|
||||
"*<Module '*.py'>",
|
||||
"* <Function 'test_func1'*>",
|
||||
"* <Class 'TestClass'>",
|
||||
# "* <Instance '()'>",
|
||||
"* <Function 'test_method'*>",
|
||||
"*<Module *.py>",
|
||||
"* <Function test_func1>",
|
||||
"* <Class TestClass>",
|
||||
"* <Function test_method>",
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -540,7 +551,7 @@ class TestTerminalFunctional(object):
|
|||
result.stdout.fnmatch_lines(["test_passes.py ..*", "* 2 pass*"])
|
||||
assert result.ret == 0
|
||||
|
||||
def test_header_trailer_info(self, testdir):
|
||||
def test_header_trailer_info(self, testdir, request):
|
||||
testdir.makepyfile(
|
||||
"""
|
||||
def test_passes():
|
||||
|
@ -564,7 +575,7 @@ class TestTerminalFunctional(object):
|
|||
"=* 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: *"])
|
||||
|
||||
def test_showlocals(self, testdir):
|
||||
|
@ -585,8 +596,9 @@ class TestTerminalFunctional(object):
|
|||
]
|
||||
)
|
||||
|
||||
def test_verbose_reporting(self, testdir, pytestconfig):
|
||||
p1 = testdir.makepyfile(
|
||||
@pytest.fixture
|
||||
def verbose_testfile(self, testdir):
|
||||
return testdir.makepyfile(
|
||||
"""
|
||||
import pytest
|
||||
def test_fail():
|
||||
|
@ -602,22 +614,32 @@ class TestTerminalFunctional(object):
|
|||
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(
|
||||
[
|
||||
"*test_verbose_reporting.py::test_fail *FAIL*",
|
||||
"*test_verbose_reporting.py::test_pass *PASS*",
|
||||
"*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
|
||||
|
||||
def test_verbose_reporting_xdist(self, verbose_testfile, testdir, pytestconfig):
|
||||
if not pytestconfig.pluginmanager.get_plugin("xdist"):
|
||||
pytest.skip("xdist plugin not installed")
|
||||
|
||||
result = testdir.runpytest(p1, "-v", "-n 1", SHOW_PYTEST_WARNINGS_ARG)
|
||||
result.stdout.fnmatch_lines(["*FAIL*test_verbose_reporting.py::test_fail*"])
|
||||
result = testdir.runpytest(
|
||||
verbose_testfile, "-v", "-n 1", "-Walways::pytest.PytestWarning"
|
||||
)
|
||||
result.stdout.fnmatch_lines(
|
||||
["*FAIL*test_verbose_reporting_xdist.py::test_fail*"]
|
||||
)
|
||||
assert result.ret == 1
|
||||
|
||||
def test_quiet_reporting(self, testdir):
|
||||
|
|
|
@ -308,9 +308,9 @@ def test_filterwarnings_mark_registration(testdir):
|
|||
def test_warning_captured_hook(testdir):
|
||||
testdir.makeconftest(
|
||||
"""
|
||||
from _pytest.warnings import _issue_config_warning
|
||||
from _pytest.warnings import _issue_warning_captured
|
||||
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(
|
||||
|
@ -623,3 +623,63 @@ def test_removed_in_pytest4_warning_as_error(testdir, change_default):
|
|||
else:
|
||||
assert change_default in ("ini", "cmdline")
|
||||
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*"])
|
||||
|
|
4
tox.ini
4
tox.ini
|
@ -121,6 +121,7 @@ setenv=
|
|||
setenv = {[testenv:py27-pluggymaster]setenv}
|
||||
|
||||
[testenv:docs]
|
||||
basepython = python3
|
||||
skipsdist = True
|
||||
usedevelop = True
|
||||
changedir = doc/en
|
||||
|
@ -130,7 +131,7 @@ commands =
|
|||
sphinx-build -W -b html . _build
|
||||
|
||||
[testenv:doctesting]
|
||||
basepython = python
|
||||
basepython = python3
|
||||
skipsdist = True
|
||||
deps =
|
||||
PyYAML
|
||||
|
@ -147,6 +148,7 @@ deps =
|
|||
sphinx
|
||||
PyYAML
|
||||
regendoc>=0.6.1
|
||||
dataclasses
|
||||
whitelist_externals =
|
||||
rm
|
||||
make
|
||||
|
|
Loading…
Reference in New Issue