Merge master into features

Several conflicts, mostly due to 2c402f4bd.

Conflicts:
	.pre-commit-config.yaml
	src/_pytest/outcomes.py
	src/_pytest/python_api.py
	tox.ini
This commit is contained in:
Daniel Hahler 2019-08-02 16:20:21 +02:00
commit b5b710b3ae
66 changed files with 626 additions and 235 deletions

View File

@ -5,13 +5,11 @@ repos:
hooks: hooks:
- id: black - id: black
args: [--safe, --quiet] args: [--safe, --quiet]
language_version: python3
- repo: https://github.com/asottile/blacken-docs - repo: https://github.com/asottile/blacken-docs
rev: v1.0.0 rev: v1.0.0
hooks: hooks:
- id: blacken-docs - id: blacken-docs
additional_dependencies: [black==19.3b0] additional_dependencies: [black==19.3b0]
language_version: python3
- repo: https://github.com/pre-commit/pre-commit-hooks - repo: https://github.com/pre-commit/pre-commit-hooks
rev: v2.2.3 rev: v2.2.3
hooks: hooks:

View File

@ -13,6 +13,10 @@ env:
global: global:
- PYTEST_ADDOPTS=-vv - PYTEST_ADDOPTS=-vv
# setuptools-scm needs all tags in order to obtain a proper version
git:
depth: false
install: install:
- python -m pip install --upgrade --pre tox - python -m pip install --upgrade --pre tox

View File

@ -239,6 +239,7 @@ Tareq Alayan
Ted Xiao Ted Xiao
Thomas Grainger Thomas Grainger
Thomas Hisch Thomas Hisch
Tim Hoffmann
Tim Strazny Tim Strazny
Tom Dalton Tom Dalton
Tom Viner Tom Viner
@ -258,6 +259,7 @@ Wil Cooley
William Lee William Lee
Wim Glenn Wim Glenn
Wouter van Ackooy Wouter van Ackooy
Xixi Zhao
Xuan Luong Xuan Luong
Xuecong Liao Xuecong Liao
Zac Hatfield-Dodds Zac Hatfield-Dodds

View File

@ -1,6 +1,6 @@
================= =========
Changelog history Changelog
================= =========
Versions follow `Semantic Versioning <https://semver.org/>`_ (``<major>.<minor>.<patch>``). Versions follow `Semantic Versioning <https://semver.org/>`_ (``<major>.<minor>.<patch>``).
@ -90,6 +90,24 @@ Removals
- `#5412 <https://github.com/pytest-dev/pytest/issues/5412>`_: ``ExceptionInfo`` objects (returned by ``pytest.raises``) now have the same ``str`` representation as ``repr``, which - `#5412 <https://github.com/pytest-dev/pytest/issues/5412>`_: ``ExceptionInfo`` objects (returned by ``pytest.raises``) now have the same ``str`` representation as ``repr``, which
avoids some confusion when users use ``print(e)`` to inspect the object. avoids some confusion when users use ``print(e)`` to inspect the object.
This means code like:
.. code-block:: python
with pytest.raises(SomeException) as e:
...
assert "some message" in str(e)
Needs to be changed to:
.. code-block:: python
with pytest.raises(SomeException) as e:
...
assert "some message" in str(e.value)
Deprecations Deprecations
@ -2173,10 +2191,10 @@ Features
design. This introduces new ``Node.iter_markers(name)`` and design. This introduces new ``Node.iter_markers(name)`` and
``Node.get_closest_marker(name)`` APIs. Users are **strongly encouraged** to ``Node.get_closest_marker(name)`` APIs. Users are **strongly encouraged** to
read the `reasons for the revamp in the docs read the `reasons for the revamp in the docs
<https://docs.pytest.org/en/latest/mark.html#marker-revamp-and-iteration>`_, <https://docs.pytest.org/en/latest/historical-notes.html#marker-revamp-and-iteration>`_,
or jump over to details about `updating existing code to use the new APIs or jump over to details about `updating existing code to use the new APIs
<https://docs.pytest.org/en/latest/mark.html#updating-code>`_. (`#3317 <https://docs.pytest.org/en/latest/historical-notes.html#updating-code>`_.
<https://github.com/pytest-dev/pytest/issues/3317>`_) (`#3317 <https://github.com/pytest-dev/pytest/issues/3317>`_)
- Now when ``@pytest.fixture`` is applied more than once to the same function a - Now when ``@pytest.fixture`` is applied more than once to the same function a
``ValueError`` is raised. This buggy behavior would cause surprising problems ``ValueError`` is raised. This buggy behavior would cause surprising problems
@ -2582,10 +2600,10 @@ Features
<https://github.com/pytest-dev/pytest/issues/3038>`_) <https://github.com/pytest-dev/pytest/issues/3038>`_)
- New `pytest_runtest_logfinish - New `pytest_runtest_logfinish
<https://docs.pytest.org/en/latest/writing_plugins.html#_pytest.hookspec.pytest_runtest_logfinish>`_ <https://docs.pytest.org/en/latest/reference.html#_pytest.hookspec.pytest_runtest_logfinish>`_
hook which is called when a test item has finished executing, analogous to hook which is called when a test item has finished executing, analogous to
`pytest_runtest_logstart `pytest_runtest_logstart
<https://docs.pytest.org/en/latest/writing_plugins.html#_pytest.hookspec.pytest_runtest_start>`_. <https://docs.pytest.org/en/latest/reference.html#_pytest.hookspec.pytest_runtest_logstart>`_.
(`#3101 <https://github.com/pytest-dev/pytest/issues/3101>`_) (`#3101 <https://github.com/pytest-dev/pytest/issues/3101>`_)
- Improve performance when collecting tests using many fixtures. (`#3107 - Improve performance when collecting tests using many fixtures. (`#3107
@ -3575,7 +3593,7 @@ Bug Fixes
Thanks `@sirex`_ for the report and `@nicoddemus`_ for the PR. Thanks `@sirex`_ for the report and `@nicoddemus`_ for the PR.
* Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_). * Replace ``raise StopIteration`` usages in the code by simple ``returns`` to finish generators, in accordance to `PEP-479`_ (`#2160`_).
Thanks `@tgoodlet`_ for the report and `@nicoddemus`_ for the PR. Thanks to `@nicoddemus`_ for the PR.
* Fix internal errors when an unprintable ``AssertionError`` is raised inside a test. * Fix internal errors when an unprintable ``AssertionError`` is raised inside a test.
Thanks `@omerhadari`_ for the PR. Thanks `@omerhadari`_ for the PR.
@ -3706,7 +3724,7 @@ Bug Fixes
.. _@syre: https://github.com/syre .. _@syre: https://github.com/syre
.. _@adler-j: https://github.com/adler-j .. _@adler-j: https://github.com/adler-j
.. _@d-b-w: https://bitbucket.org/d-b-w/ .. _@d-b-w: https://github.com/d-b-w
.. _@DuncanBetts: https://github.com/DuncanBetts .. _@DuncanBetts: https://github.com/DuncanBetts
.. _@dupuy: https://bitbucket.org/dupuy/ .. _@dupuy: https://bitbucket.org/dupuy/
.. _@kerrick-lyft: https://github.com/kerrick-lyft .. _@kerrick-lyft: https://github.com/kerrick-lyft
@ -3766,7 +3784,7 @@ Bug Fixes
.. _@adborden: https://github.com/adborden .. _@adborden: https://github.com/adborden
.. _@cwitty: https://github.com/cwitty .. _@cwitty: https://github.com/cwitty
.. _@d_b_w: https://github.com/d_b_w .. _@d_b_w: https://github.com/d-b-w
.. _@gdyuldin: https://github.com/gdyuldin .. _@gdyuldin: https://github.com/gdyuldin
.. _@matclab: https://github.com/matclab .. _@matclab: https://github.com/matclab
.. _@MSeifert04: https://github.com/MSeifert04 .. _@MSeifert04: https://github.com/MSeifert04
@ -3801,7 +3819,7 @@ Bug Fixes
Thanks `@axil`_ for the PR. Thanks `@axil`_ for the PR.
* Explain a bad scope value passed to ``@fixture`` declarations or * Explain a bad scope value passed to ``@fixture`` declarations or
a ``MetaFunc.parametrize()`` call. Thanks `@tgoodlet`_ for the PR. a ``MetaFunc.parametrize()`` call.
* This version includes ``pluggy-0.4.0``, which correctly handles * This version includes ``pluggy-0.4.0``, which correctly handles
``VersionConflict`` errors in plugins (`#704`_). ``VersionConflict`` errors in plugins (`#704`_).
@ -3811,7 +3829,6 @@ Bug Fixes
.. _@philpep: https://github.com/philpep .. _@philpep: https://github.com/philpep
.. _@raquel-ucl: https://github.com/raquel-ucl .. _@raquel-ucl: https://github.com/raquel-ucl
.. _@axil: https://github.com/axil .. _@axil: https://github.com/axil
.. _@tgoodlet: https://github.com/tgoodlet
.. _@vlad-dragos: https://github.com/vlad-dragos .. _@vlad-dragos: https://github.com/vlad-dragos
.. _#1853: https://github.com/pytest-dev/pytest/issues/1853 .. _#1853: https://github.com/pytest-dev/pytest/issues/1853
@ -4157,7 +4174,7 @@ time or change existing behaviors in order to make them less surprising/more use
* Updated docstrings with a more uniform style. * Updated docstrings with a more uniform style.
* Add stderr write for ``pytest.exit(msg)`` during startup. Previously the message was never shown. * Add stderr write for ``pytest.exit(msg)`` during startup. Previously the message was never shown.
Thanks `@BeyondEvil`_ for reporting `#1210`_. Thanks to `@JonathonSonesen`_ and Thanks `@BeyondEvil`_ for reporting `#1210`_. Thanks to `@jgsonesen`_ and
`@tomviner`_ for the PR. `@tomviner`_ for the PR.
* No longer display the incorrect test deselection reason (`#1372`_). * No longer display the incorrect test deselection reason (`#1372`_).
@ -4205,7 +4222,7 @@ time or change existing behaviors in order to make them less surprising/more use
Thanks to `@Stranger6667`_ for the PR. Thanks to `@Stranger6667`_ for the PR.
* Fixed the total tests tally in junit xml output (`#1798`_). * Fixed the total tests tally in junit xml output (`#1798`_).
Thanks to `@cryporchild`_ for the PR. Thanks to `@cboelsen`_ for the PR.
* Fixed off-by-one error with lines from ``request.node.warn``. * Fixed off-by-one error with lines from ``request.node.warn``.
Thanks to `@blueyed`_ for the PR. Thanks to `@blueyed`_ for the PR.
@ -4278,7 +4295,7 @@ time or change existing behaviors in order to make them less surprising/more use
.. _@BeyondEvil: https://github.com/BeyondEvil .. _@BeyondEvil: https://github.com/BeyondEvil
.. _@blueyed: https://github.com/blueyed .. _@blueyed: https://github.com/blueyed
.. _@ceridwen: https://github.com/ceridwen .. _@ceridwen: https://github.com/ceridwen
.. _@cryporchild: https://github.com/cryporchild .. _@cboelsen: https://github.com/cboelsen
.. _@csaftoiu: https://github.com/csaftoiu .. _@csaftoiu: https://github.com/csaftoiu
.. _@d6e: https://github.com/d6e .. _@d6e: https://github.com/d6e
.. _@davehunt: https://github.com/davehunt .. _@davehunt: https://github.com/davehunt
@ -4289,7 +4306,7 @@ time or change existing behaviors in order to make them less surprising/more use
.. _@gprasad84: https://github.com/gprasad84 .. _@gprasad84: https://github.com/gprasad84
.. _@graingert: https://github.com/graingert .. _@graingert: https://github.com/graingert
.. _@hartym: https://github.com/hartym .. _@hartym: https://github.com/hartym
.. _@JonathonSonesen: https://github.com/JonathonSonesen .. _@jgsonesen: https://github.com/jgsonesen
.. _@kalekundert: https://github.com/kalekundert .. _@kalekundert: https://github.com/kalekundert
.. _@kvas-it: https://github.com/kvas-it .. _@kvas-it: https://github.com/kvas-it
.. _@marscher: https://github.com/marscher .. _@marscher: https://github.com/marscher
@ -4426,7 +4443,7 @@ time or change existing behaviors in order to make them less surprising/more use
**Changes** **Changes**
* **Important**: `py.code <https://pylib.readthedocs.io/en/latest/code.html>`_ has been * **Important**: `py.code <https://pylib.readthedocs.io/en/stable/code.html>`_ has been
merged into the ``pytest`` repository as ``pytest._code``. This decision merged into the ``pytest`` repository as ``pytest._code``. This decision
was made because ``py.code`` had very few uses outside ``pytest`` and the was made because ``py.code`` had very few uses outside ``pytest`` and the
fact that it was in a different repository made it difficult to fix bugs on fact that it was in a different repository made it difficult to fix bugs on

View File

@ -5,8 +5,9 @@ Contribution getting started
Contributions are highly welcomed and appreciated. Every little help counts, Contributions are highly welcomed and appreciated. Every little help counts,
so do not hesitate! so do not hesitate!
.. contents:: Contribution links .. contents::
:depth: 2 :depth: 2
:backlinks: none
.. _submitfeedback: .. _submitfeedback:

View File

@ -9,7 +9,7 @@ What is it
========== ==========
Open Collective is an online funding platform for open and transparent communities. Open Collective is an online funding platform for open and transparent communities.
It provide tools to raise money and share your finances in full transparency. It provides tools to raise money and share your finances in full transparency.
It is the platform of choice for individuals and companies that want to make one-time or It is the platform of choice for individuals and companies that want to make one-time or
monthly donations directly to the project. monthly donations directly to the project.
@ -19,7 +19,7 @@ Funds
The OpenCollective funds donated to pytest will be used to fund overall maintenance, The OpenCollective funds donated to pytest will be used to fund overall maintenance,
local sprints, merchandising (stickers to distribute in conferences for example), and future local sprints, merchandising (stickers to distribute in conferences for example), and future
gatherings of pytest developers (Sprints). gatherings of pytest developers (sprints).
`Core contributors`_ which are contributing on a continuous basis are free to submit invoices `Core contributors`_ which are contributing on a continuous basis are free to submit invoices
to bill maintenance hours using the platform. How much each contributor should request is still an to bill maintenance hours using the platform. How much each contributor should request is still an

View File

@ -111,13 +111,13 @@ Consult the `Changelog <https://docs.pytest.org/en/latest/changelog.html>`__ pag
Support pytest Support pytest
-------------- --------------
You can support pytest by obtaining a `Tideflift subscription`_. You can support pytest by obtaining a `Tidelift subscription`_.
Tidelift gives software development teams a single source for purchasing and maintaining their software, Tidelift gives software development teams a single source for purchasing and maintaining their software,
with professional grade assurances from the experts who know it best, while seamlessly integrating with existing tools. with professional grade assurances from the experts who know it best, while seamlessly integrating with existing tools.
.. _`Tideflift subscription`: https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=readme .. _`Tidelift subscription`: https://tidelift.com/subscription/pkg/pypi-pytest?utm_source=pypi-pytest&utm_medium=referral&utm_campaign=readme
Security Security

View File

@ -0,0 +1 @@
Cache node splitting function which can improve collection performance in very large test suites.

View File

@ -0,0 +1,2 @@
Fix issue where ``tmp_path`` and ``tmpdir`` would not remove directories containing files marked as read-only,
which could lead to pytest crashing when executed a second time with the ``--basetemp`` option.

View File

@ -0,0 +1,3 @@
Improve type checking for some exception-raising functions (``pytest.xfail``, ``pytest.skip``, etc)
so they provide better error messages when users meant to use marks (for example ``@pytest.xfail``
instead of ``@pytest.mark.xfail``).

View File

@ -0,0 +1,2 @@
Fixed internal error when test functions were patched with objects that cannot be compared
for truth values against others, like ``numpy`` arrays.

View File

@ -0,0 +1,2 @@
``pytest.exit`` is now correctly handled in ``unittest`` cases.
This makes ``unittest`` cases handle ``quit`` from pytest's pdb correctly.

View File

@ -0,0 +1 @@
Improved output when parsing an ini configuration file fails.

View File

@ -0,0 +1,2 @@
When invoking pytest's own testsuite with ``PYTHONDONTWRITEBYTECODE=1``,
the ``test_xfail_handling`` test no longer fails.

View File

@ -4,7 +4,7 @@
<li><a href="{{ pathto('index') }}">Home</a></li> <li><a href="{{ pathto('index') }}">Home</a></li>
<li><a href="{{ pathto('getting-started') }}">Install</a></li> <li><a href="{{ pathto('getting-started') }}">Install</a></li>
<li><a href="{{ pathto('contents') }}">Contents</a></li> <li><a href="{{ pathto('contents') }}">Contents</a></li>
<li><a href="{{ pathto('reference') }}">Reference</a></li> <li><a href="{{ pathto('reference') }}">API Reference</a></li>
<li><a href="{{ pathto('example/index') }}">Examples</a></li> <li><a href="{{ pathto('example/index') }}">Examples</a></li>
<li><a href="{{ pathto('customize') }}">Customize</a></li> <li><a href="{{ pathto('customize') }}">Customize</a></li>
<li><a href="{{ pathto('changelog') }}">Changelog</a></li> <li><a href="{{ pathto('changelog') }}">Changelog</a></li>

View File

@ -16,7 +16,7 @@
{%- block footer %} {%- block footer %}
<div class="footer"> <div class="footer">
&copy; Copyright {{ copyright }}. &copy; Copyright {{ copyright }}.
Created using <a href="http://sphinx.pocoo.org/">Sphinx</a>. Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> {{ sphinx_version }}.
</div> </div>
{% if pagename == 'index' %} {% if pagename == 'index' %}
</div> </div>

View File

@ -0,0 +1,15 @@
{#
basic/searchbox.html with heading removed.
#}
{%- if pagename != "search" and builder != "singlehtml" %}
<div id="searchbox" style="display: none" role="search">
<div class="searchformwrapper">
<form class="search" action="{{ pathto('search') }}" method="get">
<input type="text" name="q" aria-labelledby="searchlabel"
placeholder="Search"/>
<input type="submit" value="{{ _('Go') }}" />
</form>
</div>
</div>
<script type="text/javascript">$('#searchbox').show(0);</script>
{%- endif %}

View File

@ -8,11 +8,12 @@
{% set page_width = '1020px' %} {% set page_width = '1020px' %}
{% set sidebar_width = '220px' %} {% set sidebar_width = '220px' %}
/* orange of logo is #d67c29 but we use black for links for now */ /* muted version of green logo color #C9D22A */
{% set link_color = '#000' %} {% set link_color = '#606413' %}
{% set link_hover_color = '#000' %} /* blue logo color */
{% set link_hover_color = '#009de0' %}
{% set base_font = 'sans-serif' %} {% set base_font = 'sans-serif' %}
{% set header_font = 'serif' %} {% set header_font = 'sans-serif' %}
@import url("basic.css"); @import url("basic.css");
@ -20,7 +21,7 @@
body { body {
font-family: {{ base_font }}; font-family: {{ base_font }};
font-size: 17px; font-size: 16px;
background-color: white; background-color: white;
color: #000; color: #000;
margin: 0; margin: 0;
@ -78,13 +79,13 @@ div.related {
} }
div.sphinxsidebar a { div.sphinxsidebar a {
color: #444;
text-decoration: none; text-decoration: none;
border-bottom: 1px dotted #999; border-bottom: none;
} }
div.sphinxsidebar a:hover { div.sphinxsidebar a:hover {
border-bottom: 1px solid #999; color: {{ link_hover_color }};
border-bottom: 1px solid {{ link_hover_color }};
} }
div.sphinxsidebar { div.sphinxsidebar {
@ -106,14 +107,14 @@ div.sphinxsidebar h3,
div.sphinxsidebar h4 { div.sphinxsidebar h4 {
font-family: {{ header_font }}; font-family: {{ header_font }};
color: #444; color: #444;
font-size: 24px; font-size: 21px;
font-weight: normal; font-weight: normal;
margin: 0 0 5px 0; margin: 16px 0 0 0;
padding: 0; padding: 0;
} }
div.sphinxsidebar h4 { div.sphinxsidebar h4 {
font-size: 20px; font-size: 18px;
} }
div.sphinxsidebar h3 a { div.sphinxsidebar h3 a {
@ -205,10 +206,22 @@ div.body p, div.body dd, div.body li {
line-height: 1.4em; line-height: 1.4em;
} }
ul.simple li {
margin-bottom: 0.5em;
}
div.topic ul.simple li {
margin-bottom: 0;
}
div.topic li > p:first-child {
margin-top: 0;
margin-bottom: 0;
}
div.admonition { div.admonition {
background: #fafafa; background: #fafafa;
margin: 20px -30px; padding: 10px 20px;
padding: 10px 30px;
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
border-bottom: 1px solid #ccc; border-bottom: 1px solid #ccc;
} }
@ -217,11 +230,6 @@ div.admonition tt.xref, div.admonition a tt {
border-bottom: 1px solid #fafafa; border-bottom: 1px solid #fafafa;
} }
dd div.admonition {
margin-left: -60px;
padding-left: 60px;
}
div.admonition p.admonition-title { div.admonition p.admonition-title {
font-family: {{ header_font }}; font-family: {{ header_font }};
font-weight: normal; font-weight: normal;
@ -231,7 +239,7 @@ div.admonition p.admonition-title {
line-height: 1; line-height: 1;
} }
div.admonition p.last { div.admonition :last-child {
margin-bottom: 0; margin-bottom: 0;
} }
@ -243,7 +251,7 @@ dt:target, .highlight {
background: #FAF3E8; background: #FAF3E8;
} }
div.note { div.note, div.warning {
background-color: #eee; background-color: #eee;
border: 1px solid #ccc; border: 1px solid #ccc;
} }
@ -257,6 +265,11 @@ div.topic {
background-color: #eee; background-color: #eee;
} }
div.topic a {
text-decoration: none;
border-bottom: none;
}
p.admonition-title { p.admonition-title {
display: inline; display: inline;
} }
@ -358,21 +371,10 @@ ul, ol {
pre { pre {
background: #eee; background: #eee;
padding: 7px 30px; padding: 7px 12px;
margin: 15px -30px;
line-height: 1.3em; line-height: 1.3em;
} }
dl pre, blockquote pre, li pre {
margin-left: -60px;
padding-left: 60px;
}
dl dl pre {
margin-left: -90px;
padding-left: 90px;
}
tt { tt {
background-color: #ecf0f3; background-color: #ecf0f3;
color: #222; color: #222;
@ -393,6 +395,20 @@ a.reference:hover {
border-bottom: 1px solid {{ link_hover_color }}; border-bottom: 1px solid {{ link_hover_color }};
} }
li.toctree-l1 a.reference,
li.toctree-l2 a.reference,
li.toctree-l3 a.reference,
li.toctree-l4 a.reference {
border-bottom: none;
}
li.toctree-l1 a.reference:hover,
li.toctree-l2 a.reference:hover,
li.toctree-l3 a.reference:hover,
li.toctree-l4 a.reference:hover {
border-bottom: 1px solid {{ link_hover_color }};
}
a.footnote-reference { a.footnote-reference {
text-decoration: none; text-decoration: none;
font-size: 0.7em; font-size: 0.7em;
@ -408,6 +424,56 @@ a:hover tt {
background: #EEE; background: #EEE;
} }
#reference div.section h2 {
/* separate code elements in the reference section */
border-top: 2px solid #ccc;
padding-top: 0.5em;
}
#reference div.section h3 {
/* separate code elements in the reference section */
border-top: 1px solid #ccc;
padding-top: 0.5em;
}
dl.class, dl.function {
margin-top: 1em;
margin-bottom: 1em;
}
dl.class > dd {
border-left: 3px solid #ccc;
margin-left: 0px;
padding-left: 30px;
}
dl.field-list {
flex-direction: column;
}
dl.field-list dd {
padding-left: 4em;
border-left: 3px solid #ccc;
margin-bottom: 0.5em;
}
dl.field-list dd > ul {
list-style: none;
padding-left: 0px;
}
dl.field-list dd > ul > li li :first-child {
text-indent: 0;
}
dl.field-list dd > ul > li :first-child {
text-indent: -2em;
padding-left: 0px;
}
dl.field-list dd > p:first-child {
text-indent: -2em;
}
@media screen and (max-width: 870px) { @media screen and (max-width: 870px) {

View File

@ -24,11 +24,9 @@ The ideal pytest helper
- feels confident in using pytest (e.g. has explored command line options, knows how to write parametrized tests, has an idea about conftest contents) - feels confident in using pytest (e.g. has explored command line options, knows how to write parametrized tests, has an idea about conftest contents)
- does not need to be an expert in every aspect! - does not need to be an expert in every aspect!
`Pytest helpers, sign up here`_! (preferably in February, hard deadline 22 March) Pytest helpers, sign up here! (preferably in February, hard deadline 22 March)
.. _`Pytest helpers, sign up here`: http://goo.gl/forms/nxqAhqWt1P
The ideal partner project The ideal partner project
----------------------------------------- -----------------------------------------
@ -40,11 +38,9 @@ The ideal partner project
- has the support of the core development team, in trying out pytest adoption - has the support of the core development team, in trying out pytest adoption
- has no tests... or 100% test coverage... or somewhere in between! - has no tests... or 100% test coverage... or somewhere in between!
`Partner projects, sign up here`_! (by 22 March) Partner projects, sign up here! (by 22 March)
.. _`Partner projects, sign up here`: http://goo.gl/forms/ZGyqlHiwk3
What does it mean to "adopt pytest"? What does it mean to "adopt pytest"?
----------------------------------------- -----------------------------------------
@ -68,11 +64,11 @@ Progressive success might look like:
It may be after the month is up, the partner project decides that pytest is not right for it. That's okay - hopefully the pytest team will also learn something about its weaknesses or deficiencies. It may be after the month is up, the partner project decides that pytest is not right for it. That's okay - hopefully the pytest team will also learn something about its weaknesses or deficiencies.
.. _`nose and unittest`: faq.html#how-does-pytest-relate-to-nose-and-unittest .. _`nose and unittest`: faq.html#how-does-pytest-relate-to-nose-and-unittest
.. _assert: asserts.html .. _assert: assert.html
.. _pycmd: https://bitbucket.org/hpk42/pycmd/overview .. _pycmd: https://bitbucket.org/hpk42/pycmd/overview
.. _`setUp/tearDown methods`: xunit_setup.html .. _`setUp/tearDown methods`: xunit_setup.html
.. _fixtures: fixture.html .. _fixtures: fixture.html
.. _markers: markers.html .. _markers: mark.html
.. _distributed: xdist.html .. _distributed: xdist.html

View File

@ -12,7 +12,7 @@ courtesy of Benjamin Peterson. You can now safely use ``assert``
statements in test modules without having to worry about side effects statements in test modules without having to worry about side effects
or python optimization ("-OO") options. This is achieved by rewriting or python optimization ("-OO") options. This is achieved by rewriting
assert statements in test modules upon import, using a PEP302 hook. assert statements in test modules upon import, using a PEP302 hook.
See http://pytest.org/assert.html#advanced-assertion-introspection for See https://docs.pytest.org/en/latest/assert.html for
detailed information. The work has been partly sponsored by my company, detailed information. The work has been partly sponsored by my company,
merlinux GmbH. merlinux GmbH.

View File

@ -75,7 +75,7 @@ The py.test Development Team
**Changes** **Changes**
* **Important**: `py.code <https://pylib.readthedocs.io/en/latest/code.html>`_ has been * **Important**: `py.code <https://pylib.readthedocs.io/en/stable/code.html>`_ has been
merged into the ``pytest`` repository as ``pytest._code``. This decision merged into the ``pytest`` repository as ``pytest._code``. This decision
was made because ``py.code`` had very few uses outside ``pytest`` and the was made because ``py.code`` had very few uses outside ``pytest`` and the
fact that it was in a different repository made it difficult to fix bugs on fact that it was in a different repository made it difficult to fix bugs on
@ -88,7 +88,7 @@ The py.test Development Team
**experimental**, so you definitely should not import it explicitly! **experimental**, so you definitely should not import it explicitly!
Please note that the original ``py.code`` is still available in Please note that the original ``py.code`` is still available in
`pylib <https://pylib.readthedocs.io>`_. `pylib <https://pylib.readthedocs.io/en/stable/>`_.
* ``pytest_enter_pdb`` now optionally receives the pytest config object. * ``pytest_enter_pdb`` now optionally receives the pytest config object.
Thanks `@nicoddemus`_ for the PR. Thanks `@nicoddemus`_ for the PR.

View File

@ -66,8 +66,8 @@ The py.test Development Team
.. _#510: https://github.com/pytest-dev/pytest/issues/510 .. _#510: https://github.com/pytest-dev/pytest/issues/510
.. _#1506: https://github.com/pytest-dev/pytest/pull/1506 .. _#1506: https://github.com/pytest-dev/pytest/pull/1506
.. _#1496: https://github.com/pytest-dev/pytest/issue/1496 .. _#1496: https://github.com/pytest-dev/pytest/issues/1496
.. _#1524: https://github.com/pytest-dev/pytest/issue/1524 .. _#1524: https://github.com/pytest-dev/pytest/pull/1524
.. _@astraw38: https://github.com/astraw38 .. _@astraw38: https://github.com/astraw38
.. _@hackebrot: https://github.com/hackebrot .. _@hackebrot: https://github.com/hackebrot

View File

@ -15,7 +15,6 @@
# #
# The full version, including alpha/beta/rc tags. # The full version, including alpha/beta/rc tags.
# The short X.Y version. # The short X.Y version.
import datetime
import os import os
import sys import sys
@ -63,7 +62,6 @@ master_doc = "contents"
# General information about the project. # General information about the project.
project = "pytest" project = "pytest"
year = datetime.datetime.utcnow().year
copyright = "20152019, holger krekel and pytest-dev team" copyright = "20152019, holger krekel and pytest-dev team"
@ -167,18 +165,18 @@ html_favicon = "img/pytest1favi.ico"
html_sidebars = { html_sidebars = {
"index": [ "index": [
"slim_searchbox.html",
"sidebarintro.html", "sidebarintro.html",
"globaltoc.html", "globaltoc.html",
"links.html", "links.html",
"sourcelink.html", "sourcelink.html",
"searchbox.html",
], ],
"**": [ "**": [
"slim_searchbox.html",
"globaltoc.html", "globaltoc.html",
"relations.html", "relations.html",
"links.html", "links.html",
"sourcelink.html", "sourcelink.html",
"searchbox.html",
], ],
} }

View File

@ -0,0 +1,38 @@
import pytest
# fixtures documentation order example
order = []
@pytest.fixture(scope="session")
def s1():
order.append("s1")
@pytest.fixture(scope="module")
def m1():
order.append("m1")
@pytest.fixture
def f1(f3):
order.append("f1")
@pytest.fixture
def f3():
order.append("f3")
@pytest.fixture(autouse=True)
def a1():
order.append("a1")
@pytest.fixture
def f2():
order.append("f2")
def test_order(f1, m1, f2, s1):
assert order == ["s1", "m1", "a1", "f3", "f1", "f2"]

View File

@ -336,7 +336,7 @@ apply a marker to an individual test instance:
@pytest.mark.foo @pytest.mark.foo
@pytest.mark.parametrize( @pytest.mark.parametrize(
("n", "expected"), [(1, 2), pytest.param((1, 3), marks=pytest.mark.bar), (2, 3)] ("n", "expected"), [(1, 2), pytest.param(1, 3, marks=pytest.mark.bar), (2, 3)]
) )
def test_increment(n, expected): def test_increment(n, expected):
assert n + 1 == expected assert n + 1 == expected

View File

@ -552,13 +552,13 @@ Then run ``pytest`` with verbose mode and with only the ``basic`` marker:
platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python platform linux -- Python 3.x.y, pytest-5.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python
cachedir: $PYTHON_PREFIX/.pytest_cache cachedir: $PYTHON_PREFIX/.pytest_cache
rootdir: $REGENDOC_TMPDIR rootdir: $REGENDOC_TMPDIR
collecting ... collected 17 items / 14 deselected / 3 selected collecting ... collected 18 items / 15 deselected / 3 selected
test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%] test_pytest_param_example.py::test_eval[1+7-8] PASSED [ 33%]
test_pytest_param_example.py::test_eval[basic_2+4] PASSED [ 66%] test_pytest_param_example.py::test_eval[basic_2+4] PASSED [ 66%]
test_pytest_param_example.py::test_eval[basic_6*9] XFAIL [100%] test_pytest_param_example.py::test_eval[basic_6*9] XFAIL [100%]
============ 2 passed, 14 deselected, 1 xfailed in 0.12 seconds ============ ============ 2 passed, 15 deselected, 1 xfailed in 0.12 seconds ============
As the result: As the result:

View File

@ -289,51 +289,29 @@ are finalized when the last test of a *package* finishes.
Use this new feature sparingly and please make sure to report any issues you find. Use this new feature sparingly and please make sure to report any issues you find.
Higher-scoped fixtures are instantiated first Order: Higher-scoped fixtures are instantiated first
--------------------------------------------- ----------------------------------------------------
Within a function request for features, fixture of higher-scopes (such as ``session``) are instantiated first than Within a function request for features, fixture of higher-scopes (such as ``session``) are instantiated first than
lower-scoped fixtures (such as ``function`` or ``class``). The relative order of fixtures of same scope follows lower-scoped fixtures (such as ``function`` or ``class``). The relative order of fixtures of same scope follows
the declared order in the test function and honours dependencies between fixtures. the declared order in the test function and honours dependencies between fixtures. Autouse fixtures will be
instantiated before explicitly used fixtures.
Consider the code below: Consider the code below:
.. code-block:: python .. literalinclude:: example/fixtures/test_fixtures_order.py
@pytest.fixture(scope="session")
def s1():
pass
@pytest.fixture(scope="module")
def m1():
pass
@pytest.fixture
def f1(tmpdir):
pass
@pytest.fixture
def f2():
pass
def test_foo(f1, m1, f2, s1):
...
The fixtures requested by ``test_foo`` will be instantiated in the following order: The fixtures requested by ``test_foo`` will be instantiated in the following order:
1. ``s1``: is the highest-scoped fixture (``session``). 1. ``s1``: is the highest-scoped fixture (``session``).
2. ``m1``: is the second highest-scoped fixture (``module``). 2. ``m1``: is the second highest-scoped fixture (``module``).
3. ``tmpdir``: is a ``function``-scoped fixture, required by ``f1``: it needs to be instantiated at this point 3. ``a1``: is a ``function``-scoped ``autouse`` fixture: it will be instantiated before other fixtures
because it is a dependency of ``f1``. within the same scope.
4. ``f1``: is the first ``function``-scoped fixture in ``test_foo`` parameter list. 4. ``f3``: is a ``function``-scoped fixture, required by ``f1``: it needs to be instantiated at this point
5. ``f2``: is the last ``function``-scoped fixture in ``test_foo`` parameter list. 5. ``f1``: is the first ``function``-scoped fixture in ``test_foo`` parameter list.
6. ``f2``: is the last ``function``-scoped fixture in ``test_foo`` parameter list.
.. _`finalization`: .. _`finalization`:
@ -400,6 +378,34 @@ The ``smtp_connection`` connection will be closed after the test finished
execution because the ``smtp_connection`` object automatically closes when execution because the ``smtp_connection`` object automatically closes when
the ``with`` statement ends. the ``with`` statement ends.
Using the contextlib.ExitStack context manager finalizers will always be called
regardless if the fixture *setup* code raises an exception. This is handy to properly
close all resources created by a fixture even if one of them fails to be created/acquired:
.. code-block:: python
# content of test_yield3.py
import contextlib
import pytest
@contextlib.contextmanager
def connect(port):
... # create connection
yield
... # close connection
@pytest.fixture
def equipments():
with contextlib.ExitStack() as stack:
yield [stack.enter_context(connect(port)) for port in ("C1", "C3", "C28")]
In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still
be properly closed.
Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the
*teardown* code (after the ``yield``) will not be called. *teardown* code (after the ``yield``) will not be called.
@ -428,27 +434,39 @@ Here's the ``smtp_connection`` fixture changed to use ``addfinalizer`` for clean
return smtp_connection # provide the fixture value return smtp_connection # provide the fixture value
Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test Here's the ``equipments`` fixture changed to use ``addfinalizer`` for cleanup:
ends, but ``addfinalizer`` has two key differences over ``yield``:
1. It is possible to register multiple finalizer functions. .. code-block:: python
# content of test_yield3.py
import contextlib
import functools
import pytest
@contextlib.contextmanager
def connect(port):
... # create connection
yield
... # close connection
2. Finalizers will always be called regardless if the fixture *setup* code raises an exception.
This is handy to properly close all resources created by a fixture even if one of them
fails to be created/acquired::
@pytest.fixture @pytest.fixture
def equipments(request): def equipments(request):
r = [] r = []
for port in ('C1', 'C3', 'C28'): for port in ("C1", "C3", "C28"):
equip = connect(port) cm = connect(port)
request.addfinalizer(equip.disconnect) equip = cm.__enter__()
request.addfinalizer(functools.partial(cm.__exit__, None, None, None))
r.append(equip) r.append(equip)
return r return r
In the example above, if ``"C28"`` fails with an exception, ``"C1"`` and ``"C3"`` will still
be properly closed. Of course, if an exception happens before the finalize function is Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test
registered then it will not be executed. ends. Of course, if an exception happens before the finalize function is registered then it
will not be executed.
.. _`request-context`: .. _`request-context`:
@ -522,7 +540,7 @@ of a fixture is needed multiple times in a single test. Instead of returning
data directly, the fixture instead returns a function which generates the data. data directly, the fixture instead returns a function which generates the data.
This function can then be called multiple times in the test. This function can then be called multiple times in the test.
Factories can have have parameters as needed:: Factories can have parameters as needed::
@pytest.fixture @pytest.fixture
def make_customer_record(): def make_customer_record():

View File

@ -122,4 +122,4 @@ Resources
* Google: * Google:
* `Flaky Tests at Google and How We Mitigate Them <https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html>`_ by John Micco, 2016 * `Flaky Tests at Google and How We Mitigate Them <https://testing.googleblog.com/2016/05/flaky-tests-at-google-and-how-we.html>`_ by John Micco, 2016
* `Where do Google's flaky tests come from? <https://docs.google.com/document/d/1mZ0-Kc97DI_F3tf_GBW_NB_aqka-P1jVOsFfufxqUUM/edit#heading=h.ec0r4fypsleh>`_ by Jeff Listfield, 2017 * `Where do Google's flaky tests come from? <https://testing.googleblog.com/2017/04/where-do-our-flaky-tests-come-from.html>`_ by Jeff Listfield, 2017

View File

@ -142,7 +142,7 @@ The first test passed and the second failed. You can easily see the intermediate
Request a unique temporary directory for functional tests Request a unique temporary directory for functional tests
-------------------------------------------------------------- --------------------------------------------------------------
``pytest`` provides `Builtin fixtures/function arguments <https://docs.pytest.org/en/latest/builtin.html#builtinfixtures>`_ to request arbitrary resources, like a unique temporary directory:: ``pytest`` provides `Builtin fixtures/function arguments <https://docs.pytest.org/en/latest/builtin.html>`_ to request arbitrary resources, like a unique temporary directory::
# content of test_tmpdir.py # content of test_tmpdir.py
def test_needsfiles(tmpdir): def test_needsfiles(tmpdir):

View File

@ -61,7 +61,7 @@ Features
- Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box; - Can run :ref:`unittest <unittest>` (including trial) and :ref:`nose <noseintegration>` test suites out of the box;
- Python Python 3.5+ and PyPy 3; - Python 3.5+ and PyPy 3;
- Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community; - Rich plugin architecture, with over 315+ `external plugins <http://plugincompat.herokuapp.com>`_ and thriving community;

View File

@ -9,7 +9,7 @@ Distributed under the terms of the `MIT`_ license, pytest is free and open sourc
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2004-2017 Holger Krekel and others Copyright (c) 2004-2019 Holger Krekel and others
Permission is hereby granted, free of charge, to any person obtaining a copy of Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in this software and associated documentation files (the "Software"), to deal in

View File

@ -14,7 +14,7 @@
.. _`distribute docs`: .. _`distribute docs`:
.. _`distribute`: https://pypi.org/project/distribute/ .. _`distribute`: https://pypi.org/project/distribute/
.. _`pip`: https://pypi.org/project/pip/ .. _`pip`: https://pypi.org/project/pip/
.. _`venv`: https://docs.python.org/3/library/venv.html/ .. _`venv`: https://docs.python.org/3/library/venv.html
.. _`virtualenv`: https://pypi.org/project/virtualenv/ .. _`virtualenv`: https://pypi.org/project/virtualenv/
.. _hudson: http://hudson-ci.org/ .. _hudson: http://hudson-ci.org/
.. _jenkins: http://jenkins-ci.org/ .. _jenkins: http://jenkins-ci.org/

View File

@ -40,7 +40,7 @@ You can register custom marks in your ``pytest.ini`` file like this:
Note that everything after the ``:`` is an optional description. Note that everything after the ``:`` is an optional description.
Alternatively, you can register new markers programatically in a Alternatively, you can register new markers programmatically in a
:ref:`pytest_configure <initialization-hooks>` hook: :ref:`pytest_configure <initialization-hooks>` hook:
.. code-block:: python .. code-block:: python

View File

@ -46,10 +46,13 @@ environment variable is missing, or to set multiple values to a known variable.
:py:meth:`monkeypatch.setenv` and :py:meth:`monkeypatch.delenv` can be used for :py:meth:`monkeypatch.setenv` and :py:meth:`monkeypatch.delenv` can be used for
these patches. these patches.
4. Use :py:meth:`monkeypatch.syspath_prepend` to modify the system ``$PATH`` safely, and 4. Use ``monkeypatch.setenv("PATH", value, prepend=os.pathsep)`` to modify ``$PATH``, and
:py:meth:`monkeypatch.chdir` to change the context of the current working directory :py:meth:`monkeypatch.chdir` to change the context of the current working directory
during a test. during a test.
5. Use py:meth:`monkeypatch.syspath_prepend` to modify ``sys.path`` which will also
call :py:meth:`pkg_resources.fixup_namespace_packages` and :py:meth:`importlib.invalidate_caches`.
See the `monkeypatch blog post`_ for some introduction material See the `monkeypatch blog post`_ for some introduction material
and a discussion of its motivation. and a discussion of its motivation.

View File

@ -28,7 +28,6 @@ Here are some examples of projects using ``pytest`` (please send notes via :ref:
* `sentry <https://getsentry.com/welcome/>`_, realtime app-maintenance and exception tracking * `sentry <https://getsentry.com/welcome/>`_, realtime app-maintenance and exception tracking
* `Astropy <http://www.astropy.org/>`_ and `affiliated packages <http://www.astropy.org/affiliated/index.html>`_ * `Astropy <http://www.astropy.org/>`_ and `affiliated packages <http://www.astropy.org/affiliated/index.html>`_
* `tox <http://testrun.org/tox>`_, virtualenv/Hudson integration tool * `tox <http://testrun.org/tox>`_, virtualenv/Hudson integration tool
* `PIDA <http://pida.co.uk>`_ framework for integrated development
* `PyPM <http://code.activestate.com/pypm/>`_ ActiveState's package manager * `PyPM <http://code.activestate.com/pypm/>`_ ActiveState's package manager
* `Fom <http://packages.python.org/Fom/>`_ a fluid object mapper for FluidDB * `Fom <http://packages.python.org/Fom/>`_ a fluid object mapper for FluidDB
* `applib <https://github.com/ActiveState/applib>`_ cross-platform utilities * `applib <https://github.com/ActiveState/applib>`_ cross-platform utilities
@ -37,8 +36,7 @@ Here are some examples of projects using ``pytest`` (please send notes via :ref:
* `mwlib <https://pypi.org/project/mwlib/>`_ mediawiki parser and utility library * `mwlib <https://pypi.org/project/mwlib/>`_ mediawiki parser and utility library
* `The Translate Toolkit <http://translate.sourceforge.net/wiki/toolkit/index>`_ for localization and conversion * `The Translate Toolkit <http://translate.sourceforge.net/wiki/toolkit/index>`_ for localization and conversion
* `execnet <http://codespeak.net/execnet>`_ rapid multi-Python deployment * `execnet <http://codespeak.net/execnet>`_ rapid multi-Python deployment
* `pylib <https://py.readthedocs.io>`_ cross-platform path, IO, dynamic code library * `pylib <https://pylib.readthedocs.io/en/stable/>`_ cross-platform path, IO, dynamic code library
* `Pacha <http://pacha.cafepais.com/>`_ configuration management in five minutes
* `bbfreeze <https://pypi.org/project/bbfreeze/>`_ create standalone executables from Python scripts * `bbfreeze <https://pypi.org/project/bbfreeze/>`_ create standalone executables from Python scripts
* `pdb++ <http://bitbucket.org/antocuni/pdb>`_ a fancier version of PDB * `pdb++ <http://bitbucket.org/antocuni/pdb>`_ a fancier version of PDB
* `py-s3fuse <http://code.google.com/p/py-s3fuse/>`_ Amazon S3 FUSE based filesystem * `py-s3fuse <http://code.google.com/p/py-s3fuse/>`_ Amazon S3 FUSE based filesystem
@ -77,7 +75,7 @@ Some organisations using pytest
* `Tandberg <http://www.tandberg.com/>`_ * `Tandberg <http://www.tandberg.com/>`_
* `Shootq <http://web.shootq.com/>`_ * `Shootq <http://web.shootq.com/>`_
* `Stups department of Heinrich Heine University Duesseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_ * `Stups department of Heinrich Heine University Duesseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_
* `cellzome <http://www.cellzome.com/>`_ * cellzome
* `Open End, Gothenborg <http://www.openend.se>`_ * `Open End, Gothenborg <http://www.openend.se>`_
* `Laboratory of Bioinformatics, Warsaw <http://genesilico.pl/>`_ * `Laboratory of Bioinformatics, Warsaw <http://genesilico.pl/>`_
* `merlinux, Germany <http://merlinux.eu>`_ * `merlinux, Germany <http://merlinux.eu>`_

View File

@ -1,5 +1,5 @@
Reference API Reference
========= =============
This page contains the full reference to pytest's API. This page contains the full reference to pytest's API.

View File

@ -4,9 +4,8 @@ Talks and Tutorials
.. sidebar:: Next Open Trainings .. sidebar:: Next Open Trainings
- `Training at Europython 2019 <https://ep2019.europython.eu/talks/94WEnsY-introduction-to-pytest/>`_, 8th July 2019, Basel, Switzerland.
- `Training at Workshoptage 2019 <https://workshoptage.ch/workshops/2019/test-driven-development-fuer-python-mit-pytest/>`_ (German), 10th September 2019, Rapperswil, Switzerland. - `Training at Workshoptage 2019 <https://workshoptage.ch/workshops/2019/test-driven-development-fuer-python-mit-pytest/>`_ (German), 10th September 2019, Rapperswil, Switzerland.
- `3 day hands-on workshop covering pytest, tox and devpi: "Professional Testing with Python" <https://python-academy.com/courses/specialtopics/python_course_testing.html>`_ (English), October 21 - 23, 2019, Leipzig, Germany.
.. _`funcargs`: funcargs.html .. _`funcargs`: funcargs.html

View File

@ -127,7 +127,7 @@ decorator or to all tests in a module by setting the ``pytestmark`` variable:
*Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_ *Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_
*plugin.* *plugin.*
.. _`-W option`: https://docs.python.org/3/using/cmdline.html?highlight=#cmdoption-W .. _`-W option`: https://docs.python.org/3/using/cmdline.html#cmdoption-w
.. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter .. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter
.. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings .. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings

View File

@ -164,7 +164,7 @@ If a package is installed this way, ``pytest`` will load
.. note:: .. note::
Make sure to include ``Framework :: Pytest`` in your list of Make sure to include ``Framework :: Pytest`` in your list of
`PyPI classifiers <https://python-packaging-user-guide.readthedocs.io/distributing/#classifiers>`_ `PyPI classifiers <https://pypi.org/classifiers/>`_
to make it easy for users to find your plugin. to make it easy for users to find your plugin.

View File

@ -14,7 +14,7 @@ import py
import pytest import pytest
from .pathlib import Path from .pathlib import Path
from .pathlib import resolve_from_str from .pathlib import resolve_from_str
from .pathlib import rmtree from .pathlib import rm_rf
README_CONTENT = """\ README_CONTENT = """\
# pytest cache directory # # pytest cache directory #
@ -44,7 +44,7 @@ class Cache:
def for_config(cls, config): def for_config(cls, config):
cachedir = cls.cache_dir_from_config(config) cachedir = cls.cache_dir_from_config(config)
if config.getoption("cacheclear") and cachedir.exists(): if config.getoption("cacheclear") and cachedir.exists():
rmtree(cachedir, force=True) rm_rf(cachedir)
cachedir.mkdir() cachedir.mkdir()
return cls(cachedir, config) return cls(cachedir, config)

View File

@ -70,13 +70,18 @@ def num_mock_patch_args(function):
patchings = getattr(function, "patchings", None) patchings = getattr(function, "patchings", None)
if not patchings: if not patchings:
return 0 return 0
mock_modules = [sys.modules.get("mock"), sys.modules.get("unittest.mock")]
if any(mock_modules): mock_sentinel = getattr(sys.modules.get("mock"), "DEFAULT", object())
sentinels = [m.DEFAULT for m in mock_modules if m is not None] ut_mock_sentinel = getattr(sys.modules.get("unittest.mock"), "DEFAULT", object())
return len( return len(
[p for p in patchings if not p.attribute_name and p.new in sentinels] [
p
for p in patchings
if not p.attribute_name
and (p.new is mock_sentinel or p.new is ut_mock_sentinel)
]
) )
return len(patchings)
def getfuncargnames(function, is_method=False, cls=None): def getfuncargnames(function, is_method=False, cls=None):

View File

@ -399,7 +399,7 @@ class DropShorterLongHelpFormatter(argparse.HelpFormatter):
"""shorten help for long options that differ only in extra hyphens """shorten help for long options that differ only in extra hyphens
- collapse **long** options that are the same except for extra hyphens - collapse **long** options that are the same except for extra hyphens
- special action attribute map_long_option allows surpressing additional - special action attribute map_long_option allows suppressing additional
long options long options
- shortcut if there are only two options and one of them is a short one - shortcut if there are only two options and one of them is a short one
- cache result on action object as this is called at least 2 times - cache result on action object as this is called at least 2 times

View File

@ -30,7 +30,11 @@ def getcfg(args, config=None):
for inibasename in inibasenames: for inibasename in inibasenames:
p = base.join(inibasename) p = base.join(inibasename)
if exists(p): if exists(p):
try:
iniconfig = py.iniconfig.IniConfig(p) iniconfig = py.iniconfig.IniConfig(p)
except py.iniconfig.ParseError as exc:
raise UsageError(str(exc))
if ( if (
inibasename == "setup.cfg" inibasename == "setup.cfg"
and "tool:pytest" in iniconfig.sections and "tool:pytest" in iniconfig.sections

View File

@ -171,9 +171,7 @@ class Mark:
@attr.s @attr.s
class MarkDecorator: class MarkDecorator:
""" A decorator for test functions and test classes. When applied """ A decorator for test functions and test classes. When applied
it will create :class:`MarkInfo` objects which may be it will create :class:`Mark` objects which are often created like this::
:ref:`retrieved by hooks as item keywords <excontrolskip>`.
MarkDecorator instances are often created like this::
mark1 = pytest.mark.NAME # simple MarkDecorator mark1 = pytest.mark.NAME # simple MarkDecorator
mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator mark2 = pytest.mark.NAME(name1=value) # parametrized MarkDecorator
@ -185,6 +183,7 @@ class MarkDecorator:
pass pass
When a MarkDecorator instance is called it does the following: When a MarkDecorator instance is called it does the following:
1. If called with a single class as its only positional argument and no 1. If called with a single class as its only positional argument and no
additional keyword arguments, it attaches itself to the class so it additional keyword arguments, it attaches itself to the class so it
gets applied automatically to all test cases found in that class. gets applied automatically to all test cases found in that class.

View File

@ -1,5 +1,6 @@
import os import os
import warnings import warnings
from functools import lru_cache
import py import py
@ -13,6 +14,7 @@ SEP = "/"
tracebackcutdir = py.path.local(_pytest.__file__).dirpath() tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
@lru_cache(maxsize=None)
def _splitnode(nodeid): def _splitnode(nodeid):
"""Split a nodeid into constituent 'parts'. """Split a nodeid into constituent 'parts'.
@ -30,11 +32,12 @@ def _splitnode(nodeid):
""" """
if nodeid == "": if nodeid == "":
# If there is no root node at all, return an empty list so the caller's logic can remain sane # If there is no root node at all, return an empty list so the caller's logic can remain sane
return [] return ()
parts = nodeid.split(SEP) parts = nodeid.split(SEP)
# Replace single last element 'test_foo.py::Bar' with multiple elements 'test_foo.py', 'Bar' # Replace single last element 'test_foo.py::Bar' with multiple elements 'test_foo.py', 'Bar'
parts[-1:] = parts[-1].split("::") parts[-1:] = parts[-1].split("::")
return parts # Convert parts into a tuple to avoid possible errors with caching of a mutable type
return tuple(parts)
def ischildnode(baseid, nodeid): def ischildnode(baseid, nodeid):

View File

@ -18,6 +18,12 @@ class OutcomeException(BaseException):
""" """
def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None: def __init__(self, msg: Optional[str] = None, pytrace: bool = True) -> None:
if msg is not None and not isinstance(msg, str):
error_msg = (
"{} expected string as 'msg' parameter, got '{}' instead.\n"
"Perhaps you meant to use a mark?"
)
raise TypeError(error_msg.format(type(self).__name__, type(msg).__name__))
BaseException.__init__(self, msg) BaseException.__init__(self, msg)
self.msg = msg self.msg = msg
self.pytrace = pytrace self.pytrace = pytrace

View File

@ -7,12 +7,15 @@ import os
import shutil import shutil
import sys import sys
import uuid import uuid
import warnings
from functools import partial
from os.path import expanduser from os.path import expanduser
from os.path import expandvars from os.path import expandvars
from os.path import isabs from os.path import isabs
from os.path import sep from os.path import sep
from posixpath import sep as posix_sep from posixpath import sep as posix_sep
from _pytest.warning_types import PytestWarning
if sys.version_info[:2] >= (3, 6): if sys.version_info[:2] >= (3, 6):
from pathlib import Path, PurePath from pathlib import Path, PurePath
@ -32,17 +35,53 @@ def ensure_reset_dir(path):
ensures the given path is an empty directory ensures the given path is an empty directory
""" """
if path.exists(): if path.exists():
rmtree(path, force=True) rm_rf(path)
path.mkdir() path.mkdir()
def rmtree(path, force=False): def on_rm_rf_error(func, path: str, exc, *, start_path):
if force: """Handles known read-only errors during rmtree."""
# NOTE: ignore_errors might leave dead folders around. excvalue = exc[1]
# Python needs a rm -rf as a followup.
shutil.rmtree(str(path), ignore_errors=True) if not isinstance(excvalue, PermissionError):
else: warnings.warn(
shutil.rmtree(str(path)) PytestWarning("(rm_rf) error removing {}: {}".format(path, excvalue))
)
return
if func not in (os.rmdir, os.remove, os.unlink):
warnings.warn(
PytestWarning("(rm_rf) error removing {}: {}".format(path, excvalue))
)
return
# Chmod + retry.
import stat
def chmod_rw(p: str):
mode = os.stat(p).st_mode
os.chmod(p, mode | stat.S_IRUSR | stat.S_IWUSR)
# For files, we need to recursively go upwards in the directories to
# ensure they all are also writable.
p = Path(path)
if p.is_file():
for parent in p.parents:
chmod_rw(str(parent))
# stop when we reach the original path passed to rm_rf
if parent == start_path:
break
chmod_rw(str(path))
func(path)
def rm_rf(path: Path):
"""Remove the path contents recursively, even if some elements
are read-only.
"""
onerror = partial(on_rm_rf_error, start_path=path)
shutil.rmtree(str(path), onerror=onerror)
def find_prefixed(root, prefix): def find_prefixed(root, prefix):
@ -168,7 +207,7 @@ def maybe_delete_a_numbered_dir(path):
garbage = parent.joinpath("garbage-{}".format(uuid.uuid4())) garbage = parent.joinpath("garbage-{}".format(uuid.uuid4()))
path.rename(garbage) path.rename(garbage)
rmtree(garbage, force=True) rm_rf(garbage)
except (OSError, EnvironmentError): except (OSError, EnvironmentError):
# known races: # known races:
# * other process did a cleanup at the same time # * other process did a cleanup at the same time

View File

@ -71,7 +71,6 @@ class StepwisePlugin:
config.hook.pytest_deselected(items=already_passed) config.hook.pytest_deselected(items=already_passed)
def pytest_runtest_logreport(self, report): def pytest_runtest_logreport(self, report):
# Skip this hook if plugin is not active or the test is xfailed.
if not self.active: if not self.active:
return return

View File

@ -692,7 +692,7 @@ class TerminalReporter:
else: else:
excrepr.reprcrash.toterminal(self._tw) excrepr.reprcrash.toterminal(self._tw)
self._tw.line( self._tw.line(
"(to show a full traceback on KeyboardInterrupt use --fulltrace)", "(to show a full traceback on KeyboardInterrupt use --full-trace)",
yellow=True, yellow=True,
) )

View File

@ -6,6 +6,7 @@ import _pytest._code
import pytest import pytest
from _pytest.compat import getimfunc from _pytest.compat import getimfunc
from _pytest.config import hookimpl from _pytest.config import hookimpl
from _pytest.outcomes import exit
from _pytest.outcomes import fail from _pytest.outcomes import fail
from _pytest.outcomes import skip from _pytest.outcomes import skip
from _pytest.outcomes import xfail from _pytest.outcomes import xfail
@ -154,6 +155,11 @@ class TestCaseFunction(Function):
self.__dict__.setdefault("_excinfo", []).append(excinfo) self.__dict__.setdefault("_excinfo", []).append(excinfo)
def addError(self, testcase, rawexcinfo): def addError(self, testcase, rawexcinfo):
try:
if isinstance(rawexcinfo[1], exit.Exception):
exit(rawexcinfo[1].msg)
except TypeError:
pass
self._addexcinfo(rawexcinfo) self._addexcinfo(rawexcinfo)
def addFailure(self, testcase, rawexcinfo): def addFailure(self, testcase, rawexcinfo):

View File

@ -378,7 +378,7 @@ def test_excinfo_no_python_sourcecode(tmpdir):
excinfo = pytest.raises(ValueError, template.render, h=h) excinfo = pytest.raises(ValueError, template.render, h=h)
for item in excinfo.traceback: for item in excinfo.traceback:
print(item) # XXX: for some reason jinja.Template.render is printed in full print(item) # XXX: for some reason jinja.Template.render is printed in full
item.source # shouldnt fail item.source # shouldn't fail
if item.path.basename == "test.txt": if item.path.basename == "test.txt":
assert str(item.source) == "{{ h()}}:" assert str(item.source) == "{{ h()}}:"

View File

@ -36,7 +36,7 @@ def test_terminal_reporter_writer_attr(pytestconfig):
assert terminal_reporter.writer is terminal_reporter._tw assert terminal_reporter.writer is terminal_reporter._tw
@pytest.mark.parametrize("plugin", deprecated.DEPRECATED_EXTERNAL_PLUGINS) @pytest.mark.parametrize("plugin", sorted(deprecated.DEPRECATED_EXTERNAL_PLUGINS))
@pytest.mark.filterwarnings("default") @pytest.mark.filterwarnings("default")
def test_external_plugins_integrated(testdir, plugin): def test_external_plugins_integrated(testdir, plugin):
testdir.syspathinsert() testdir.syspathinsert()

View File

@ -104,21 +104,15 @@ class TestMockDecoration:
values = getfuncargnames(f) values = getfuncargnames(f)
assert values == ("x",) assert values == ("x",)
@pytest.mark.xfail( def test_getfuncargnames_patching(self):
strict=False, reason="getfuncargnames breaks if mock is imported"
)
def test_wrapped_getfuncargnames_patching(self):
from _pytest.compat import getfuncargnames from _pytest.compat import getfuncargnames
from unittest.mock import patch
def wrap(f): class T:
def func(): def original(self, x, y, z):
pass pass
func.__wrapped__ = f @patch.object(T, "original")
func.patchings = ["qwe"]
return func
@wrap
def f(x, y, z): def f(x, y, z):
pass pass
@ -126,7 +120,6 @@ class TestMockDecoration:
assert values == ("y", "z") assert values == ("y", "z")
def test_unittest_mock(self, testdir): def test_unittest_mock(self, testdir):
pytest.importorskip("unittest.mock")
testdir.makepyfile( testdir.makepyfile(
""" """
import unittest.mock import unittest.mock
@ -142,7 +135,6 @@ class TestMockDecoration:
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
def test_unittest_mock_and_fixture(self, testdir): def test_unittest_mock_and_fixture(self, testdir):
pytest.importorskip("unittest.mock")
testdir.makepyfile( testdir.makepyfile(
""" """
import os.path import os.path
@ -164,7 +156,6 @@ class TestMockDecoration:
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
def test_unittest_mock_and_pypi_mock(self, testdir): def test_unittest_mock_and_pypi_mock(self, testdir):
pytest.importorskip("unittest.mock")
pytest.importorskip("mock", "1.0.1") pytest.importorskip("mock", "1.0.1")
testdir.makepyfile( testdir.makepyfile(
""" """
@ -187,6 +178,34 @@ class TestMockDecoration:
reprec = testdir.inline_run() reprec = testdir.inline_run()
reprec.assertoutcome(passed=2) reprec.assertoutcome(passed=2)
def test_mock_sentinel_check_against_numpy_like(self, testdir):
"""Ensure our function that detects mock arguments compares against sentinels using
identity to circumvent objects which can't be compared with equality against others
in a truth context, like with numpy arrays (#5606).
"""
testdir.makepyfile(
dummy="""
class NumpyLike:
def __init__(self, value):
self.value = value
def __eq__(self, other):
raise ValueError("like numpy, cannot compare against others for truth")
FOO = NumpyLike(10)
"""
)
testdir.makepyfile(
"""
from unittest.mock import patch
import dummy
class Test(object):
@patch("dummy.FOO", new=dummy.NumpyLike(50))
def test_hello(self):
assert dummy.FOO.value == 50
"""
)
reprec = testdir.inline_run()
reprec.assertoutcome(passed=1)
def test_mock(self, testdir): def test_mock(self, testdir):
pytest.importorskip("mock", "1.0.1") pytest.importorskip("mock", "1.0.1")
testdir.makepyfile( testdir.makepyfile(

View File

@ -123,6 +123,12 @@ class TestParseIni:
config = testdir.parseconfigure(sub) config = testdir.parseconfigure(sub)
assert config.getini("minversion") == "2.0" assert config.getini("minversion") == "2.0"
def test_ini_parse_error(self, testdir):
testdir.tmpdir.join("pytest.ini").write("addopts = -x")
result = testdir.runpytest()
assert result.ret != 0
result.stderr.fnmatch_lines(["ERROR: *pytest.ini:1: no section header defined"])
@pytest.mark.xfail(reason="probably not needed") @pytest.mark.xfail(reason="probably not needed")
def test_confcutdir(self, testdir): def test_confcutdir(self, testdir):
sub = testdir.mkdir("sub") sub = testdir.mkdir("sub")

View File

@ -592,7 +592,7 @@ class TestPython:
assert "hx" in fnode.toxml() assert "hx" in fnode.toxml()
def test_assertion_binchars(self, testdir): def test_assertion_binchars(self, testdir):
"""this test did fail when the escaping wasnt strict""" """this test did fail when the escaping wasn't strict"""
testdir.makepyfile( testdir.makepyfile(
""" """
@ -715,7 +715,7 @@ def test_dont_configure_on_slaves(tmpdir):
return "pytest" return "pytest"
junitprefix = None junitprefix = None
# XXX: shouldnt need tmpdir ? # XXX: shouldn't need tmpdir ?
xmlpath = str(tmpdir.join("junix.xml")) xmlpath = str(tmpdir.join("junix.xml"))
register = gotten.append register = gotten.append

View File

@ -430,7 +430,7 @@ class TestFunctional:
def test_b(self): def test_b(self):
assert True assert True
class TestC(object): class TestC(object):
# this one didnt get marked # this one didn't get marked
def test_d(self): def test_d(self):
assert True assert True
""" """

View File

@ -253,7 +253,7 @@ def test_apiwrapper_problem_issue260(testdir):
def test_setup_teardown_linking_issue265(testdir): def test_setup_teardown_linking_issue265(testdir):
# we accidentally didnt integrate nose setupstate with normal setupstate # we accidentally didn't integrate nose setupstate with normal setupstate
# this test ensures that won't happen again # this test ensures that won't happen again
testdir.makepyfile( testdir.makepyfile(
''' '''

View File

@ -230,8 +230,8 @@ class TestInlineRunModulesCleanup:
): ):
spy_factory = self.spy_factory() spy_factory = self.spy_factory()
monkeypatch.setattr(pytester, "SysModulesSnapshot", spy_factory) monkeypatch.setattr(pytester, "SysModulesSnapshot", spy_factory)
original = dict(sys.modules)
testdir.syspathinsert() testdir.syspathinsert()
original = dict(sys.modules)
testdir.makepyfile(import1="# you son of a silly person") testdir.makepyfile(import1="# you son of a silly person")
testdir.makepyfile(import2="# my hovercraft is full of eels") testdir.makepyfile(import2="# my hovercraft is full of eels")
test_mod = testdir.makepyfile( test_mod = testdir.makepyfile(

View File

@ -11,6 +11,7 @@ from _pytest import main
from _pytest import outcomes from _pytest import outcomes
from _pytest import reports from _pytest import reports
from _pytest import runner from _pytest import runner
from _pytest.outcomes import OutcomeException
class TestSetupState: class TestSetupState:
@ -990,3 +991,18 @@ class TestReportContents:
rep = reports[1] rep = reports[1]
assert rep.capstdout == "" assert rep.capstdout == ""
assert rep.capstderr == "" assert rep.capstderr == ""
def test_outcome_exception_bad_msg():
"""Check that OutcomeExceptions validate their input to prevent confusing errors (#5578)"""
def func():
pass
expected = (
"OutcomeException expected string as 'msg' parameter, got 'function' instead.\n"
"Perhaps you meant to use a mark?"
)
with pytest.raises(TypeError) as excinfo:
OutcomeException(func)
assert str(excinfo.value) == expected

View File

@ -1066,7 +1066,8 @@ def test_module_level_skip_error(testdir):
testdir.makepyfile( testdir.makepyfile(
""" """
import pytest import pytest
@pytest.skip pytest.skip("skip_module_level")
def test_func(): def test_func():
assert True assert True
""" """

View File

@ -207,6 +207,7 @@ def test_xfail_handling(testdir):
# because we are writing to the same file, mtime might not be affected enough to # because we are writing to the same file, mtime might not be affected enough to
# invalidate the cache, making this next run flaky # invalidate the cache, making this next run flaky
if testdir.tmpdir.join("__pycache__").exists():
testdir.tmpdir.join("__pycache__").remove() testdir.tmpdir.join("__pycache__").remove()
testdir.makepyfile(contents.format(assert_value="0", strict="True")) testdir.makepyfile(contents.format(assert_value="0", strict="True"))
result = testdir.runpytest("--sw", "-v") result = testdir.runpytest("--sw", "-v")

View File

@ -233,7 +233,7 @@ class TestTerminal:
) )
else: else:
result.stdout.fnmatch_lines( result.stdout.fnmatch_lines(
["(to show a full traceback on KeyboardInterrupt use --fulltrace)"] ["(to show a full traceback on KeyboardInterrupt use --full-trace)"]
) )
result.stdout.fnmatch_lines(["*KeyboardInterrupt*"]) result.stdout.fnmatch_lines(["*KeyboardInterrupt*"])

View File

@ -1,3 +1,5 @@
import os
import stat
import sys import sys
import attr import attr
@ -303,22 +305,6 @@ class TestNumberedDir:
p, consider_lock_dead_if_created_before=p.stat().st_mtime + 1 p, consider_lock_dead_if_created_before=p.stat().st_mtime + 1
) )
def test_rmtree(self, tmp_path):
from _pytest.pathlib import rmtree
adir = tmp_path / "adir"
adir.mkdir()
rmtree(adir)
assert not adir.exists()
adir.mkdir()
afile = adir / "afile"
afile.write_bytes(b"aa")
rmtree(adir, force=True)
assert not adir.exists()
def test_cleanup_ignores_symlink(self, tmp_path): def test_cleanup_ignores_symlink(self, tmp_path):
the_symlink = tmp_path / (self.PREFIX + "current") the_symlink = tmp_path / (self.PREFIX + "current")
attempt_symlink_to(the_symlink, tmp_path / (self.PREFIX + "5")) attempt_symlink_to(the_symlink, tmp_path / (self.PREFIX + "5"))
@ -331,6 +317,83 @@ class TestNumberedDir:
assert folder.is_dir() assert folder.is_dir()
class TestRmRf:
def test_rm_rf(self, tmp_path):
from _pytest.pathlib import rm_rf
adir = tmp_path / "adir"
adir.mkdir()
rm_rf(adir)
assert not adir.exists()
adir.mkdir()
afile = adir / "afile"
afile.write_bytes(b"aa")
rm_rf(adir)
assert not adir.exists()
def test_rm_rf_with_read_only_file(self, tmp_path):
"""Ensure rm_rf can remove directories with read-only files in them (#5524)"""
from _pytest.pathlib import rm_rf
fn = tmp_path / "dir/foo.txt"
fn.parent.mkdir()
fn.touch()
self.chmod_r(fn)
rm_rf(fn.parent)
assert not fn.parent.is_dir()
def chmod_r(self, path):
mode = os.stat(str(path)).st_mode
os.chmod(str(path), mode & ~stat.S_IWRITE)
def test_rm_rf_with_read_only_directory(self, tmp_path):
"""Ensure rm_rf can remove read-only directories (#5524)"""
from _pytest.pathlib import rm_rf
adir = tmp_path / "dir"
adir.mkdir()
(adir / "foo.txt").touch()
self.chmod_r(adir)
rm_rf(adir)
assert not adir.is_dir()
def test_on_rm_rf_error(self, tmp_path):
from _pytest.pathlib import on_rm_rf_error
adir = tmp_path / "dir"
adir.mkdir()
fn = adir / "foo.txt"
fn.touch()
self.chmod_r(fn)
# unknown exception
with pytest.warns(pytest.PytestWarning):
exc_info = (None, RuntimeError(), None)
on_rm_rf_error(os.unlink, str(fn), exc_info, start_path=tmp_path)
assert fn.is_file()
# unknown function
with pytest.warns(pytest.PytestWarning):
exc_info = (None, PermissionError(), None)
on_rm_rf_error(None, str(fn), exc_info, start_path=tmp_path)
assert fn.is_file()
exc_info = (None, PermissionError(), None)
on_rm_rf_error(os.unlink, str(fn), exc_info, start_path=tmp_path)
assert not fn.is_file()
def attempt_symlink_to(path, to_path): def attempt_symlink_to(path, to_path):
"""Try to make a symlink from "path" to "to_path", skipping in case this platform """Try to make a symlink from "path" to "to_path", skipping in case this platform
does not support it or we don't have sufficient privileges (common on Windows).""" does not support it or we don't have sufficient privileges (common on Windows)."""
@ -342,3 +405,24 @@ def attempt_symlink_to(path, to_path):
def test_tmpdir_equals_tmp_path(tmpdir, tmp_path): def test_tmpdir_equals_tmp_path(tmpdir, tmp_path):
assert Path(tmpdir) == tmp_path assert Path(tmpdir) == tmp_path
def test_basetemp_with_read_only_files(testdir):
"""Integration test for #5524"""
testdir.makepyfile(
"""
import os
import stat
def test(tmp_path):
fn = tmp_path / 'foo.txt'
fn.write_text('hello')
mode = os.stat(str(fn)).st_mode
os.chmod(str(fn), mode & ~stat.S_IREAD)
"""
)
result = testdir.runpytest("--basetemp=tmp")
assert result.ret == 0
# running a second time and ensure we don't crash
result = testdir.runpytest("--basetemp=tmp")
assert result.ret == 0

View File

@ -1048,3 +1048,39 @@ def test_setup_inheritance_skipping(testdir, test_name, expected_outcome):
testdir.copy_example("unittest/{}".format(test_name)) testdir.copy_example("unittest/{}".format(test_name))
result = testdir.runpytest() result = testdir.runpytest()
result.stdout.fnmatch_lines(["* {} in *".format(expected_outcome)]) result.stdout.fnmatch_lines(["* {} in *".format(expected_outcome)])
def test_BdbQuit(testdir):
testdir.makepyfile(
test_foo="""
import unittest
class MyTestCase(unittest.TestCase):
def test_bdbquit(self):
import bdb
raise bdb.BdbQuit()
def test_should_not_run(self):
pass
"""
)
reprec = testdir.inline_run()
reprec.assertoutcome(failed=1, passed=1)
def test_exit_outcome(testdir):
testdir.makepyfile(
test_foo="""
import pytest
import unittest
class MyTestCase(unittest.TestCase):
def test_exit_outcome(self):
pytest.exit("pytest_exit called")
def test_should_not_run(self):
pass
"""
)
result = testdir.runpytest()
result.stdout.fnmatch_lines(["*Exit: pytest_exit called*", "*= no tests ran in *"])

View File

@ -127,11 +127,10 @@ norecursedirs = testing/example_scripts
xfail_strict=true xfail_strict=true
filterwarnings = filterwarnings =
error error
default:Using or importing the ABCs:DeprecationWarning:unittest2.*
ignore:Module already imported so cannot be rewritten:pytest.PytestWarning ignore:Module already imported so cannot be rewritten:pytest.PytestWarning
# produced by path.local # produced by python3.6/site.py itself (3.6.7 on Travis, could not trigger it with 3.6.8).
ignore:bad escape.*:DeprecationWarning:re ignore:.*U.*mode is deprecated:DeprecationWarning:(?!(pytest|_pytest))
# produced by path.readlines
ignore:.*U.*mode is deprecated:DeprecationWarning
# produced by pytest-xdist # produced by pytest-xdist
ignore:.*type argument to addoption.*:DeprecationWarning ignore:.*type argument to addoption.*:DeprecationWarning
# produced by python >=3.5 on execnet (pytest-xdist) # produced by python >=3.5 on execnet (pytest-xdist)
@ -139,6 +138,8 @@ filterwarnings =
# pytest's own futurewarnings # pytest's own futurewarnings
ignore::pytest.PytestExperimentalApiWarning ignore::pytest.PytestExperimentalApiWarning
# Do not cause SyntaxError for invalid escape sequences in py37. # Do not cause SyntaxError for invalid escape sequences in py37.
# Those are caught/handled by pyupgrade, and not easy to filter with the
# module being the filename (with .py removed).
default:invalid escape sequence:DeprecationWarning default:invalid escape sequence:DeprecationWarning
# ignore use of unregistered marks, because we use many to test the implementation # ignore use of unregistered marks, because we use many to test the implementation
ignore::_pytest.warning_types.PytestUnknownMarkWarning ignore::_pytest.warning_types.PytestUnknownMarkWarning