Merge pull request #4600 from nicoddemus/release-4.1.0

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

1
.gitignore vendored
View File

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

View File

@ -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

View File

@ -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>`_)

View File

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

View File

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

View File

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

View File

@ -39,7 +39,7 @@ clean:
-rm -rf $(BUILDDIR)/*
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

View File

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

View File

@ -0,0 +1,44 @@
pytest-4.1.0
=======================================
The pytest team is proud to announce the 4.1.0 release!
pytest is a mature Python testing tool with more than a 2000 tests
against itself, passing on many different interpreters and platforms.
This release contains a number of bugs fixes and improvements, so users are encouraged
to take a look at the CHANGELOG:
https://docs.pytest.org/en/latest/changelog.html
For complete documentation, please visit:
https://docs.pytest.org/en/latest/
As usual, you can upgrade from pypi via:
pip install -U pytest
Thanks to all who contributed to this release, among them:
* Adam Johnson
* Aly Sivji
* Andrey Paramonov
* Anthony Sottile
* Bruno Oliveira
* Daniel Hahler
* David Vo
* Hyunchel Kim
* Jeffrey Rackauckas
* Kanguros
* Nicholas Devenish
* Pedro Algarvio
* Randy Barlow
* Ronny Pfannschmidt
* Tomer Keren
* feuillemorte
* wim glenn
Happy testing,
The Pytest Development Team

View File

@ -100,10 +100,9 @@ If you want to write test code that works on Python 2.4 as well,
you may also use two other ways to test for an expected exception::
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*.

View File

@ -68,8 +68,6 @@ For information about fixtures, see :ref:`fixtures`. To see a complete list of a
def test_function(record_property):
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

View File

@ -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.

View File

@ -7,6 +7,11 @@ This page lists all pytest features that are currently deprecated or have been r
The objective is to give users a clear rationale why a certain feature has been removed, and what alternatives
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)``.

View File

@ -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")

View File

@ -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

View File

@ -90,9 +90,9 @@ interesting to just look at the collection tree:
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
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 =======================

View File

@ -147,15 +147,15 @@ objects, they are still using the default pytest representation:
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
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:

View File

@ -134,10 +134,10 @@ The test collection would look like this:
platform linux -- Python 3.x.y, pytest-4.x.y, py-1.x.y, pluggy-0.x.y
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 =======================

View File

@ -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)")
s = "qwe"
> raises(TypeError, int, s)
E ValueError: invalid literal for int() with base 10: 'qwe'
failure_demo.py:145:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
> 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 =========================

View File

@ -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

View File

@ -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 =======================

View File

@ -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
~~~~

View File

@ -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::

View File

@ -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:
tup = sys.exc_info()
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))
@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(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:
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
except AttributeError:
return "<ExceptionInfo uninitialized>"
if self._excinfo is None:
return "<ExceptionInfo for raises contextmanager>"
return "<ExceptionInfo %s tblen=%d>" % (self.typename, len(self.traceback))
def exconly(self, tryshort=False):
""" return the exception as a string
@ -516,13 +557,11 @@ 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:
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
return str(loc)
entry = self.traceback[-1]
loc = ReprFileLocation(entry.path, entry.lineno + 1, self.exconly())
return str(loc)
def __unicode__(self):
entry = self.traceback[-1]

View File

@ -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.

View File

@ -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]

View File

@ -33,6 +33,13 @@ which provides the `--lf` and `--ff` options, as well as the `cache` fixture.
See [the docs](https://docs.pytest.org/en/latest/cache.html) for more information.
"""
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 """

View File

@ -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):

View File

@ -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",

View File

@ -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,19 +284,27 @@ 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(
"{} plugin has been merged into the core, "
"please remove it from your requirements.".format(
name.replace("_", "-")
warnings.warn(
PytestWarning(
"{} plugin has been merged into the core, "
"please remove it from your requirements.".format(
name.replace("_", "-")
)
)
)
return
@ -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

View File

@ -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,

View File

@ -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
rootdir = get_common_ancestor(dirs)
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:
rootdir, inifile, inicfg = getcfg(dirs, config=config)
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 {}

View File

@ -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,13 +132,18 @@ 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:
_pdb.set_trace(frame)
@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)
class PdbInvoke(object):
@ -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)

View File

@ -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"
)

View File

@ -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)
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
@six.wraps(function)
def result(*args, **kwargs):
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 = parametrize_mark.args[0]
if not isinstance(argnames, (tuple, list)):
argnames = [
x.strip() for x in argnames.split(",") if x.strip()
]
if argname in argnames:
break
else:
argnames = func_params[0]
if not isinstance(argnames, (tuple, list)):
argnames = [x.strip() for x in argnames.split(",") if x.strip()]
if argname not in func_params and argname not in argnames:
metafunc.parametrize(
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)
if marker.name:
name = marker.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

View File

@ -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

View File

@ -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,8 +504,9 @@ class LogXML(object):
"""accumulates total duration for nodeid from given report and updates
the Junit.testcase with the new total if already created.
"""
reporter = self.node_reporter(report)
reporter.duration += getattr(report, "duration", 0.0)
if self.report_duration == "total" or report.when == self.report_duration:
reporter = self.node_reporter(report)
reporter.duration += getattr(report, "duration", 0.0)
def pytest_collectreport(self, report):
if not report.passed:

View File

@ -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):

View File

@ -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):

View File

@ -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,11 +230,7 @@ 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)
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::

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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.
In the context manager form you may use the keyword argument
``message`` to specify a custom failure message::
You can also use the keyword argument ``match`` to assert that the
exception matches a text or regex::
>>> with raises(ZeroDivisionError, message="Expecting ZeroDivisionError"):
... pass
Traceback (most recent call last):
...
Failed: Expecting ZeroDivisionError
>>> 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 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):

View File

@ -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)

View File

@ -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):

View File

@ -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"

View File

@ -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):

View File

@ -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):

View File

@ -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)
)

View File

@ -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",

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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,
"""
teardown = None
try:
c(1)
finally:
if teardown:
teardown()
""",
)
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)"

View File

@ -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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -496,3 +496,14 @@ class TestApprox(object):
assert actual != approx(expected, rel=5e-8, abs=0)
assert 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)

View File

@ -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(
"""

View File

@ -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

View File

@ -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:
escaped.encode("ascii")
else:
escaped.decode("ascii")
assert isinstance(escaped, six.text_type)
escaped.encode("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)

View File

@ -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')"
excinfo = pytest.raises(ValueError, source)
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):
pytest.raises(ValueError, "a,x = []")
with pytest.warns(PytestDeprecationWarning) as warninfo:
pytest.raises(ValueError, "a,x = []")
assert warninfo[0].filename == __file__
def test_raises_exec_correct_filename(self):
excinfo = pytest.raises(ValueError, 'int("s")')
assert __file__ in excinfo.traceback[-1].path
with pytest.warns(PytestDeprecationWarning):
excinfo = pytest.raises(ValueError, 'int("s")')
assert __file__ in excinfo.traceback[-1].path
def test_raises_syntax_error(self):
pytest.raises(SyntaxError, "qwe qwe qwe")
with pytest.warns(PytestDeprecationWarning) as warninfo:
pytest.raises(SyntaxError, "qwe qwe qwe")
assert warninfo[0].filename == __file__
def test_raises_function(self):
pytest.raises(ValueError, int, "hello")
@ -119,8 +126,9 @@ class TestRaises(object):
def test_custom_raise_message(self):
message = "TEST_MESSAGE"
try:
with pytest.raises(ValueError, message=message):
pass
with pytest.warns(PytestDeprecationWarning):
with pytest.raises(ValueError, message=message):
pass
except pytest.raises.Exception as e:
assert e.msg == message
else:

View File

@ -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

View File

@ -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")

View File

@ -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

View File

@ -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):

View File

@ -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()

View File

@ -12,7 +12,6 @@ from _pytest.config.findpaths import determine_setup
from _pytest.config.findpaths import get_common_ancestor
from _pytest.config.findpaths import 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

View File

@ -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(
"""

View File

@ -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)

View File

@ -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"

View File

@ -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(
"""

View File

@ -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

View File

@ -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."""

View File

@ -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()

View File

@ -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)

View File

@ -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)"
pytest.warns(RuntimeWarning, source1)
pytest.raises(pytest.fail.Exception, lambda: pytest.warns(UserWarning, source2))
pytest.warns(RuntimeWarning, source3)
with pytest.warns(PytestDeprecationWarning) as warninfo: # yo dawg
pytest.warns(RuntimeWarning, source1)
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(

View File

@ -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()

View File

@ -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,20 +551,16 @@ 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")
s = excinfo.exconly(tryshort=True)
assert s.startswith("Failed")
def test_pytest_exit_msg(testdir):
@ -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(
"""

View File

@ -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(
"""

View File

@ -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,
"""
testdir.parseconfig("-p", "nqweotexistent")
""",
)
with pytest.raises(ImportError):
testdir.parseconfig("-p", "nqweotexistent")
# pytest.raises(ImportError,
# "config.do_configure(config)"
# )

View File

@ -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*",
]
)

View File

@ -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):

View File

@ -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*"])

View File

@ -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