Merge pull request #5687 from blueyed/merge-master
Merge master into features
This commit is contained in:
		
						commit
						29e336bd9b
					
				|  | @ -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: | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								AUTHORS
								
								
								
								
							
							
						
						
									
										2
									
								
								AUTHORS
								
								
								
								
							|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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: | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | Cache node splitting function which can improve collection performance in very large test suites. | ||||||
|  | @ -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. | ||||||
|  | @ -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``). | ||||||
|  | @ -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. | ||||||
|  | @ -0,0 +1,2 @@ | ||||||
|  | ``pytest.exit`` is now correctly handled in ``unittest`` cases. | ||||||
|  | This makes ``unittest`` cases handle ``quit`` from pytest's pdb correctly. | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | Improved output when parsing an ini configuration file fails. | ||||||
|  | @ -0,0 +1,2 @@ | ||||||
|  | When invoking pytest's own testsuite with ``PYTHONDONTWRITEBYTECODE=1``, | ||||||
|  | the ``test_xfail_handling`` test no longer fails. | ||||||
|  | @ -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> | ||||||
|  |  | ||||||
|  | @ -16,7 +16,7 @@ | ||||||
| {%- block footer %} | {%- block footer %} | ||||||
|   <div class="footer"> |   <div class="footer"> | ||||||
|     © Copyright {{ copyright }}. |     © 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> | ||||||
|  |  | ||||||
|  | @ -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 %} | ||||||
|  | @ -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) { | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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,8 +62,7 @@ master_doc = "contents" | ||||||
| 
 | 
 | ||||||
| # General information about the project. | # General information about the project. | ||||||
| project = "pytest" | project = "pytest" | ||||||
| year = datetime.datetime.utcnow().year | copyright = "2015–2019, holger krekel and pytest-dev team" | ||||||
| copyright = "2015–2019 , holger krekel and pytest-dev team" |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| # The language for content autogenerated by Sphinx. Refer to documentation | # The language for content autogenerated by Sphinx. Refer to documentation | ||||||
|  | @ -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", |  | ||||||
|     ], |     ], | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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"] | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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(): | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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): | ||||||
|  |  | ||||||
|  | @ -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; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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/ | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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>`_ | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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): | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
|  |  | ||||||
|  | @ -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): | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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): | ||||||
|  | @ -82,9 +121,9 @@ def _force_symlink(root, target, link_to): | ||||||
|     """helper to create the current symlink |     """helper to create the current symlink | ||||||
| 
 | 
 | ||||||
|     it's full of race conditions that are reasonably ok to ignore |     it's full of race conditions that are reasonably ok to ignore | ||||||
|     for the context of best effort linking to the latest testrun |     for the context of best effort linking to the latest test run | ||||||
| 
 | 
 | ||||||
|     the presumption being thatin case of much parallelism |     the presumption being that in case of much parallelism | ||||||
|     the inaccuracy is going to be acceptable |     the inaccuracy is going to be acceptable | ||||||
|     """ |     """ | ||||||
|     current_symlink = root.joinpath(target) |     current_symlink = root.joinpath(target) | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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, | ||||||
|                 ) |                 ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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): | ||||||
|  |  | ||||||
|  | @ -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()}}:" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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() | ||||||
|  |  | ||||||
|  | @ -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( | ||||||
|  |  | ||||||
|  | @ -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") | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|  | @ -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( | ||||||
|         ''' |         ''' | ||||||
|  |  | ||||||
|  | @ -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( | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|     """ |     """ | ||||||
|  |  | ||||||
|  | @ -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") | ||||||
|  |  | ||||||
|  | @ -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*"]) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 *"]) | ||||||
|  |  | ||||||
							
								
								
									
										9
									
								
								tox.ini
								
								
								
								
							
							
						
						
									
										9
									
								
								tox.ini
								
								
								
								
							|  | @ -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 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue