Compare commits
101 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b63f6770a1 | ||
|
|
8a8687122d | ||
|
|
7277fbdb20 | ||
|
|
6908d93ba1 | ||
|
|
c578418791 | ||
|
|
16df4da1f7 | ||
|
|
6e2b5a3f1b | ||
|
|
b3bf7fc496 | ||
|
|
bb659fcffe | ||
|
|
6de19ab7ba | ||
|
|
22b7701431 | ||
|
|
ff8dbd0ad8 | ||
|
|
5e832017d5 | ||
|
|
4a62102b57 | ||
|
|
f2ba8d70b9 | ||
|
|
afe847ecdc | ||
|
|
8c3c4307db | ||
|
|
731c35fcab | ||
|
|
31b971d79d | ||
|
|
4e57a39067 | ||
|
|
af0344e940 | ||
|
|
97367cf773 | ||
|
|
336cf3e1f5 | ||
|
|
4e4ebbef5a | ||
|
|
b09d60c60a | ||
|
|
0908f40e43 | ||
|
|
0e73724e58 | ||
|
|
9970dea8c1 | ||
|
|
218af42325 | ||
|
|
6fa7b16482 | ||
|
|
4a992bafdb | ||
|
|
21137cf8c5 | ||
|
|
5a856b6e29 | ||
|
|
f0541b685b | ||
|
|
536f1723ac | ||
|
|
8bb589fc5d | ||
|
|
b2d7c26d80 | ||
|
|
7cbf265bb5 | ||
|
|
917b9a8352 | ||
|
|
2127a2378a | ||
|
|
d2db6626cf | ||
|
|
620ba5971f | ||
|
|
57e2ced969 | ||
|
|
80944e32ad | ||
|
|
54a90e9555 | ||
|
|
9d41eaedbf | ||
|
|
46d157fe07 | ||
|
|
87e4a28351 | ||
|
|
5ee9793c99 | ||
|
|
1863b7c7b2 | ||
|
|
01ed6dfc3b | ||
|
|
59b3693988 | ||
|
|
05796be21a | ||
|
|
f826b23f58 | ||
|
|
9abff7f72f | ||
|
|
f74f14f038 | ||
|
|
bcbad5b1af | ||
|
|
5d785e415e | ||
|
|
409d2f1d54 | ||
|
|
cca4de20cf | ||
|
|
c98ad2a0a0 | ||
|
|
5de203195c | ||
|
|
021e843427 | ||
|
|
ac9c8fcdab | ||
|
|
3871810d1c | ||
|
|
281fcd5a58 | ||
|
|
2fd7626046 | ||
|
|
0540d72c87 | ||
|
|
1dee443c2b | ||
|
|
32e2642233 | ||
|
|
454426cba5 | ||
|
|
f96a1d89c5 | ||
|
|
ee0844dbd8 | ||
|
|
b74c626026 | ||
|
|
6117930642 | ||
|
|
7bb06b6dad | ||
|
|
7950c26a8e | ||
|
|
836dc451f4 | ||
|
|
8df3e55a31 | ||
|
|
53add4435f | ||
|
|
d7a5c5716f | ||
|
|
313a884459 | ||
|
|
c39689da41 | ||
|
|
17f64704c2 | ||
|
|
f9953fbe7c | ||
|
|
0ea80eb63c | ||
|
|
38ebf8dd10 | ||
|
|
04b1583d10 | ||
|
|
d9b93674c3 | ||
|
|
7d6bde2496 | ||
|
|
bd065a12bb | ||
|
|
f9df750025 | ||
|
|
d343f9497c | ||
|
|
5192191c38 | ||
|
|
69343310c6 | ||
|
|
c9c2c34b44 | ||
|
|
9beeef970e | ||
|
|
43aa037ebd | ||
|
|
2abf2070f2 | ||
|
|
ce0ff0040f | ||
|
|
6d2e11b7d1 |
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
15
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -2,13 +2,14 @@ Thanks for submitting a PR, your contribution is really appreciated!
|
||||
|
||||
Here's a quick checklist that should be present in PRs:
|
||||
|
||||
- [ ] Target: for bug or doc fixes, target `master`; for new features, target `features`;
|
||||
- [ ] Add a new news fragment into the changelog folder
|
||||
* name it `$issue_id.$type` for example (588.bug)
|
||||
* if you don't have an issue_id change it to the pr id after creating the pr
|
||||
* ensure type is one of `removal`, `feature`, `bugfix`, `vendor`, `doc` or `trivial`
|
||||
* Make sure to use full sentences with correct case and punctuation, for example: "Fix issue with non-ascii contents in doctest text files."
|
||||
- [ ] Target: for `bugfix`, `vendor`, `doc` or `trivial` fixes, target `master`; for removals or features target `features`;
|
||||
- [ ] Make sure to include reasonable tests for your change if necessary
|
||||
|
||||
Unless your change is trivial documentation fix (e.g., a typo or reword of a small section) please:
|
||||
Unless your change is a trivial or a documentation fix (e.g., a typo or reword of a small section) please:
|
||||
|
||||
- [ ] Make sure to include one or more tests for your change;
|
||||
- [ ] Add yourself to `AUTHORS`;
|
||||
- [ ] Add a new entry to `CHANGELOG.rst`
|
||||
* Choose any open position to avoid merge conflicts with other PRs.
|
||||
* Add a link to the issue you are fixing (if any) using RST syntax.
|
||||
* The pytest team likes to have people to acknowledged in the `CHANGELOG`, so please add a thank note to yourself ("Thanks @user for the PR") and a link to your GitHub profile. It may sound weird thanking yourself, but otherwise a maintainer would have to do it manually before or after merging instead of just using GitHub's merge button. This makes it easier on the maintainers to merge PRs.
|
||||
|
||||
6
AUTHORS
6
AUTHORS
@@ -13,8 +13,8 @@ Andreas Zeidler
|
||||
Andrzej Ostrowski
|
||||
Andy Freeland
|
||||
Anthon van der Neut
|
||||
Antony Lee
|
||||
Anthony Sottile
|
||||
Antony Lee
|
||||
Armin Rigo
|
||||
Aron Curzon
|
||||
Aviv Palivoda
|
||||
@@ -70,6 +70,7 @@ Grig Gheorghiu
|
||||
Grigorii Eremeev (budulianin)
|
||||
Guido Wesdorp
|
||||
Harald Armin Massa
|
||||
Hui Wang (coldnight)
|
||||
Ian Bicking
|
||||
Jaap Broekhuizen
|
||||
Jan Balster
|
||||
@@ -143,6 +144,7 @@ Ross Lawley
|
||||
Russel Winder
|
||||
Ryan Wooden
|
||||
Samuele Pedroni
|
||||
Segev Finer
|
||||
Simon Gomizelj
|
||||
Skylar Downes
|
||||
Stefan Farmbauer
|
||||
@@ -158,8 +160,8 @@ Trevor Bekolay
|
||||
Tyler Goodlet
|
||||
Vasily Kuznetsov
|
||||
Victor Uriarte
|
||||
Vlad Dragos
|
||||
Vidar T. Fauske
|
||||
Vitaly Lashmanov
|
||||
Vlad Dragos
|
||||
Wouter van Ackooy
|
||||
Xuecong Liao
|
||||
|
||||
146
CHANGELOG.rst
146
CHANGELOG.rst
@@ -1,15 +1,143 @@
|
||||
3.1.0 (2017-05-20)
|
||||
..
|
||||
You should *NOT* be adding new change log entries to this file, this
|
||||
file is managed by towncrier. You *may* edit previous change logs to
|
||||
fix problems like typo corrections or such.
|
||||
To add a new change log entry, please see
|
||||
https://pip.pypa.io/en/latest/development/#adding-a-news-entry
|
||||
we named the news folder changelog
|
||||
|
||||
.. towncrier release notes start
|
||||
|
||||
Pytest 3.1.3 (2017-07-03)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Fix decode error in Python 2 for doctests in docstrings. (`#2434
|
||||
<https://github.com/pytest-dev/pytest/issues/2434>`_)
|
||||
|
||||
- Exceptions raised during teardown by finalizers are now suppressed until all
|
||||
finalizers are called, with the initial exception reraised. (`#2440
|
||||
<https://github.com/pytest-dev/pytest/issues/2440>`_)
|
||||
|
||||
- Fix incorrect "collected items" report when specifying tests on the command-
|
||||
line. (`#2464 <https://github.com/pytest-dev/pytest/issues/2464>`_)
|
||||
|
||||
- ``deprecated_call`` in context-manager form now captures deprecation warnings
|
||||
even if the same warning has already been raised. Also, ``deprecated_call``
|
||||
will always produce the same error message (previously it would produce
|
||||
different messages in context-manager vs. function-call mode). (`#2469
|
||||
<https://github.com/pytest-dev/pytest/issues/2469>`_)
|
||||
|
||||
- Fix issue where paths collected by pytest could have triple leading ``/``
|
||||
characters. (`#2475 <https://github.com/pytest-dev/pytest/issues/2475>`_)
|
||||
|
||||
- Fix internal error when trying to detect the start of a recursive traceback.
|
||||
(`#2486 <https://github.com/pytest-dev/pytest/issues/2486>`_)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Explicitly state for which hooks the calls stop after the first non-None
|
||||
result. (`#2493 <https://github.com/pytest-dev/pytest/issues/2493>`_)
|
||||
|
||||
|
||||
Trivial/Internal Changes
|
||||
------------------------
|
||||
|
||||
- Create invoke tasks for updating the vendored packages. (`#2474
|
||||
<https://github.com/pytest-dev/pytest/issues/2474>`_)
|
||||
|
||||
- Update copyright dates in LICENSE, README.rst and in the documentation.
|
||||
(`#2499 <https://github.com/pytest-dev/pytest/issues/2499>`_)
|
||||
|
||||
|
||||
Pytest 3.1.2 (2017-06-08)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- Required options added via ``pytest_addoption`` will no longer prevent using
|
||||
--help without passing them. (#1999)
|
||||
|
||||
- Respect ``python_files`` in assertion rewriting. (#2121)
|
||||
|
||||
- Fix recursion error detection when frames in the traceback contain objects
|
||||
that can't be compared (like ``numpy`` arrays). (#2459)
|
||||
|
||||
- ``UnicodeWarning`` is issued from the internal pytest warnings plugin only
|
||||
when the message contains non-ascii unicode (Python 2 only). (#2463)
|
||||
|
||||
- Added a workaround for Python 3.6 ``WindowsConsoleIO`` breaking due to Pytests's
|
||||
``FDCapture``. Other code using console handles might still be affected by the
|
||||
very same issue and might require further workarounds/fixes, i.e. ``colorama``.
|
||||
(#2467)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Fix internal API links to ``pluggy`` objects. (#2331)
|
||||
|
||||
- Make it clear that ``pytest.xfail`` stops test execution at the calling point
|
||||
and improve overall flow of the ``skipping`` docs. (#810)
|
||||
|
||||
|
||||
Pytest 3.1.1 (2017-05-30)
|
||||
=========================
|
||||
|
||||
Bug Fixes
|
||||
---------
|
||||
|
||||
- pytest warning capture no longer overrides existing warning filters. The
|
||||
previous behaviour would override all filters and caused regressions in test
|
||||
suites which configure warning filters to match their needs. Note that as a
|
||||
side-effect of this is that ``DeprecationWarning`` and
|
||||
``PendingDeprecationWarning`` are no longer shown by default. (#2430)
|
||||
|
||||
- Fix issue with non-ascii contents in doctest text files. (#2434)
|
||||
|
||||
- Fix encoding errors for unicode warnings in Python 2. (#2436)
|
||||
|
||||
- ``pytest.deprecated_call`` now captures ``PendingDeprecationWarning`` in
|
||||
context manager form. (#2441)
|
||||
|
||||
|
||||
Improved Documentation
|
||||
----------------------
|
||||
|
||||
- Addition of towncrier for changelog management. (#2390)
|
||||
|
||||
|
||||
3.1.0 (2017-05-22)
|
||||
==================
|
||||
|
||||
|
||||
New Features
|
||||
------------
|
||||
|
||||
* The ``pytest-warnings`` plugin has been integrated into the core, so now ``pytest`` automatically
|
||||
* The ``pytest-warnings`` plugin has been integrated into the core and now ``pytest`` automatically
|
||||
captures and displays warnings at the end of the test session.
|
||||
|
||||
.. warning::
|
||||
|
||||
This feature may disrupt test suites which apply and treat warnings themselves, and can be
|
||||
disabled in your ``pytest.ini``:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
addopts = -p no:warnings
|
||||
|
||||
See the `warnings documentation page <https://docs.pytest.org/en/latest/warnings.html>`_ for more
|
||||
information.
|
||||
|
||||
Thanks `@nicoddemus`_ for the PR.
|
||||
|
||||
* Added ``junit_suite_name`` ini option to specify root `<testsuite>` name for JUnit XML reports (`#533`_).
|
||||
* Added ``junit_suite_name`` ini option to specify root ``<testsuite>`` name for JUnit XML reports (`#533`_).
|
||||
|
||||
* Added an ini option ``doctest_encoding`` to specify which encoding to use for doctest files.
|
||||
Thanks `@wheerd`_ for the PR (`#2101`_).
|
||||
@@ -53,8 +181,6 @@ Changes
|
||||
* ``--pdbcls`` no longer implies ``--pdb``. This makes it possible to use
|
||||
``addopts=--pdbcls=module.SomeClass`` on ``pytest.ini``. Thanks `@davidszotten`_ for
|
||||
the PR (`#1952`_).
|
||||
* Change exception raised by ``capture.DontReadFromInput.fileno()`` from ``ValueError``
|
||||
to ``io.UnsupportedOperation``. Thanks `@vlad-dragos`_ for the PR.
|
||||
|
||||
* fix `#2013`_: turn RecordedWarning into ``namedtuple``,
|
||||
to give it a comprehensible repr while preventing unwarranted modification.
|
||||
@@ -73,7 +199,7 @@ Changes
|
||||
|
||||
* Add ``venv`` to the default ``norecursedirs`` setting.
|
||||
Thanks `@The-Compiler`_ for the PR.
|
||||
|
||||
|
||||
* ``PluginManager.import_plugin`` now accepts unicode plugin names in Python 2.
|
||||
Thanks `@reutsharabani`_ for the PR.
|
||||
|
||||
@@ -86,7 +212,7 @@ Changes
|
||||
Thanks `@RonnyPfannschmidt`_ for the PR.
|
||||
|
||||
* fix `#2391`_: consider pytest_plugins on all plugin modules
|
||||
Thansks `@RonnyPfannschmidt`_ for the PR.
|
||||
Thanks `@RonnyPfannschmidt`_ for the PR.
|
||||
|
||||
|
||||
Bug Fixes
|
||||
@@ -98,7 +224,7 @@ Bug Fixes
|
||||
|
||||
* Change capture.py's ``DontReadFromInput`` class to throw ``io.UnsupportedOperation`` errors rather
|
||||
than ValueErrors in the ``fileno`` method (`#2276`_).
|
||||
Thanks `@metasyn`_ for the PR.
|
||||
Thanks `@metasyn`_ and `@vlad-dragos`_ for the PR.
|
||||
|
||||
* Fix exception formatting while importing modules when the exception message
|
||||
contains non-ascii characters (`#2336`_).
|
||||
@@ -171,13 +297,13 @@ Bug Fixes
|
||||
|
||||
* Ignore exceptions raised from descriptors (e.g. properties) during Python test collection (`#2234`_).
|
||||
Thanks to `@bluetech`_.
|
||||
|
||||
|
||||
* ``--override-ini`` now correctly overrides some fundamental options like ``python_files`` (`#2238`_).
|
||||
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`_).
|
||||
Thanks `@tgoodlet`_ for the report and `@nicoddemus`_ for the PR.
|
||||
|
||||
|
||||
* Fix internal errors when an unprintable ``AssertionError`` is raised inside a test.
|
||||
Thanks `@omerhadari`_ for the PR.
|
||||
|
||||
|
||||
@@ -3,82 +3,59 @@ How to release pytest
|
||||
|
||||
.. important::
|
||||
|
||||
pytest releases must be prepared on **linux** because the docs and examples expect
|
||||
pytest releases must be prepared on **Linux** because the docs and examples expect
|
||||
to be executed in that platform.
|
||||
|
||||
#. Install development dependencies in a virtual environment with::
|
||||
|
||||
pip3 install -r tasks/requirements.txt
|
||||
|
||||
#. Create a branch ``release-X.Y.Z`` with the version for the release. Make sure it is up to date
|
||||
with the latest ``master`` (for patch releases) and with the latest ``features`` merged with
|
||||
the latest ``master`` (for minor releases). Ensure your are in a clean work tree.
|
||||
#. Create a branch ``release-X.Y.Z`` with the version for the release.
|
||||
|
||||
#. Check and finalize ``CHANGELOG.rst`` (will be automated soon).
|
||||
* **patch releases**: from the latest ``master``;
|
||||
|
||||
#. Execute to automatically generate docs, announcements and upload a package to
|
||||
* **minor releases**: from the latest ``features``; then merge with the latest ``master``;
|
||||
|
||||
Ensure your are in a clean work tree.
|
||||
|
||||
#. Generate docs, changelog, announcements and upload a package to
|
||||
your ``devpi`` staging server::
|
||||
|
||||
invoke generate.pre_release <VERSION> <DEVPI USER> --password <DEVPI PASSWORD>
|
||||
|
||||
If ``--password`` is not given, it is assumed the user is already logged in. If you don't have
|
||||
an account, please ask for one!
|
||||
If ``--password`` is not given, it is assumed the user is already logged in ``devpi``.
|
||||
If you don't have an account, please ask for one.
|
||||
|
||||
#. Run from multiple machines::
|
||||
#. Open a PR for this branch targeting ``master``.
|
||||
|
||||
devpi use https://devpi.net/USER/dev
|
||||
devpi test pytest==VERSION
|
||||
#. Test the package
|
||||
|
||||
Alternatively, you can use `devpi-cloud-tester <https://github.com/nicoddemus/devpi-cloud-tester>`_ to test
|
||||
the package on AppVeyor and Travis (follow instructions on the ``README``).
|
||||
* **Manual method**
|
||||
|
||||
#. Check that tests pass for relevant combinations with::
|
||||
Run from multiple machines::
|
||||
|
||||
devpi use https://devpi.net/USER/dev
|
||||
devpi test pytest==VERSION
|
||||
|
||||
Check that tests pass for relevant combinations with::
|
||||
|
||||
devpi list pytest
|
||||
|
||||
or look at failures with "devpi list -f pytest".
|
||||
* **CI servers**
|
||||
|
||||
#. Feeling confident? Publish to pypi::
|
||||
Configure a repository as per-instructions on
|
||||
devpi-cloud-test_ to test the package on Travis_ and AppVeyor_.
|
||||
All test environments should pass.
|
||||
|
||||
devpi push pytest==VERSION pypi:NAME
|
||||
#. Publish to PyPI::
|
||||
|
||||
where NAME is the name of pypi.python.org as configured in your ``~/.pypirc``
|
||||
invoke generate.publish_release <VERSION> <DEVPI USER> <PYPI_NAME>
|
||||
|
||||
where PYPI_NAME is the name of pypi.python.org as configured in your ``~/.pypirc``
|
||||
file `for devpi <http://doc.devpi.net/latest/quickstart-releaseprocess.html?highlight=pypirc#devpi-push-releasing-to-an-external-index>`_.
|
||||
|
||||
#. Tag the release::
|
||||
#. After a minor/major release, merge ``features`` into ``master`` and push (or open a PR).
|
||||
|
||||
git tag VERSION <hash>
|
||||
git push origin VERSION
|
||||
|
||||
Make sure ``<hash>`` is **exactly** the git hash at the time the package was created.
|
||||
|
||||
#. Send release announcement to mailing lists:
|
||||
|
||||
- pytest-dev@python.org
|
||||
- python-announce-list@python.org
|
||||
- testing-in-python@lists.idyll.org (only for minor/major releases)
|
||||
|
||||
And announce the release on Twitter, making sure to add the hashtag ``#pytest``.
|
||||
|
||||
#. **After the release**
|
||||
|
||||
a. **patch release (2.8.3)**:
|
||||
|
||||
1. Checkout ``master``.
|
||||
2. Update version number in ``_pytest/__init__.py`` to ``"2.8.4.dev0"``.
|
||||
3. Create a new section in ``CHANGELOG.rst`` titled ``2.8.4.dev0`` and add a few bullet points as placeholders for new entries.
|
||||
4. Commit and push.
|
||||
|
||||
b. **minor release (2.9.0)**:
|
||||
|
||||
1. Merge ``features`` into ``master``.
|
||||
2. Checkout ``master``.
|
||||
3. Follow the same steps for a **patch release** above, using the next patch release: ``2.9.1.dev0``.
|
||||
4. Commit ``master``.
|
||||
5. Checkout ``features`` and merge with ``master`` (should be a fast-forward at this point).
|
||||
6. Update version number in ``_pytest/__init__.py`` to the next minor release: ``"2.10.0.dev0"``.
|
||||
7. Create a new section in ``CHANGELOG.rst`` titled ``2.10.0.dev0``, above ``2.9.1.dev0``, and add a few bullet points as placeholders for new entries.
|
||||
8. Commit ``features``.
|
||||
9. Push ``master`` and ``features``.
|
||||
|
||||
c. **major release (3.0.0)**: same steps as that of a **minor release**
|
||||
.. _devpi-cloud-test: https://github.com/obestwalter/devpi-cloud-test
|
||||
.. _AppVeyor: https://www.appveyor.com/
|
||||
.. _Travis: https://travis-ci.org
|
||||
|
||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2004-2016 Holger Krekel and others
|
||||
Copyright (c) 2004-2017 Holger Krekel and others
|
||||
|
||||
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
|
||||
|
||||
37
MANIFEST.in
37
MANIFEST.in
@@ -1,37 +0,0 @@
|
||||
include CHANGELOG.rst
|
||||
include LICENSE
|
||||
include AUTHORS
|
||||
|
||||
include README.rst
|
||||
include CONTRIBUTING.rst
|
||||
include HOWTORELEASE.rst
|
||||
|
||||
include tox.ini
|
||||
include setup.py
|
||||
|
||||
recursive-include scripts *.py
|
||||
recursive-include scripts *.bat
|
||||
|
||||
include .coveragerc
|
||||
|
||||
recursive-include bench *.py
|
||||
recursive-include extra *.py
|
||||
|
||||
graft testing
|
||||
graft doc
|
||||
prune doc/en/_build
|
||||
graft tasks
|
||||
|
||||
exclude _pytest/impl
|
||||
|
||||
graft _pytest/vendored_packages
|
||||
|
||||
recursive-exclude * *.pyc *.pyo
|
||||
recursive-exclude testing/.hypothesis *
|
||||
recursive-exclude testing/freeze/~ *
|
||||
recursive-exclude testing/freeze/build *
|
||||
recursive-exclude testing/freeze/dist *
|
||||
|
||||
exclude appveyor.yml
|
||||
exclude .travis.yml
|
||||
prune .github
|
||||
@@ -102,7 +102,7 @@ Consult the `Changelog <http://docs.pytest.org/en/latest/changelog.html>`__ page
|
||||
License
|
||||
-------
|
||||
|
||||
Copyright Holger Krekel and others, 2004-2016.
|
||||
Copyright Holger Krekel and others, 2004-2017.
|
||||
|
||||
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import sys
|
||||
from inspect import CO_VARARGS, CO_VARKEYWORDS
|
||||
import re
|
||||
from weakref import ref
|
||||
from _pytest.compat import _PY2, _PY3, PY35
|
||||
from _pytest.compat import _PY2, _PY3, PY35, safe_str
|
||||
|
||||
import py
|
||||
builtin_repr = repr
|
||||
@@ -602,21 +602,51 @@ class FormattedExcinfo(object):
|
||||
traceback = excinfo.traceback
|
||||
if self.tbfilter:
|
||||
traceback = traceback.filter()
|
||||
recursionindex = None
|
||||
|
||||
if is_recursion_error(excinfo):
|
||||
recursionindex = traceback.recursionindex()
|
||||
traceback, extraline = self._truncate_recursive_traceback(traceback)
|
||||
else:
|
||||
extraline = None
|
||||
|
||||
last = traceback[-1]
|
||||
entries = []
|
||||
extraline = None
|
||||
for index, entry in enumerate(traceback):
|
||||
einfo = (last == entry) and excinfo or None
|
||||
reprentry = self.repr_traceback_entry(entry, einfo)
|
||||
entries.append(reprentry)
|
||||
if index == recursionindex:
|
||||
extraline = "!!! Recursion detected (same locals & position)"
|
||||
break
|
||||
return ReprTraceback(entries, extraline, style=self.style)
|
||||
|
||||
def _truncate_recursive_traceback(self, traceback):
|
||||
"""
|
||||
Truncate the given recursive traceback trying to find the starting point
|
||||
of the recursion.
|
||||
|
||||
The detection is done by going through each traceback entry and finding the
|
||||
point in which the locals of the frame are equal to the locals of a previous frame (see ``recursionindex()``.
|
||||
|
||||
Handle the situation where the recursion process might raise an exception (for example
|
||||
comparing numpy arrays using equality raises a TypeError), in which case we do our best to
|
||||
warn the user of the error and show a limited traceback.
|
||||
"""
|
||||
try:
|
||||
recursionindex = traceback.recursionindex()
|
||||
except Exception as e:
|
||||
max_frames = 10
|
||||
extraline = (
|
||||
'!!! Recursion error detected, but an error occurred locating the origin of recursion.\n'
|
||||
' The following exception happened when comparing locals in the stack frame:\n'
|
||||
' {exc_type}: {exc_msg}\n'
|
||||
' Displaying first and last {max_frames} stack frames out of {total}.'
|
||||
).format(exc_type=type(e).__name__, exc_msg=safe_str(e), max_frames=max_frames, total=len(traceback))
|
||||
traceback = traceback[:max_frames] + traceback[-max_frames:]
|
||||
else:
|
||||
if recursionindex is not None:
|
||||
extraline = "!!! Recursion detected (same locals & position)"
|
||||
traceback = traceback[:recursionindex + 1]
|
||||
else:
|
||||
extraline = None
|
||||
|
||||
return traceback, extraline
|
||||
|
||||
def repr_excinfo(self, excinfo):
|
||||
if _PY2:
|
||||
|
||||
@@ -11,7 +11,6 @@ import re
|
||||
import struct
|
||||
import sys
|
||||
import types
|
||||
from fnmatch import fnmatch
|
||||
|
||||
import py
|
||||
from _pytest.assertion import util
|
||||
@@ -163,11 +162,7 @@ class AssertionRewritingHook(object):
|
||||
# modules not passed explicitly on the command line are only
|
||||
# rewritten if they match the naming convention for test files
|
||||
for pat in self.fnpats:
|
||||
# use fnmatch instead of fn_pypath.fnmatch because the
|
||||
# latter might trigger an import to fnmatch.fnmatch
|
||||
# internally, which would cause this method to be
|
||||
# called recursively
|
||||
if fnmatch(fn_pypath.basename, pat):
|
||||
if fn_pypath.fnmatch(pat):
|
||||
state.trace("matched test file %r" % (fn,))
|
||||
return True
|
||||
|
||||
|
||||
@@ -219,7 +219,7 @@ def cacheshow(config, session):
|
||||
basedir = config.cache._cachedir
|
||||
vdir = basedir.join("v")
|
||||
tw.sep("-", "cache values")
|
||||
for valpath in vdir.visit(lambda x: x.isfile()):
|
||||
for valpath in sorted(vdir.visit(lambda x: x.isfile())):
|
||||
key = valpath.relto(vdir).replace(valpath.sep, "/")
|
||||
val = config.cache.get(key, dummy)
|
||||
if val is dummy:
|
||||
@@ -235,7 +235,7 @@ def cacheshow(config, session):
|
||||
ddir = basedir.join("d")
|
||||
if ddir.isdir() and ddir.listdir():
|
||||
tw.sep("-", "cache directories")
|
||||
for p in basedir.join("d").visit():
|
||||
for p in sorted(basedir.join("d").visit()):
|
||||
#if p.check(dir=1):
|
||||
# print("%s/" % p.relto(basedir))
|
||||
if p.isfile():
|
||||
|
||||
@@ -7,6 +7,7 @@ from __future__ import absolute_import, division, print_function
|
||||
import contextlib
|
||||
import sys
|
||||
import os
|
||||
import io
|
||||
from io import UnsupportedOperation
|
||||
from tempfile import TemporaryFile
|
||||
|
||||
@@ -33,8 +34,10 @@ def pytest_addoption(parser):
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_load_initial_conftests(early_config, parser, args):
|
||||
_readline_workaround()
|
||||
ns = early_config.known_args_namespace
|
||||
if ns.capture == "fd":
|
||||
_py36_windowsconsoleio_workaround()
|
||||
_readline_workaround()
|
||||
pluginmanager = early_config.pluginmanager
|
||||
capman = CaptureManager(ns.capture)
|
||||
pluginmanager.register(capman, "capturemanager")
|
||||
@@ -491,3 +494,49 @@ def _readline_workaround():
|
||||
import readline # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def _py36_windowsconsoleio_workaround():
|
||||
"""
|
||||
Python 3.6 implemented unicode console handling for Windows. This works
|
||||
by reading/writing to the raw console handle using
|
||||
``{Read,Write}ConsoleW``.
|
||||
|
||||
The problem is that we are going to ``dup2`` over the stdio file
|
||||
descriptors when doing ``FDCapture`` and this will ``CloseHandle`` the
|
||||
handles used by Python to write to the console. Though there is still some
|
||||
weirdness and the console handle seems to only be closed randomly and not
|
||||
on the first call to ``CloseHandle``, or maybe it gets reopened with the
|
||||
same handle value when we suspend capturing.
|
||||
|
||||
The workaround in this case will reopen stdio with a different fd which
|
||||
also means a different handle by replicating the logic in
|
||||
"Py_lifecycle.c:initstdio/create_stdio".
|
||||
|
||||
See https://github.com/pytest-dev/py/issues/103
|
||||
"""
|
||||
if not sys.platform.startswith('win32') or sys.version_info[:2] < (3, 6):
|
||||
return
|
||||
|
||||
buffered = hasattr(sys.stdout.buffer, 'raw')
|
||||
raw_stdout = sys.stdout.buffer.raw if buffered else sys.stdout.buffer
|
||||
|
||||
if not isinstance(raw_stdout, io._WindowsConsoleIO):
|
||||
return
|
||||
|
||||
def _reopen_stdio(f, mode):
|
||||
if not buffered and mode[0] == 'w':
|
||||
buffering = 0
|
||||
else:
|
||||
buffering = -1
|
||||
|
||||
return io.TextIOWrapper(
|
||||
open(os.dup(f.fileno()), mode, buffering),
|
||||
f.encoding,
|
||||
f.errors,
|
||||
f.newlines,
|
||||
f.line_buffering)
|
||||
|
||||
sys.__stdin__ = sys.stdin = _reopen_stdio(sys.stdin, 'rb')
|
||||
sys.__stdout__ = sys.stdout = _reopen_stdio(sys.stdout, 'wb')
|
||||
sys.__stderr__ = sys.stderr = _reopen_stdio(sys.stderr, 'wb')
|
||||
|
||||
@@ -126,6 +126,7 @@ if _PY3:
|
||||
import codecs
|
||||
imap = map
|
||||
STRING_TYPES = bytes, str
|
||||
UNICODE_TYPES = str,
|
||||
|
||||
def _escape_strings(val):
|
||||
"""If val is pure ascii, returns it as a str(). Otherwise, escapes
|
||||
@@ -157,6 +158,7 @@ if _PY3:
|
||||
return val.encode('unicode_escape').decode('ascii')
|
||||
else:
|
||||
STRING_TYPES = bytes, str, unicode
|
||||
UNICODE_TYPES = unicode,
|
||||
|
||||
from itertools import imap # NOQA
|
||||
|
||||
|
||||
@@ -71,6 +71,12 @@ class UsageError(Exception):
|
||||
""" error in pytest usage or invocation"""
|
||||
|
||||
|
||||
class PrintHelp(Exception):
|
||||
"""Raised when pytest should print it's help to skip the rest of the
|
||||
argument parsing and validation."""
|
||||
pass
|
||||
|
||||
|
||||
def filename_arg(path, optname):
|
||||
""" Argparse type validator for filename arguments.
|
||||
|
||||
@@ -163,7 +169,7 @@ def _prepareconfig(args=None, plugins=None):
|
||||
|
||||
class PytestPluginManager(PluginManager):
|
||||
"""
|
||||
Overwrites :py:class:`pluggy.PluginManager` to add pytest-specific
|
||||
Overwrites :py:class:`pluggy.PluginManager <_pytest.vendored_packages.pluggy.PluginManager>` to add pytest-specific
|
||||
functionality:
|
||||
|
||||
* loading plugins from the command line, ``PYTEST_PLUGIN`` env variable and
|
||||
@@ -200,7 +206,7 @@ class PytestPluginManager(PluginManager):
|
||||
"""
|
||||
.. deprecated:: 2.8
|
||||
|
||||
Use :py:meth:`pluggy.PluginManager.add_hookspecs` instead.
|
||||
Use :py:meth:`pluggy.PluginManager.add_hookspecs <_pytest.vendored_packages.pluggy.PluginManager.add_hookspecs>` instead.
|
||||
"""
|
||||
warning = dict(code="I2",
|
||||
fslocation=_pytest._code.getfslineno(sys._getframe(1)),
|
||||
@@ -984,7 +990,7 @@ class Config(object):
|
||||
self._parser.addini('minversion', 'minimally required pytest version')
|
||||
self._override_ini = ns.override_ini or ()
|
||||
|
||||
def _consider_importhook(self, args, entrypoint_name):
|
||||
def _consider_importhook(self, args):
|
||||
"""Install the PEP 302 import hook if using assertion re-writing.
|
||||
|
||||
Needs to parse the --assert=<mode> option from the commandline
|
||||
@@ -999,26 +1005,41 @@ class Config(object):
|
||||
except SystemError:
|
||||
mode = 'plain'
|
||||
else:
|
||||
import pkg_resources
|
||||
self.pluginmanager.rewrite_hook = hook
|
||||
for entrypoint in pkg_resources.iter_entry_points('pytest11'):
|
||||
# 'RECORD' available for plugins installed normally (pip install)
|
||||
# 'SOURCES.txt' available for plugins installed in dev mode (pip install -e)
|
||||
# for installed plugins 'SOURCES.txt' returns an empty list, and vice-versa
|
||||
# so it shouldn't be an issue
|
||||
for metadata in ('RECORD', 'SOURCES.txt'):
|
||||
for entry in entrypoint.dist._get_metadata(metadata):
|
||||
fn = entry.split(',')[0]
|
||||
is_simple_module = os.sep not in fn and fn.endswith('.py')
|
||||
is_package = fn.count(os.sep) == 1 and fn.endswith('__init__.py')
|
||||
if is_simple_module:
|
||||
module_name, ext = os.path.splitext(fn)
|
||||
hook.mark_rewrite(module_name)
|
||||
elif is_package:
|
||||
package_name = os.path.dirname(fn)
|
||||
hook.mark_rewrite(package_name)
|
||||
self._mark_plugins_for_rewrite(hook)
|
||||
self._warn_about_missing_assertion(mode)
|
||||
|
||||
def _mark_plugins_for_rewrite(self, hook):
|
||||
"""
|
||||
Given an importhook, mark for rewrite any top-level
|
||||
modules or packages in the distribution package for
|
||||
all pytest plugins.
|
||||
"""
|
||||
import pkg_resources
|
||||
self.pluginmanager.rewrite_hook = hook
|
||||
|
||||
# 'RECORD' available for plugins installed normally (pip install)
|
||||
# 'SOURCES.txt' available for plugins installed in dev mode (pip install -e)
|
||||
# for installed plugins 'SOURCES.txt' returns an empty list, and vice-versa
|
||||
# so it shouldn't be an issue
|
||||
metadata_files = 'RECORD', 'SOURCES.txt'
|
||||
|
||||
package_files = (
|
||||
entry.split(',')[0]
|
||||
for entrypoint in pkg_resources.iter_entry_points('pytest11')
|
||||
for metadata in metadata_files
|
||||
for entry in entrypoint.dist._get_metadata(metadata)
|
||||
)
|
||||
|
||||
for fn in package_files:
|
||||
is_simple_module = os.sep not in fn and fn.endswith('.py')
|
||||
is_package = fn.count(os.sep) == 1 and fn.endswith('__init__.py')
|
||||
if is_simple_module:
|
||||
module_name, ext = os.path.splitext(fn)
|
||||
hook.mark_rewrite(module_name)
|
||||
elif is_package:
|
||||
package_name = os.path.dirname(fn)
|
||||
hook.mark_rewrite(package_name)
|
||||
|
||||
def _warn_about_missing_assertion(self, mode):
|
||||
try:
|
||||
assert False
|
||||
@@ -1042,10 +1063,9 @@ class Config(object):
|
||||
args[:] = shlex.split(os.environ.get('PYTEST_ADDOPTS', '')) + args
|
||||
args[:] = self.getini("addopts") + args
|
||||
self._checkversion()
|
||||
entrypoint_name = 'pytest11'
|
||||
self._consider_importhook(args, entrypoint_name)
|
||||
self._consider_importhook(args)
|
||||
self.pluginmanager.consider_preparse(args)
|
||||
self.pluginmanager.load_setuptools_entrypoints(entrypoint_name)
|
||||
self.pluginmanager.load_setuptools_entrypoints('pytest11')
|
||||
self.pluginmanager.consider_env()
|
||||
self.known_args_namespace = ns = self._parser.parse_known_args(args, namespace=self.option.copy())
|
||||
confcutdir = self.known_args_namespace.confcutdir
|
||||
@@ -1086,14 +1106,18 @@ class Config(object):
|
||||
self._preparse(args, addopts=addopts)
|
||||
# XXX deprecated hook:
|
||||
self.hook.pytest_cmdline_preparse(config=self, args=args)
|
||||
args = self._parser.parse_setoption(args, self.option, namespace=self.option)
|
||||
if not args:
|
||||
cwd = os.getcwd()
|
||||
if cwd == self.rootdir:
|
||||
args = self.getini('testpaths')
|
||||
self._parser.after_preparse = True
|
||||
try:
|
||||
args = self._parser.parse_setoption(args, self.option, namespace=self.option)
|
||||
if not args:
|
||||
args = [cwd]
|
||||
self.args = args
|
||||
cwd = os.getcwd()
|
||||
if cwd == self.rootdir:
|
||||
args = self.getini('testpaths')
|
||||
if not args:
|
||||
args = [cwd]
|
||||
self.args = args
|
||||
except PrintHelp:
|
||||
pass
|
||||
|
||||
def addinivalue_line(self, name, line):
|
||||
""" add a line to an ini-file option. The option must have been
|
||||
@@ -1106,7 +1130,7 @@ class Config(object):
|
||||
def getini(self, name):
|
||||
""" return configuration value from an :ref:`ini file <inifiles>`. If the
|
||||
specified name hasn't been registered through a prior
|
||||
:py:func:`parser.addini <pytest.config.Parser.addini>`
|
||||
:py:func:`parser.addini <_pytest.config.Parser.addini>`
|
||||
call (usually from a plugin), a ValueError is raised. """
|
||||
try:
|
||||
return self._inicache[name]
|
||||
|
||||
@@ -177,10 +177,10 @@ class DoctestTextfile(pytest.Module):
|
||||
name = self.fspath.basename
|
||||
globs = {'__name__': '__main__'}
|
||||
|
||||
|
||||
optionflags = get_optionflags(self)
|
||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
||||
checker=_get_checker())
|
||||
_fix_spoof_python2(runner, encoding)
|
||||
|
||||
parser = doctest.DocTestParser()
|
||||
test = parser.get_doctest(text, globs, name, filename, 0)
|
||||
@@ -216,6 +216,7 @@ class DoctestModule(pytest.Module):
|
||||
optionflags = get_optionflags(self)
|
||||
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
|
||||
checker=_get_checker())
|
||||
|
||||
for test in finder.find(module, module.__name__):
|
||||
if test.examples: # skip empty doctests
|
||||
yield DoctestItem(test.name, self, runner, test)
|
||||
@@ -324,6 +325,33 @@ def _get_report_choice(key):
|
||||
DOCTEST_REPORT_CHOICE_NONE: 0,
|
||||
}[key]
|
||||
|
||||
|
||||
def _fix_spoof_python2(runner, encoding):
|
||||
"""
|
||||
Installs a "SpoofOut" into the given DebugRunner so it properly deals with unicode output. This
|
||||
should patch only doctests for text files because they don't have a way to declare their
|
||||
encoding. Doctests in docstrings from Python modules don't have the same problem given that
|
||||
Python already decoded the strings.
|
||||
|
||||
This fixes the problem related in issue #2434.
|
||||
"""
|
||||
from _pytest.compat import _PY2
|
||||
if not _PY2:
|
||||
return
|
||||
|
||||
from doctest import _SpoofOut
|
||||
|
||||
class UnicodeSpoof(_SpoofOut):
|
||||
|
||||
def getvalue(self):
|
||||
result = _SpoofOut.getvalue(self)
|
||||
if encoding:
|
||||
result = result.decode(encoding)
|
||||
return result
|
||||
|
||||
runner._fakeout = UnicodeSpoof()
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
def doctest_namespace():
|
||||
"""
|
||||
|
||||
@@ -733,10 +733,19 @@ class FixtureDef:
|
||||
self._finalizer.append(finalizer)
|
||||
|
||||
def finish(self):
|
||||
exceptions = []
|
||||
try:
|
||||
while self._finalizer:
|
||||
func = self._finalizer.pop()
|
||||
func()
|
||||
try:
|
||||
func = self._finalizer.pop()
|
||||
func()
|
||||
except:
|
||||
exceptions.append(sys.exc_info())
|
||||
if exceptions:
|
||||
e = exceptions[0]
|
||||
del exceptions # ensure we don't keep all frames alive because of the traceback
|
||||
py.builtin._reraise(*e)
|
||||
|
||||
finally:
|
||||
ihook = self._fixturemanager.session.ihook
|
||||
ihook.pytest_fixture_post_finalizer(fixturedef=self)
|
||||
|
||||
@@ -3,13 +3,46 @@ from __future__ import absolute_import, division, print_function
|
||||
|
||||
import py
|
||||
import pytest
|
||||
from _pytest.config import PrintHelp
|
||||
import os, sys
|
||||
from argparse import Action
|
||||
|
||||
|
||||
class HelpAction(Action):
|
||||
"""This is an argparse Action that will raise an exception in
|
||||
order to skip the rest of the argument parsing when --help is passed.
|
||||
This prevents argparse from quitting due to missing required arguments
|
||||
when any are defined, for example by ``pytest_addoption``.
|
||||
This is similar to the way that the builtin argparse --help option is
|
||||
implemented by raising SystemExit.
|
||||
"""
|
||||
|
||||
def __init__(self,
|
||||
option_strings,
|
||||
dest=None,
|
||||
default=False,
|
||||
help=None):
|
||||
super(HelpAction, self).__init__(
|
||||
option_strings=option_strings,
|
||||
dest=dest,
|
||||
const=True,
|
||||
default=default,
|
||||
nargs=0,
|
||||
help=help)
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
setattr(namespace, self.dest, self.const)
|
||||
|
||||
# We should only skip the rest of the parsing after preparse is done
|
||||
if getattr(parser._parser, 'after_preparse', False):
|
||||
raise PrintHelp
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
group = parser.getgroup('debugconfig')
|
||||
group.addoption('--version', action="store_true",
|
||||
help="display pytest lib version and import information.")
|
||||
group._addoption("-h", "--help", action="store_true", dest="help",
|
||||
group._addoption("-h", "--help", action=HelpAction, dest="help",
|
||||
help="show help message and configuration info")
|
||||
group._addoption('-p', action="append", dest="plugins", default = [],
|
||||
metavar="name",
|
||||
|
||||
@@ -73,7 +73,9 @@ def pytest_configure(config):
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_cmdline_parse(pluginmanager, args):
|
||||
"""return initialized config object, parsing the specified args. """
|
||||
"""return initialized config object, parsing the specified args.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
def pytest_cmdline_preparse(config, args):
|
||||
"""(deprecated) modify command line arguments before option parsing. """
|
||||
@@ -81,7 +83,9 @@ def pytest_cmdline_preparse(config, args):
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_cmdline_main(config):
|
||||
""" called for performing the main command line action. The default
|
||||
implementation will invoke the configure hooks and runtest_mainloop. """
|
||||
implementation will invoke the configure hooks and runtest_mainloop.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
def pytest_load_initial_conftests(early_config, parser, args):
|
||||
""" implements the loading of initial conftest files ahead
|
||||
@@ -94,7 +98,9 @@ def pytest_load_initial_conftests(early_config, parser, args):
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_collection(session):
|
||||
""" perform the collection protocol for the given session. """
|
||||
""" perform the collection protocol for the given session.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
def pytest_collection_modifyitems(session, config, items):
|
||||
""" called after collection has been performed, may filter or re-order
|
||||
@@ -108,11 +114,15 @@ def pytest_ignore_collect(path, config):
|
||||
""" return True to prevent considering this path for collection.
|
||||
This hook is consulted for all files and directories prior to calling
|
||||
more specific hooks.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult`
|
||||
"""
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_collect_directory(path, parent):
|
||||
""" called before traversing a directory for collection files. """
|
||||
""" called before traversing a directory for collection files.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
def pytest_collect_file(path, parent):
|
||||
""" return collection Node or None for the given path. Any new node
|
||||
@@ -133,7 +143,9 @@ def pytest_deselected(items):
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_make_collect_report(collector):
|
||||
""" perform ``collector.collect()`` and return a CollectReport. """
|
||||
""" perform ``collector.collect()`` and return a CollectReport.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Python test function related hooks
|
||||
@@ -145,15 +157,20 @@ def pytest_pycollect_makemodule(path, parent):
|
||||
This hook will be called for each matching test module path.
|
||||
The pytest_collect_file hook needs to be used if you want to
|
||||
create test modules for files that do not match as a test module.
|
||||
"""
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_pycollect_makeitem(collector, name, obj):
|
||||
""" return custom item/collector for a python object in a module, or None. """
|
||||
""" return custom item/collector for a python object in a module, or None.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_pyfunc_call(pyfuncitem):
|
||||
""" call underlying test function. """
|
||||
""" call underlying test function.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
def pytest_generate_tests(metafunc):
|
||||
""" generate (multiple) parametrized calls to a test function."""
|
||||
@@ -163,7 +180,8 @@ def pytest_make_parametrize_id(config, val, argname):
|
||||
"""Return a user-friendly string representation of the given ``val`` that will be used
|
||||
by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``.
|
||||
The parameter name is available as ``argname``, if required.
|
||||
"""
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# generic runtest related hooks
|
||||
@@ -172,7 +190,9 @@ def pytest_make_parametrize_id(config, val, argname):
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_runtestloop(session):
|
||||
""" called for performing the main runtest loop
|
||||
(after collection finished). """
|
||||
(after collection finished).
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
def pytest_itemstart(item, node):
|
||||
""" (deprecated, use pytest_runtest_logstart). """
|
||||
@@ -190,7 +210,9 @@ def pytest_runtest_protocol(item, nextitem):
|
||||
:py:func:`pytest_runtest_teardown`.
|
||||
|
||||
:return boolean: True if no further hook implementations should be invoked.
|
||||
"""
|
||||
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
def pytest_runtest_logstart(nodeid, location):
|
||||
""" signal the start of running a single test item. """
|
||||
@@ -213,9 +235,10 @@ def pytest_runtest_teardown(item, nextitem):
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_runtest_makereport(item, call):
|
||||
""" return a :py:class:`_pytest.runner.TestReport` object
|
||||
for the given :py:class:`pytest.Item` and
|
||||
for the given :py:class:`pytest.Item <_pytest.main.Item>` and
|
||||
:py:class:`_pytest.runner.CallInfo`.
|
||||
"""
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
def pytest_runtest_logreport(report):
|
||||
""" process a test setup/call/teardown report relating to
|
||||
@@ -227,7 +250,9 @@ def pytest_runtest_logreport(report):
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_fixture_setup(fixturedef, request):
|
||||
""" performs fixture setup execution. """
|
||||
""" performs fixture setup execution.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
def pytest_fixture_post_finalizer(fixturedef):
|
||||
""" called after fixture teardown, but before the cache is cleared so
|
||||
@@ -277,7 +302,9 @@ def pytest_report_header(config, startdir):
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_report_teststatus(report):
|
||||
""" return result-category, shortletter and verbose word for reporting."""
|
||||
""" return result-category, shortletter and verbose word for reporting.
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
def pytest_terminal_summary(terminalreporter, exitstatus):
|
||||
""" add additional section in terminal summary reporting. """
|
||||
@@ -295,7 +322,9 @@ def pytest_logwarning(message, code, nodeid, fslocation):
|
||||
|
||||
@hookspec(firstresult=True)
|
||||
def pytest_doctest_prepare_content(content):
|
||||
""" return processed content for a given doctest"""
|
||||
""" return processed content for a given doctest
|
||||
|
||||
Stops at first non-None result, see :ref:`firstresult` """
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# error handling and internal debugging hooks
|
||||
|
||||
@@ -168,14 +168,13 @@ def pytest_runtestloop(session):
|
||||
|
||||
|
||||
def pytest_ignore_collect(path, config):
|
||||
p = path.dirpath()
|
||||
ignore_paths = config._getconftest_pathlist("collect_ignore", path=p)
|
||||
ignore_paths = config._getconftest_pathlist("collect_ignore", path=path.dirpath())
|
||||
ignore_paths = ignore_paths or []
|
||||
excludeopt = config.getoption("ignore")
|
||||
if excludeopt:
|
||||
ignore_paths.extend([py.path.local(x) for x in excludeopt])
|
||||
|
||||
if path in ignore_paths:
|
||||
if py.path.local(path) in ignore_paths:
|
||||
return True
|
||||
|
||||
# Skip duplicate paths.
|
||||
@@ -763,7 +762,11 @@ class Session(FSCollector):
|
||||
if not has_matched and len(rep.result) == 1 and x.name == "()":
|
||||
nextnames.insert(0, name)
|
||||
resultnodes.extend(self.matchnodes([x], nextnames))
|
||||
node.ihook.pytest_collectreport(report=rep)
|
||||
else:
|
||||
# report collection failures here to avoid failing to run some test
|
||||
# specified in the command line because the module could not be
|
||||
# imported (#134)
|
||||
node.ihook.pytest_collectreport(report=rep)
|
||||
return resultnodes
|
||||
|
||||
def genitems(self, node):
|
||||
|
||||
@@ -27,10 +27,8 @@ def recwarn():
|
||||
|
||||
|
||||
def deprecated_call(func=None, *args, **kwargs):
|
||||
""" assert that calling ``func(*args, **kwargs)`` triggers a
|
||||
``DeprecationWarning`` or ``PendingDeprecationWarning``.
|
||||
|
||||
This function can be used as a context manager::
|
||||
"""context manager that can be used to ensure a block of code triggers a
|
||||
``DeprecationWarning`` or ``PendingDeprecationWarning``::
|
||||
|
||||
>>> import warnings
|
||||
>>> def api_call_v2():
|
||||
@@ -40,38 +38,47 @@ def deprecated_call(func=None, *args, **kwargs):
|
||||
>>> with deprecated_call():
|
||||
... assert api_call_v2() == 200
|
||||
|
||||
Note: we cannot use WarningsRecorder here because it is still subject
|
||||
to the mechanism that prevents warnings of the same type from being
|
||||
triggered twice for the same module. See #1190.
|
||||
``deprecated_call`` can also be used by passing a function and ``*args`` and ``*kwargs``,
|
||||
in which case it will ensure calling ``func(*args, **kwargs)`` produces one of the warnings
|
||||
types above.
|
||||
"""
|
||||
if not func:
|
||||
return WarningsChecker(expected_warning=DeprecationWarning)
|
||||
|
||||
categories = []
|
||||
|
||||
def warn_explicit(message, category, *args, **kwargs):
|
||||
categories.append(category)
|
||||
|
||||
def warn(message, category=None, *args, **kwargs):
|
||||
if isinstance(message, Warning):
|
||||
categories.append(message.__class__)
|
||||
else:
|
||||
categories.append(category)
|
||||
|
||||
old_warn = warnings.warn
|
||||
old_warn_explicit = warnings.warn_explicit
|
||||
warnings.warn_explicit = warn_explicit
|
||||
warnings.warn = warn
|
||||
try:
|
||||
ret = func(*args, **kwargs)
|
||||
finally:
|
||||
warnings.warn_explicit = old_warn_explicit
|
||||
warnings.warn = old_warn
|
||||
deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
|
||||
if not any(issubclass(c, deprecation_categories) for c in categories):
|
||||
return _DeprecatedCallContext()
|
||||
else:
|
||||
__tracebackhide__ = True
|
||||
raise AssertionError("%r did not produce DeprecationWarning" % (func,))
|
||||
return ret
|
||||
with _DeprecatedCallContext():
|
||||
return func(*args, **kwargs)
|
||||
|
||||
|
||||
class _DeprecatedCallContext(object):
|
||||
"""Implements the logic to capture deprecation warnings as a context manager."""
|
||||
|
||||
def __enter__(self):
|
||||
self._captured_categories = []
|
||||
self._old_warn = warnings.warn
|
||||
self._old_warn_explicit = warnings.warn_explicit
|
||||
warnings.warn_explicit = self._warn_explicit
|
||||
warnings.warn = self._warn
|
||||
|
||||
def _warn_explicit(self, message, category, *args, **kwargs):
|
||||
self._captured_categories.append(category)
|
||||
|
||||
def _warn(self, message, category=None, *args, **kwargs):
|
||||
if isinstance(message, Warning):
|
||||
self._captured_categories.append(message.__class__)
|
||||
else:
|
||||
self._captured_categories.append(category)
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
warnings.warn_explicit = self._old_warn_explicit
|
||||
warnings.warn = self._old_warn
|
||||
|
||||
if exc_type is None:
|
||||
deprecation_categories = (DeprecationWarning, PendingDeprecationWarning)
|
||||
if not any(issubclass(c, deprecation_categories) for c in self._captured_categories):
|
||||
__tracebackhide__ = True
|
||||
msg = "Did not produce DeprecationWarning or PendingDeprecationWarning"
|
||||
raise AssertionError(msg)
|
||||
|
||||
|
||||
def warns(expected_warning, *args, **kwargs):
|
||||
|
||||
@@ -282,7 +282,7 @@ class TerminalReporter:
|
||||
line = "collected "
|
||||
else:
|
||||
line = "collecting "
|
||||
line += str(self._numcollected) + " items"
|
||||
line += str(self._numcollected) + " item" + ('' if self._numcollected == 1 else 's')
|
||||
if errors:
|
||||
line += " / %d errors" % errors
|
||||
if skipped:
|
||||
|
||||
@@ -540,7 +540,7 @@ class PluginManager(object):
|
||||
of HookImpl instances and the keyword arguments for the hook call.
|
||||
|
||||
``after(outcome, hook_name, hook_impls, kwargs)`` receives the
|
||||
same arguments as ``before`` but also a :py:class:`_CallOutcome`` object
|
||||
same arguments as ``before`` but also a :py:class:`_CallOutcome <_pytest.vendored_packages.pluggy._CallOutcome>` object
|
||||
which represents the result of the overall hook call.
|
||||
"""
|
||||
return _TracedHookExecution(self, before, after).undo
|
||||
|
||||
@@ -5,6 +5,8 @@ from contextlib import contextmanager
|
||||
|
||||
import pytest
|
||||
|
||||
from _pytest import compat
|
||||
|
||||
|
||||
def _setoption(wmod, arg):
|
||||
"""
|
||||
@@ -51,7 +53,6 @@ def catch_warnings_for_item(item):
|
||||
args = item.config.getoption('pythonwarnings') or []
|
||||
inifilters = item.config.getini("filterwarnings")
|
||||
with warnings.catch_warnings(record=True) as log:
|
||||
warnings.simplefilter('once')
|
||||
for arg in args:
|
||||
warnings._setoption(arg)
|
||||
|
||||
@@ -61,11 +62,25 @@ def catch_warnings_for_item(item):
|
||||
yield
|
||||
|
||||
for warning in log:
|
||||
warn_msg = warning.message
|
||||
unicode_warning = False
|
||||
|
||||
if compat._PY2 and any(isinstance(m, compat.UNICODE_TYPES) for m in warn_msg.args):
|
||||
new_args = [compat.safe_str(m) for m in warn_msg.args]
|
||||
unicode_warning = warn_msg.args != new_args
|
||||
warn_msg.args = new_args
|
||||
|
||||
msg = warnings.formatwarning(
|
||||
warning.message, warning.category,
|
||||
warn_msg, warning.category,
|
||||
warning.filename, warning.lineno, warning.line)
|
||||
item.warn("unused", msg)
|
||||
|
||||
if unicode_warning:
|
||||
warnings.warn(
|
||||
"Warning is using unicode non convertible to ascii, "
|
||||
"converting to a safe representation:\n %s" % msg,
|
||||
UnicodeWarning)
|
||||
|
||||
|
||||
@pytest.hookimpl(hookwrapper=True)
|
||||
def pytest_runtest_protocol(item):
|
||||
|
||||
39
changelog/_template.rst
Normal file
39
changelog/_template.rst
Normal file
@@ -0,0 +1,39 @@
|
||||
{% for section in sections %}
|
||||
{% set underline = "-" %}
|
||||
{% if section %}
|
||||
{{section}}
|
||||
{{ underline * section|length }}{% set underline = "~" %}
|
||||
|
||||
{% endif %}
|
||||
{% if sections[section] %}
|
||||
{% for category, val in definitions.items() if category in sections[section] %}
|
||||
|
||||
{{ definitions[category]['name'] }}
|
||||
{{ underline * definitions[category]['name']|length }}
|
||||
|
||||
{% if definitions[category]['showcontent'] %}
|
||||
{% for text, values in sections[section][category]|dictsort(by='value') %}
|
||||
- {{ text }}{% if category != 'vendor' %} (`{{ values[0] }} <https://github.com/pytest-dev/pytest/issues/{{ values[0][1:] }}>`_){% endif %}
|
||||
|
||||
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
- {{ sections[section][category]['']|sort|join(', ') }}
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% if sections[section][category]|length == 0 %}
|
||||
|
||||
No significant changes.
|
||||
|
||||
|
||||
{% else %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
|
||||
No significant changes.
|
||||
|
||||
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
@@ -6,6 +6,9 @@ Release announcements
|
||||
:maxdepth: 2
|
||||
|
||||
|
||||
release-3.1.3
|
||||
release-3.1.2
|
||||
release-3.1.1
|
||||
release-3.1.0
|
||||
release-3.0.7
|
||||
release-3.0.6
|
||||
|
||||
23
doc/en/announce/release-3.1.1.rst
Normal file
23
doc/en/announce/release-3.1.1.rst
Normal file
@@ -0,0 +1,23 @@
|
||||
pytest-3.1.1
|
||||
=======================================
|
||||
|
||||
pytest 3.1.1 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Bruno Oliveira
|
||||
* Florian Bruhin
|
||||
* Floris Bruynooghe
|
||||
* Jason R. Coombs
|
||||
* Ronny Pfannschmidt
|
||||
* wanghui
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
23
doc/en/announce/release-3.1.2.rst
Normal file
23
doc/en/announce/release-3.1.2.rst
Normal file
@@ -0,0 +1,23 @@
|
||||
pytest-3.1.2
|
||||
=======================================
|
||||
|
||||
pytest 3.1.2 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Andreas Pelme
|
||||
* ApaDoctor
|
||||
* Bruno Oliveira
|
||||
* Florian Bruhin
|
||||
* Ronny Pfannschmidt
|
||||
* Segev Finer
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
23
doc/en/announce/release-3.1.3.rst
Normal file
23
doc/en/announce/release-3.1.3.rst
Normal file
@@ -0,0 +1,23 @@
|
||||
pytest-3.1.3
|
||||
=======================================
|
||||
|
||||
pytest 3.1.3 has just been released to PyPI.
|
||||
|
||||
This is a bug-fix release, being a drop-in replacement. To upgrade::
|
||||
|
||||
pip install --upgrade pytest
|
||||
|
||||
The full changelog is available at http://doc.pytest.org/en/latest/changelog.html.
|
||||
|
||||
Thanks to all who contributed to this release, among them:
|
||||
|
||||
* Antoine Legrand
|
||||
* Bruno Oliveira
|
||||
* Max Moroz
|
||||
* Raphael Pierzina
|
||||
* Ronny Pfannschmidt
|
||||
* Ryan Fitzpatrick
|
||||
|
||||
|
||||
Happy testing,
|
||||
The pytest Development Team
|
||||
@@ -28,7 +28,7 @@ you will see the return value of the function call::
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
collected 1 item
|
||||
|
||||
test_assert1.py F
|
||||
|
||||
@@ -172,7 +172,7 @@ if you run this module::
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
collected 1 item
|
||||
|
||||
test_assert2.py F
|
||||
|
||||
|
||||
@@ -231,10 +231,10 @@ You can always peek at the content of the cache using the
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
cachedir: $REGENDOC_TMPDIR/.cache
|
||||
------------------------------- cache values -------------------------------
|
||||
example/value contains:
|
||||
42
|
||||
cache/lastfailed contains:
|
||||
{'test_caching.py::test_function': True}
|
||||
example/value contains:
|
||||
42
|
||||
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ then you can just invoke ``pytest`` without command line options::
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
|
||||
collected 1 items
|
||||
collected 1 item
|
||||
|
||||
mymodule.py .
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ tests based on their module, class, method, or function name::
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 5 items
|
||||
collecting ... collected 1 item
|
||||
|
||||
test_server.py::TestClass::test_method PASSED
|
||||
|
||||
@@ -82,7 +82,7 @@ You can also select on the class::
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 4 items
|
||||
collecting ... collected 1 item
|
||||
|
||||
test_server.py::TestClass::test_method PASSED
|
||||
|
||||
@@ -95,7 +95,7 @@ Or select multiple nodes::
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y -- $PYTHON_PREFIX/bin/python3.5
|
||||
cachedir: .cache
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collecting ... collected 8 items
|
||||
collecting ... collected 2 items
|
||||
|
||||
test_server.py::TestClass::test_method PASSED
|
||||
test_server.py::test_send_http PASSED
|
||||
@@ -354,7 +354,7 @@ the test needs::
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
collected 1 item
|
||||
|
||||
test_someenv.py s
|
||||
|
||||
@@ -366,7 +366,7 @@ and here is one that specifies exactly the environment needed::
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
collected 1 item
|
||||
|
||||
test_someenv.py .
|
||||
|
||||
|
||||
@@ -116,6 +116,15 @@ the argument name::
|
||||
diff = a - b
|
||||
assert diff == expected
|
||||
|
||||
@pytest.mark.parametrize("a,b,expected", [
|
||||
pytest.param(datetime(2001, 12, 12), datetime(2001, 12, 11),
|
||||
timedelta(1), id='forward'),
|
||||
pytest.param(datetime(2001, 12, 11), datetime(2001, 12, 12),
|
||||
timedelta(-1), id='backward'),
|
||||
])
|
||||
def test_timedistance_v3(a, b, expected):
|
||||
diff = a - b
|
||||
assert diff == expected
|
||||
|
||||
In ``test_timedistance_v0``, we let pytest generate the test IDs.
|
||||
|
||||
@@ -132,7 +141,7 @@ objects, they are still using the default pytest representation::
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 6 items
|
||||
collected 8 items
|
||||
<Module 'test_time.py'>
|
||||
<Function 'test_timedistance_v0[a0-b0-expected0]'>
|
||||
<Function 'test_timedistance_v0[a1-b1-expected1]'>
|
||||
@@ -140,9 +149,14 @@ objects, they are still using the default pytest representation::
|
||||
<Function 'test_timedistance_v1[backward]'>
|
||||
<Function 'test_timedistance_v2[20011212-20011211-expected0]'>
|
||||
<Function 'test_timedistance_v2[20011211-20011212-expected1]'>
|
||||
<Function 'test_timedistance_v3[forward]'>
|
||||
<Function 'test_timedistance_v3[backward]'>
|
||||
|
||||
======= no tests ran in 0.12 seconds ========
|
||||
|
||||
In ``test_timedistance_v3``, we used ``pytest.param`` to specify the test IDs
|
||||
together with the actual data, instead of listing them separately.
|
||||
|
||||
A quick port of "testscenarios"
|
||||
------------------------------------
|
||||
|
||||
@@ -322,7 +336,7 @@ The result of this test will be successful::
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
collected 1 item
|
||||
<Module 'test_indirect_list.py'>
|
||||
<Function 'test_indirect[a-b]'>
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ using it::
|
||||
@pytest.fixture
|
||||
def smtp():
|
||||
import smtplib
|
||||
return smtplib.SMTP("smtp.gmail.com")
|
||||
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
|
||||
|
||||
def test_ehlo(smtp):
|
||||
response, msg = smtp.ehlo()
|
||||
@@ -72,7 +72,7 @@ marked ``smtp`` fixture function. Running the test looks like this::
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
collected 1 item
|
||||
|
||||
test_smtpsimple.py F
|
||||
|
||||
@@ -123,20 +123,13 @@ with a list of available function arguments.
|
||||
but is not anymore advertised as the primary means of declaring fixture
|
||||
functions.
|
||||
|
||||
"Funcargs" a prime example of dependency injection
|
||||
Fixtures: a prime example of dependency injection
|
||||
---------------------------------------------------
|
||||
|
||||
When injecting fixtures to test functions, pytest-2.0 introduced the
|
||||
term "funcargs" or "funcarg mechanism" which continues to be present
|
||||
also in docs today. It now refers to the specific case of injecting
|
||||
fixture values as arguments to test functions. With pytest-2.3 there are
|
||||
more possibilities to use fixtures but "funcargs" remain as the main way
|
||||
as they allow to directly state the dependencies of a test function.
|
||||
|
||||
As the following examples show in more detail, funcargs allow test
|
||||
functions to easily receive and work against specific pre-initialized
|
||||
application objects without having to care about import/setup/cleanup
|
||||
details. It's a prime example of `dependency injection`_ where fixture
|
||||
Fixtures allow test functions to easily receive and work
|
||||
against specific pre-initialized application objects without having
|
||||
to care about import/setup/cleanup details.
|
||||
It's a prime example of `dependency injection`_ where fixture
|
||||
functions take the role of the *injector* and test functions are the
|
||||
*consumers* of fixture objects.
|
||||
|
||||
@@ -164,7 +157,7 @@ access the fixture function::
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def smtp():
|
||||
return smtplib.SMTP("smtp.gmail.com")
|
||||
return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
|
||||
|
||||
The name of the fixture again is ``smtp`` and you can access its result by
|
||||
listing the name ``smtp`` as an input parameter in any test or fixture
|
||||
@@ -176,7 +169,7 @@ function (in or below the directory where ``conftest.py`` is located)::
|
||||
response, msg = smtp.ehlo()
|
||||
assert response == 250
|
||||
assert b"smtp.gmail.com" in msg
|
||||
assert 0 # for demo purposes
|
||||
assert 0 # for demo purposes
|
||||
|
||||
def test_noop(smtp):
|
||||
response, msg = smtp.noop()
|
||||
@@ -254,7 +247,7 @@ the code after the *yield* statement serves as the teardown code:
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def smtp():
|
||||
smtp = smtplib.SMTP("smtp.gmail.com")
|
||||
smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
|
||||
yield smtp # provide the fixture value
|
||||
print("teardown smtp")
|
||||
smtp.close()
|
||||
@@ -288,7 +281,7 @@ Note that we can also seamlessly use the ``yield`` syntax with ``with`` statemen
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def smtp():
|
||||
with smtplib.SMTP("smtp.gmail.com") as smtp:
|
||||
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp:
|
||||
yield smtp # provide the fixture value
|
||||
|
||||
|
||||
@@ -296,6 +289,9 @@ The ``smtp`` connection will be closed after the test finished execution
|
||||
because the ``smtp`` object automatically closes when
|
||||
the ``with`` statement ends.
|
||||
|
||||
Note that if an exception happens during the *setup* code (before the ``yield`` keyword), the
|
||||
*teardown* code (after the ``yield``) will not be called.
|
||||
|
||||
|
||||
.. note::
|
||||
Prior to version 2.10, in order to use a ``yield`` statement to execute teardown code one
|
||||
@@ -303,29 +299,51 @@ the ``with`` statement ends.
|
||||
fixtures can use ``yield`` directly so the ``yield_fixture`` decorator is no longer needed
|
||||
and considered deprecated.
|
||||
|
||||
.. note::
|
||||
As historical note, another way to write teardown code is
|
||||
by accepting a ``request`` object into your fixture function and can call its
|
||||
``request.addfinalizer`` one or multiple times::
|
||||
|
||||
# content of conftest.py
|
||||
An alternative option for executing *teardown* code is to
|
||||
make use of the ``addfinalizer`` method of the `request-context`_ object to register
|
||||
finalization functions.
|
||||
|
||||
import smtplib
|
||||
import pytest
|
||||
Here's the ``smtp`` fixture changed to use ``addfinalizer`` for cleanup:
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def smtp(request):
|
||||
smtp = smtplib.SMTP("smtp.gmail.com")
|
||||
def fin():
|
||||
print ("teardown smtp")
|
||||
smtp.close()
|
||||
request.addfinalizer(fin)
|
||||
return smtp # provide the fixture value
|
||||
.. code-block:: python
|
||||
|
||||
The ``fin`` function will execute when the last test in the module has finished execution.
|
||||
# content of conftest.py
|
||||
import smtplib
|
||||
import pytest
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def smtp(request):
|
||||
smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
|
||||
def fin():
|
||||
print ("teardown smtp")
|
||||
smtp.close()
|
||||
request.addfinalizer(fin)
|
||||
return smtp # provide the fixture value
|
||||
|
||||
|
||||
Both ``yield`` and ``addfinalizer`` methods work similarly by calling their code after the test
|
||||
ends, but ``addfinalizer`` has two key differences over ``yield``:
|
||||
|
||||
1. It is possible to register multiple finalizer functions.
|
||||
|
||||
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
|
||||
def equipments(request):
|
||||
r = []
|
||||
for port in ('C1', 'C3', 'C28'):
|
||||
equip = connect(port)
|
||||
request.addfinalizer(equip.disconnect)
|
||||
r.append(equip)
|
||||
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
|
||||
registered then it will not be executed.
|
||||
|
||||
This method is still fully supported, but ``yield`` is recommended from 2.10 onward because
|
||||
it is considered simpler and better describes the natural code flow.
|
||||
|
||||
.. _`request-context`:
|
||||
|
||||
@@ -344,7 +362,7 @@ read an optional server URL from the test module which uses our fixture::
|
||||
@pytest.fixture(scope="module")
|
||||
def smtp(request):
|
||||
server = getattr(request.module, "smtpserver", "smtp.gmail.com")
|
||||
smtp = smtplib.SMTP(server)
|
||||
smtp = smtplib.SMTP(server, 587, timeout=5)
|
||||
yield smtp
|
||||
print ("finalizing %s (%s)" % (smtp, server))
|
||||
smtp.close()
|
||||
@@ -408,7 +426,7 @@ through the special :py:class:`request <FixtureRequest>` object::
|
||||
@pytest.fixture(scope="module",
|
||||
params=["smtp.gmail.com", "mail.python.org"])
|
||||
def smtp(request):
|
||||
smtp = smtplib.SMTP(request.param)
|
||||
smtp = smtplib.SMTP(request.param, 587, timeout=5)
|
||||
yield smtp
|
||||
print ("finalizing %s" % smtp)
|
||||
smtp.close()
|
||||
@@ -453,7 +471,7 @@ So let's just do another run::
|
||||
response, msg = smtp.ehlo()
|
||||
assert response == 250
|
||||
> assert b"smtp.gmail.com" in msg
|
||||
E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nSIZE 51200000\nETRN\nSTARTTLS\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8'
|
||||
E AssertionError: assert b'smtp.gmail.com' in b'mail.python.org\nPIPELINING\nSIZE 51200000\nETRN\nSTARTTLS\nAUTH DIGEST-MD5 NTLM CRAM-MD5\nENHANCEDSTATUSCODES\n8BITMIME\nDSN\nSMTPUTF8'
|
||||
|
||||
test_module.py:5: AssertionError
|
||||
-------------------------- Captured stdout setup ---------------------------
|
||||
@@ -782,8 +800,8 @@ Autouse fixtures (xUnit setup on steroids)
|
||||
.. regendoc:wipe
|
||||
|
||||
Occasionally, you may want to have fixtures get invoked automatically
|
||||
without a `usefixtures`_ or `funcargs`_ reference. As a practical
|
||||
example, suppose we have a database fixture which has a
|
||||
without declaring a function argument explicitly or a `usefixtures`_ decorator.
|
||||
As a practical example, suppose we have a database fixture which has a
|
||||
begin/rollback/commit architecture and we want to automatically surround
|
||||
each test method by a transaction and a rollback. Here is a dummy
|
||||
self-contained implementation of this idea::
|
||||
|
||||
@@ -48,7 +48,7 @@ That's it. You can execute the test function now::
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
collected 1 item
|
||||
|
||||
test_sample.py F
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ To execute it::
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
collected 1 item
|
||||
|
||||
test_sample.py F
|
||||
|
||||
@@ -83,8 +83,8 @@ Consult the :ref:`Changelog <changelog>` page for fixes and enhancements of each
|
||||
License
|
||||
-------
|
||||
|
||||
Copyright Holger Krekel and others, 2004-2016.
|
||||
Copyright Holger Krekel and others, 2004-2017.
|
||||
|
||||
Distributed under the terms of the `MIT`_ license, pytest is free and open source software.
|
||||
|
||||
.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE
|
||||
.. _`MIT`: https://github.com/pytest-dev/pytest/blob/master/LICENSE
|
||||
|
||||
@@ -9,7 +9,7 @@ Distributed under the terms of the `MIT`_ license, pytest is free and open sourc
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2004-2016 Holger Krekel and others
|
||||
Copyright (c) 2004-2017 Holger Krekel and others
|
||||
|
||||
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
|
||||
|
||||
@@ -5,14 +5,17 @@
|
||||
Skip and xfail: dealing with tests that cannot succeed
|
||||
=====================================================================
|
||||
|
||||
If you have test functions that cannot be run on certain platforms
|
||||
or that you expect to fail you can mark them accordingly or you
|
||||
may call helper functions during execution of setup or test functions.
|
||||
You can mark test functions that cannot be run on certain platforms
|
||||
or that you expect to fail so pytest can deal with them accordingly and
|
||||
present a summary of the test session, while keeping the test suite *green*.
|
||||
|
||||
A *skip* means that you expect your test to pass unless the environment
|
||||
(e.g. wrong Python interpreter, missing dependency) prevents it to run.
|
||||
And *xfail* means that your test can run but you expect it to fail
|
||||
because there is an implementation problem.
|
||||
A **skip** means that you expect your test to pass only if some conditions are met,
|
||||
otherwise pytest should skip running the test altogether. Common examples are skipping
|
||||
windows-only tests on non-windows platforms, or skipping tests that depend on an external
|
||||
resource which is not available at the moment (for example a database).
|
||||
|
||||
A **xfail** means that you expect a test to fail for some reason.
|
||||
A common example is a test for a feature not yet implemented, or a bug not yet fixed.
|
||||
|
||||
``pytest`` counts and lists *skip* and *xfail* tests separately. Detailed
|
||||
information about skipped/xfailed tests is not shown by default to avoid
|
||||
@@ -26,8 +29,8 @@ corresponding to the "short" letters shown in the test progress::
|
||||
.. _skipif:
|
||||
.. _`condition booleans`:
|
||||
|
||||
Marking a test function to be skipped
|
||||
-------------------------------------------
|
||||
Skipping test functions
|
||||
-----------------------
|
||||
|
||||
.. versionadded:: 2.9
|
||||
|
||||
@@ -40,10 +43,23 @@ which may be passed an optional ``reason``:
|
||||
def test_the_unknown():
|
||||
...
|
||||
|
||||
|
||||
Alternatively, it is also possible to skip imperatively during test execution or setup
|
||||
by calling the ``pytest.skip(reason)`` function:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_function():
|
||||
if not valid_config():
|
||||
pytest.skip("unsupported configuration")
|
||||
|
||||
The imperative method is useful when it is not possible to evaluate the skip condition
|
||||
during import time.
|
||||
|
||||
``skipif``
|
||||
~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 2.0, 2.4
|
||||
.. versionadded:: 2.0
|
||||
|
||||
If you wish to skip something conditionally then you can use ``skipif`` instead.
|
||||
Here is an example of marking a test function to be skipped
|
||||
@@ -55,16 +71,12 @@ when run on a Python3.3 interpreter::
|
||||
def test_function():
|
||||
...
|
||||
|
||||
During test function setup the condition ("sys.version_info >= (3,3)") is
|
||||
checked. If it evaluates to True, the test function will be skipped
|
||||
with the specified reason. Note that pytest enforces specifying a reason
|
||||
in order to report meaningful "skip reasons" (e.g. when using ``-rs``).
|
||||
If the condition is a string, it will be evaluated as python expression.
|
||||
If the condition evaluates to ``True`` during collection, the test function will be skipped,
|
||||
with the specified reason appearing in the summary when using ``-rs``.
|
||||
|
||||
You can share skipif markers between modules. Consider this test module::
|
||||
You can share ``skipif`` markers between modules. Consider this test module::
|
||||
|
||||
# content of test_mymodule.py
|
||||
|
||||
import mymodule
|
||||
minversion = pytest.mark.skipif(mymodule.__versioninfo__ < (1,1),
|
||||
reason="at least mymodule-1.1 required")
|
||||
@@ -72,7 +84,7 @@ You can share skipif markers between modules. Consider this test module::
|
||||
def test_function():
|
||||
...
|
||||
|
||||
You can import it from another test module::
|
||||
You can import the marker and reuse it in another test module::
|
||||
|
||||
# test_myothermodule.py
|
||||
from test_mymodule import minversion
|
||||
@@ -85,16 +97,15 @@ For larger test suites it's usually a good idea to have one file
|
||||
where you define the markers which you then consistently apply
|
||||
throughout your test suite.
|
||||
|
||||
Alternatively, the pre pytest-2.4 way to specify :ref:`condition strings
|
||||
<string conditions>` instead of booleans will remain fully supported in future
|
||||
versions of pytest. It couldn't be easily used for importing markers
|
||||
between test modules so it's no longer advertised as the primary method.
|
||||
Alternatively, you can use :ref:`condition strings
|
||||
<string conditions>` instead of booleans, but they can't be shared between modules easily
|
||||
so they are supported mainly for backward compatibility reasons.
|
||||
|
||||
|
||||
Skip all test functions of a class or module
|
||||
---------------------------------------------
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can use the ``skipif`` decorator (and any other marker) on classes::
|
||||
You can use the ``skipif`` marker (as any other marker) on classes::
|
||||
|
||||
@pytest.mark.skipif(sys.platform == 'win32',
|
||||
reason="does not run on windows")
|
||||
@@ -103,10 +114,10 @@ You can use the ``skipif`` decorator (and any other marker) on classes::
|
||||
def test_function(self):
|
||||
"will not be setup or run under 'win32' platform"
|
||||
|
||||
If the condition is true, this marker will produce a skip result for
|
||||
each of the test methods.
|
||||
If the condition is ``True``, this marker will produce a skip result for
|
||||
each of the test methods of that class.
|
||||
|
||||
If you want to skip all test functions of a module, you must use
|
||||
If you want to skip all test functions of a module, you may use
|
||||
the ``pytestmark`` name on the global level:
|
||||
|
||||
.. code-block:: python
|
||||
@@ -114,15 +125,57 @@ the ``pytestmark`` name on the global level:
|
||||
# test_module.py
|
||||
pytestmark = pytest.mark.skipif(...)
|
||||
|
||||
If multiple "skipif" decorators are applied to a test function, it
|
||||
If multiple ``skipif`` decorators are applied to a test function, it
|
||||
will be skipped if any of the skip conditions is true.
|
||||
|
||||
.. _`whole class- or module level`: mark.html#scoped-marking
|
||||
|
||||
|
||||
Skipping on a missing import dependency
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
You can use the following helper at module level
|
||||
or within a test or test setup function::
|
||||
|
||||
docutils = pytest.importorskip("docutils")
|
||||
|
||||
If ``docutils`` cannot be imported here, this will lead to a
|
||||
skip outcome of the test. You can also skip based on the
|
||||
version number of a library::
|
||||
|
||||
docutils = pytest.importorskip("docutils", minversion="0.3")
|
||||
|
||||
The version will be read from the specified
|
||||
module's ``__version__`` attribute.
|
||||
|
||||
Summary
|
||||
~~~~~~~
|
||||
|
||||
Here's a quick guide on how to skip tests in a module in different situations:
|
||||
|
||||
1. Skip all tests in a module unconditionally:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytestmark = pytest.mark.skip('all tests still WIP')
|
||||
|
||||
2. Skip all tests in a module based on some condition:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytestmark = pytest.mark.skipif(sys.platform == 'win32', 'tests for linux only')
|
||||
|
||||
3. Skip all tests in a module if some import is missing:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pexpect = pytest.importorskip('pexpect')
|
||||
|
||||
|
||||
.. _xfail:
|
||||
|
||||
Mark a test function as expected to fail
|
||||
-------------------------------------------------------
|
||||
XFail: mark test functions as expected to fail
|
||||
----------------------------------------------
|
||||
|
||||
You can use the ``xfail`` marker to indicate that you
|
||||
expect a test to fail::
|
||||
@@ -135,6 +188,29 @@ This test will be run but no traceback will be reported
|
||||
when it fails. Instead terminal reporting will list it in the
|
||||
"expected to fail" (``XFAIL``) or "unexpectedly passing" (``XPASS``) sections.
|
||||
|
||||
Alternatively, you can also mark a test as ``XFAIL`` from within a test or setup function
|
||||
imperatively:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def test_function():
|
||||
if not valid_config():
|
||||
pytest.xfail("failing configuration (but should work)")
|
||||
|
||||
This will unconditionally make ``test_function`` ``XFAIL``. Note that no other code is executed
|
||||
after ``pytest.xfail`` call, differently from the marker. That's because it is implemented
|
||||
internally by raising a known exception.
|
||||
|
||||
Here's the signature of the ``xfail`` **marker** (not the function), using Python 3 keyword-only
|
||||
arguments syntax:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def xfail(condition=None, *, reason=None, raises=None, run=True, strict=False):
|
||||
|
||||
|
||||
|
||||
|
||||
``strict`` parameter
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -200,18 +276,19 @@ even executed, use the ``run`` parameter as ``False``:
|
||||
def test_function():
|
||||
...
|
||||
|
||||
This is specially useful for marking crashing tests for later inspection.
|
||||
This is specially useful for xfailing tests that are crashing the interpreter and should be
|
||||
investigated later.
|
||||
|
||||
|
||||
Ignoring xfail marks
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
Ignoring xfail
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
By specifying on the commandline::
|
||||
|
||||
pytest --runxfail
|
||||
|
||||
you can force the running and reporting of an ``xfail`` marked test
|
||||
as if it weren't marked at all.
|
||||
as if it weren't marked at all. This also causes ``pytest.xfail`` to produce no effect.
|
||||
|
||||
Examples
|
||||
~~~~~~~~
|
||||
@@ -245,91 +322,35 @@ Running it with the report-on-xfail option gives this output::
|
||||
|
||||
======= 7 xfailed in 0.12 seconds ========
|
||||
|
||||
xfail signature summary
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Here's the signature of the ``xfail`` marker, using Python 3 keyword-only
|
||||
arguments syntax:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
def xfail(condition=None, *, reason=None, raises=None, run=True, strict=False):
|
||||
|
||||
|
||||
|
||||
.. _`skip/xfail with parametrize`:
|
||||
|
||||
Skip/xfail with parametrize
|
||||
---------------------------
|
||||
|
||||
It is possible to apply markers like skip and xfail to individual
|
||||
test instances when using parametrize::
|
||||
test instances when using parametrize:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import pytest
|
||||
|
||||
@pytest.mark.parametrize(("n", "expected"), [
|
||||
(1, 2),
|
||||
pytest.mark.xfail((1, 0)),
|
||||
pytest.mark.xfail(reason="some bug")((1, 3)),
|
||||
pytest.param(1, 0, marks=pytest.mark.xfail),
|
||||
pytest.param(1, 3, marks=pytest.mark.xfail(reason="some bug")),
|
||||
(2, 3),
|
||||
(3, 4),
|
||||
(4, 5),
|
||||
pytest.mark.skipif("sys.version_info >= (3,0)")((10, 11)),
|
||||
pytest.param(10, 11, marks=pytest.mark.skipif(sys.version_info >= (3, 0), reason="py2k")),
|
||||
])
|
||||
def test_increment(n, expected):
|
||||
assert n + 1 == expected
|
||||
|
||||
|
||||
Imperative xfail from within a test or setup function
|
||||
------------------------------------------------------
|
||||
|
||||
If you cannot declare xfail- of skipif conditions at import
|
||||
time you can also imperatively produce an according outcome
|
||||
imperatively, in test or setup code::
|
||||
|
||||
def test_function():
|
||||
if not valid_config():
|
||||
pytest.xfail("failing configuration (but should work)")
|
||||
# or
|
||||
pytest.skip("unsupported configuration")
|
||||
|
||||
Note that calling ``pytest.skip`` at the module level
|
||||
is not allowed since pytest 3.0. If you are upgrading
|
||||
and ``pytest.skip`` was being used at the module level, you can set a
|
||||
``pytestmark`` variable:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# before pytest 3.0
|
||||
pytest.skip('skipping all tests because of reasons')
|
||||
# after pytest 3.0
|
||||
pytestmark = pytest.mark.skip('skipping all tests because of reasons')
|
||||
|
||||
``pytestmark`` applies a mark or list of marks to all tests in a module.
|
||||
|
||||
|
||||
Skipping on a missing import dependency
|
||||
--------------------------------------------------
|
||||
|
||||
You can use the following import helper at module level
|
||||
or within a test or test setup function::
|
||||
|
||||
docutils = pytest.importorskip("docutils")
|
||||
|
||||
If ``docutils`` cannot be imported here, this will lead to a
|
||||
skip outcome of the test. You can also skip based on the
|
||||
version number of a library::
|
||||
|
||||
docutils = pytest.importorskip("docutils", minversion="0.3")
|
||||
|
||||
The version will be read from the specified
|
||||
module's ``__version__`` attribute.
|
||||
|
||||
|
||||
.. _string conditions:
|
||||
|
||||
specifying conditions as strings versus booleans
|
||||
----------------------------------------------------------
|
||||
Conditions as strings instead of booleans
|
||||
-----------------------------------------
|
||||
|
||||
Prior to pytest-2.4 the only way to specify skipif/xfail conditions was
|
||||
to use strings::
|
||||
@@ -346,7 +367,7 @@ all the module globals, and ``os`` and ``sys`` as a minimum.
|
||||
Since pytest-2.4 `condition booleans`_ are considered preferable
|
||||
because markers can then be freely imported between test modules.
|
||||
With strings you need to import not only the marker but all variables
|
||||
everything used by the marker, which violates encapsulation.
|
||||
used by the marker, which violates encapsulation.
|
||||
|
||||
The reason for specifying the condition as a string was that ``pytest`` can
|
||||
report a summary of skip conditions based purely on the condition string.
|
||||
@@ -387,25 +408,3 @@ The equivalent with "boolean conditions" is::
|
||||
``config.getvalue()`` will not execute correctly.
|
||||
|
||||
|
||||
Summary
|
||||
-------
|
||||
|
||||
Here's a quick guide on how to skip tests in a module in different situations:
|
||||
|
||||
1. Skip all tests in a module unconditionally:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytestmark = pytest.mark.skip('all tests still WIP')
|
||||
|
||||
2. Skip all tests in a module based on some condition:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pytestmark = pytest.mark.skipif(sys.platform == 'win32', 'tests for linux only')
|
||||
|
||||
3. Skip all tests in a module if some import is missing:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
pexpect = pytest.importorskip('pexpect')
|
||||
|
||||
@@ -31,7 +31,7 @@ Running this would result in a passed test except for the last
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
collected 1 item
|
||||
|
||||
test_tmpdir.py F
|
||||
|
||||
|
||||
@@ -5,18 +5,18 @@ Warnings Capture
|
||||
|
||||
.. versionadded:: 3.1
|
||||
|
||||
Starting from version ``3.1``, pytest now automatically catches all warnings during test execution
|
||||
Starting from version ``3.1``, pytest now automatically catches warnings during test execution
|
||||
and displays them at the end of the session::
|
||||
|
||||
# content of test_show_warnings.py
|
||||
import warnings
|
||||
|
||||
def deprecated_function():
|
||||
warnings.warn("this function is deprecated, use another_function()", DeprecationWarning)
|
||||
def api_v1():
|
||||
warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||
return 1
|
||||
|
||||
def test_one():
|
||||
assert deprecated_function() == 1
|
||||
assert api_v1() == 1
|
||||
|
||||
Running pytest now produces this output::
|
||||
|
||||
@@ -24,41 +24,43 @@ Running pytest now produces this output::
|
||||
======= test session starts ========
|
||||
platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
|
||||
rootdir: $REGENDOC_TMPDIR, inifile:
|
||||
collected 1 items
|
||||
collected 1 item
|
||||
|
||||
test_show_warnings.py .
|
||||
|
||||
======= warnings summary ========
|
||||
test_show_warnings.py::test_one
|
||||
$REGENDOC_TMPDIR/test_show_warnings.py:4: DeprecationWarning: this function is deprecated, use another_function()
|
||||
warnings.warn("this function is deprecated, use another_function()", DeprecationWarning)
|
||||
$REGENDOC_TMPDIR/test_show_warnings.py:4: UserWarning: api v1, should use functions from v2
|
||||
warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||
|
||||
-- Docs: http://doc.pytest.org/en/latest/warnings.html
|
||||
======= 1 passed, 1 warnings in 0.12 seconds ========
|
||||
|
||||
Pytest by default catches all warnings except for ``DeprecationWarning`` and ``PendingDeprecationWarning``.
|
||||
|
||||
The ``-W`` flag can be passed to control which warnings will be displayed or even turn
|
||||
them into errors::
|
||||
|
||||
$ pytest -q test_show_warnings.py -W error::DeprecationWarning
|
||||
$ pytest -q test_show_warnings.py -W error::UserWarning
|
||||
F
|
||||
======= FAILURES ========
|
||||
_______ test_one ________
|
||||
|
||||
def test_one():
|
||||
> assert deprecated_function() == 1
|
||||
> assert api_v1() == 1
|
||||
|
||||
test_show_warnings.py:8:
|
||||
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
|
||||
|
||||
def deprecated_function():
|
||||
> warnings.warn("this function is deprecated, use another_function()", DeprecationWarning)
|
||||
E DeprecationWarning: this function is deprecated, use another_function()
|
||||
def api_v1():
|
||||
> warnings.warn(UserWarning("api v1, should use functions from v2"))
|
||||
E UserWarning: api v1, should use functions from v2
|
||||
|
||||
test_show_warnings.py:4: DeprecationWarning
|
||||
test_show_warnings.py:4: UserWarning
|
||||
1 failed in 0.12 seconds
|
||||
|
||||
The same option can be set in the ``pytest.ini`` file using the ``filterwarnings`` ini option.
|
||||
For example, the configuration below will ignore all deprecation warnings, but will transform
|
||||
For example, the configuration below will ignore all user warnings, but will transform
|
||||
all other warnings into errors.
|
||||
|
||||
.. code-block:: ini
|
||||
@@ -66,7 +68,7 @@ all other warnings into errors.
|
||||
[pytest]
|
||||
filterwarnings =
|
||||
error
|
||||
ignore::DeprecationWarning
|
||||
ignore::UserWarning
|
||||
|
||||
|
||||
When a warning matches more than one option in the list, the action for the last matching option
|
||||
@@ -76,6 +78,19 @@ Both ``-W`` command-line option and ``filterwarnings`` ini option are based on P
|
||||
`-W option`_ and `warnings.simplefilter`_, so please refer to those sections in the Python
|
||||
documentation for other examples and advanced usage.
|
||||
|
||||
.. note::
|
||||
|
||||
``DeprecationWarning`` and ``PendingDeprecationWarning`` are hidden by the standard library
|
||||
by default so you have to explicitly configure them to be displayed in your ``pytest.ini``:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
filterwarnings =
|
||||
once::DeprecationWarning
|
||||
once::PendingDeprecationWarning
|
||||
|
||||
|
||||
*Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_
|
||||
*plugin.*
|
||||
|
||||
@@ -83,6 +98,19 @@ documentation for other examples and advanced usage.
|
||||
.. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter
|
||||
.. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings
|
||||
|
||||
|
||||
Disabling warning capture
|
||||
-------------------------
|
||||
|
||||
This feature is enabled by default but can be disabled entirely in your ``pytest.ini`` file with:
|
||||
|
||||
.. code-block:: ini
|
||||
|
||||
[pytest]
|
||||
addopts = -p no:warnings
|
||||
|
||||
Or passing ``-p no:warnings`` in the command-line.
|
||||
|
||||
.. _`asserting warnings`:
|
||||
|
||||
.. _assertwarnings:
|
||||
|
||||
@@ -255,11 +255,11 @@ if ``myapp.testsupport.myplugin`` also declares ``pytest_plugins``, the contents
|
||||
of the variable will also be loaded as plugins, and so on.
|
||||
|
||||
This mechanism makes it easy to share fixtures within applications or even
|
||||
external applications without the need to create external plugins using
|
||||
external applications without the need to create external plugins using
|
||||
the ``setuptools``'s entry point technique.
|
||||
|
||||
Plugins imported by ``pytest_plugins`` will also automatically be marked
|
||||
for assertion rewriting (see :func:`pytest.register_assert_rewrite`).
|
||||
for assertion rewriting (see :func:`pytest.register_assert_rewrite`).
|
||||
However for this to have any effect the module must not be
|
||||
imported already; if it was already imported at the time the
|
||||
``pytest_plugins`` statement is processed, a warning will result and
|
||||
@@ -357,6 +357,8 @@ allowed to raise exceptions. Doing so will break the pytest run.
|
||||
|
||||
|
||||
|
||||
.. _firstresult:
|
||||
|
||||
firstresult: stop at first non-None result
|
||||
-------------------------------------------
|
||||
|
||||
@@ -383,7 +385,7 @@ hook wrappers and passes the same arguments as to the regular hooks.
|
||||
|
||||
At the yield point of the hook wrapper pytest will execute the next hook
|
||||
implementations and return their result to the yield point in the form of
|
||||
a :py:class:`CallOutcome` instance which encapsulates a result or
|
||||
a :py:class:`CallOutcome <_pytest.vendored_packages.pluggy._CallOutcome>` instance which encapsulates a result or
|
||||
exception info. The yield point itself will thus typically not raise
|
||||
exceptions (unless there are bugs).
|
||||
|
||||
@@ -448,7 +450,7 @@ Here is the order of execution:
|
||||
Plugin1).
|
||||
|
||||
4. Plugin3's pytest_collection_modifyitems then executing the code after the yield
|
||||
point. The yield receives a :py:class:`CallOutcome` instance which encapsulates
|
||||
point. The yield receives a :py:class:`CallOutcome <_pytest.vendored_packages.pluggy._CallOutcome>` instance which encapsulates
|
||||
the result from calling the non-wrappers. Wrappers shall not modify the result.
|
||||
|
||||
It's possible to use ``tryfirst`` and ``trylast`` also in conjunction with
|
||||
@@ -525,7 +527,7 @@ Initialization, command line and configuration hooks
|
||||
Generic "runtest" hooks
|
||||
-----------------------
|
||||
|
||||
All runtest related hooks receive a :py:class:`pytest.Item` object.
|
||||
All runtest related hooks receive a :py:class:`pytest.Item <_pytest.main.Item>` object.
|
||||
|
||||
.. autofunction:: pytest_runtest_protocol
|
||||
.. autofunction:: pytest_runtest_setup
|
||||
|
||||
35
pyproject.toml
Normal file
35
pyproject.toml
Normal file
@@ -0,0 +1,35 @@
|
||||
[tool.towncrier]
|
||||
package = "pytest"
|
||||
filename = "CHANGELOG.rst"
|
||||
directory = "changelog/"
|
||||
template = "changelog/_template.rst"
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "removal"
|
||||
name = "Deprecations and Removals"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "feature"
|
||||
name = "Features"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "bugfix"
|
||||
name = "Bug Fixes"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "vendor"
|
||||
name = "Vendored Libraries"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "doc"
|
||||
name = "Improved Documentation"
|
||||
showcontent = true
|
||||
|
||||
[[tool.towncrier.type]]
|
||||
directory = "trivial"
|
||||
name = "Trivial/Internal Changes"
|
||||
showcontent = true
|
||||
@@ -1,20 +0,0 @@
|
||||
"""
|
||||
Script used by tox.ini to check the manifest file if we are under version control, or skip the
|
||||
check altogether if not.
|
||||
|
||||
"check-manifest" will needs a vcs to work, which is not available when testing the package
|
||||
instead of the source code (with ``devpi test`` for example).
|
||||
"""
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
if os.path.isdir('.git'):
|
||||
sys.exit(subprocess.call('check-manifest', shell=True))
|
||||
else:
|
||||
print('No .git directory found, skipping checking the manifest file')
|
||||
sys.exit(0)
|
||||
11
scripts/check-rst.py
Normal file
11
scripts/check-rst.py
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
from __future__ import print_function
|
||||
|
||||
import subprocess
|
||||
import glob
|
||||
import sys
|
||||
|
||||
sys.exit(subprocess.call([
|
||||
'rst-lint', '--encoding', 'utf-8',
|
||||
'CHANGELOG.rst', 'HOWTORELEASE.rst', 'README.rst',
|
||||
] + glob.glob('changelog/[0-9]*.*')))
|
||||
2
setup.py
2
setup.py
@@ -43,7 +43,7 @@ def has_environment_marker_support():
|
||||
|
||||
|
||||
def main():
|
||||
install_requires = ['py>=1.4.29', 'setuptools'] # pluggy is vendored in _pytest.vendored_packages
|
||||
install_requires = ['py>=1.4.33', 'setuptools'] # pluggy is vendored in _pytest.vendored_packages
|
||||
extras_require = {}
|
||||
if has_environment_marker_support():
|
||||
extras_require[':python_version=="2.6"'] = ['argparse']
|
||||
|
||||
@@ -4,6 +4,10 @@ Invoke tasks to help with pytest development and release process.
|
||||
|
||||
import invoke
|
||||
|
||||
from . import generate
|
||||
from . import generate, vendoring
|
||||
|
||||
ns = invoke.Collection(generate)
|
||||
|
||||
ns = invoke.Collection(
|
||||
generate,
|
||||
vendoring
|
||||
)
|
||||
|
||||
@@ -96,9 +96,10 @@ def devpi_upload(ctx, version, user, password=None):
|
||||
'(if not given assumed logged in)',
|
||||
})
|
||||
def pre_release(ctx, version, user, password=None):
|
||||
"""Generates new docs, release announcements and uploads a new release to devpi for testing."""
|
||||
"""Generates new docs, release announcements and uploads a new release to devpi for testing."""
|
||||
announce(ctx, version)
|
||||
regen(ctx)
|
||||
changelog(ctx, version, write_out=True)
|
||||
|
||||
msg = 'Preparing release version {}'.format(version)
|
||||
check_call(['git', 'commit', '-a', '-m', msg])
|
||||
@@ -110,3 +111,52 @@ def pre_release(ctx, version, user, password=None):
|
||||
print()
|
||||
print('[generate.pre_release] Please push your branch and open a PR.')
|
||||
|
||||
|
||||
@invoke.task(help={
|
||||
'version': 'version being released',
|
||||
'user': 'name of the user on devpi to stage the generated package',
|
||||
'pypi_name': 'name of the pypi configuration section in your ~/.pypirc',
|
||||
})
|
||||
def publish_release(ctx, version, user, pypi_name):
|
||||
"""Publishes a package previously created by the 'pre_release' command."""
|
||||
from git import Repo
|
||||
repo = Repo('.')
|
||||
tag_names = [x.name for x in repo.tags]
|
||||
if version not in tag_names:
|
||||
print('Could not find tag for version {}, exiting...'.format(version))
|
||||
raise invoke.Exit(code=2)
|
||||
|
||||
check_call(['devpi', 'use', 'https://devpi.net/{}/dev'.format(user)])
|
||||
check_call(['devpi', 'push', 'pytest=={}'.format(version), 'pypi:{}'.format(pypi_name)])
|
||||
check_call(['git', 'push', 'git@github.com:pytest-dev/pytest.git', version])
|
||||
|
||||
emails = [
|
||||
'pytest-dev@python.org',
|
||||
'python-announce-list@python.org'
|
||||
]
|
||||
if version.endswith('.0'):
|
||||
emails.append('testing-in-python@lists.idyll.org')
|
||||
print('Version {} has been published to PyPI!'.format(version))
|
||||
print()
|
||||
print('Please send an email announcement with the contents from:')
|
||||
print()
|
||||
print(' doc/en/announce/release-{}.rst'.format(version))
|
||||
print()
|
||||
print('To the following mail lists:')
|
||||
print()
|
||||
print(' ', ','.join(emails))
|
||||
print()
|
||||
print('And announce it on twitter adding the #pytest hash tag.')
|
||||
|
||||
|
||||
@invoke.task(help={
|
||||
'version': 'version being released',
|
||||
'write_out': 'write changes to the actial changelog'
|
||||
})
|
||||
def changelog(ctx, version, write_out=False):
|
||||
if write_out:
|
||||
addopts = []
|
||||
else:
|
||||
addopts = ['--draft']
|
||||
check_call(['towncrier', '--version', version] + addopts)
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ The pytest team is proud to announce the {version} release!
|
||||
pytest is a mature Python testing tool with more than a 1600 tests
|
||||
against itself, passing on many different interpreters and platforms.
|
||||
|
||||
This release contains a bugs fixes and improvements, so users are encouraged
|
||||
This release contains a number of bugs fixes and improvements, so users are encouraged
|
||||
to take a look at the CHANGELOG:
|
||||
|
||||
http://doc.pytest.org/en/latest/changelog.html
|
||||
http://doc.pytest.org/en/latest/changelog.html
|
||||
|
||||
For complete documentation, please visit:
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
invoke
|
||||
tox
|
||||
gitpython
|
||||
towncrier
|
||||
wheel
|
||||
|
||||
23
tasks/vendoring.py
Normal file
23
tasks/vendoring.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from __future__ import absolute_import, print_function
|
||||
import py
|
||||
import invoke
|
||||
|
||||
VENDOR_TARGET = py.path.local("_pytest/vendored_packages")
|
||||
GOOD_FILES = 'README.md', '__init__.py'
|
||||
|
||||
@invoke.task()
|
||||
def remove_libs(ctx):
|
||||
print("removing vendored libs")
|
||||
for path in VENDOR_TARGET.listdir():
|
||||
if path.basename not in GOOD_FILES:
|
||||
print(" ", path)
|
||||
path.remove()
|
||||
|
||||
@invoke.task(pre=[remove_libs])
|
||||
def update_libs(ctx):
|
||||
print("installing libs")
|
||||
ctx.run("pip install -t {target} pluggy".format(target=VENDOR_TARGET))
|
||||
ctx.run("git add {target}".format(target=VENDOR_TARGET))
|
||||
print("Please commit to finish the update after running the tests:")
|
||||
print()
|
||||
print(' git commit -am "Updated vendored libs"')
|
||||
@@ -317,8 +317,8 @@ class TestGeneralUsage(object):
|
||||
])
|
||||
assert 'sessionstarttime' not in result.stderr.str()
|
||||
|
||||
@pytest.mark.parametrize('lookfor', ['test_fun.py', 'test_fun.py::test_a'])
|
||||
def test_issue134_report_syntaxerror_when_collecting_member(self, testdir, lookfor):
|
||||
@pytest.mark.parametrize('lookfor', ['test_fun.py::test_a'])
|
||||
def test_issue134_report_error_when_collecting_member(self, testdir, lookfor):
|
||||
testdir.makepyfile(test_fun="""
|
||||
def test_a():
|
||||
pass
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import absolute_import, division, print_function
|
||||
|
||||
import sys
|
||||
import operator
|
||||
import _pytest
|
||||
import py
|
||||
@@ -1140,3 +1141,58 @@ def test_cwd_deleted(testdir):
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(['* 1 failed in *'])
|
||||
assert 'INTERNALERROR' not in result.stdout.str() + result.stderr.str()
|
||||
|
||||
|
||||
def test_exception_repr_extraction_error_on_recursion():
|
||||
"""
|
||||
Ensure we can properly detect a recursion error even
|
||||
if some locals raise error on comparision (#2459).
|
||||
"""
|
||||
class numpy_like(object):
|
||||
|
||||
def __eq__(self, other):
|
||||
if type(other) is numpy_like:
|
||||
raise ValueError('The truth value of an array '
|
||||
'with more than one element is ambiguous.')
|
||||
|
||||
def a(x):
|
||||
return b(numpy_like())
|
||||
|
||||
def b(x):
|
||||
return a(numpy_like())
|
||||
|
||||
try:
|
||||
a(numpy_like())
|
||||
except:
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
from _pytest.pytester import LineMatcher
|
||||
exc_info = ExceptionInfo()
|
||||
|
||||
matcher = LineMatcher(str(exc_info.getrepr()).splitlines())
|
||||
matcher.fnmatch_lines([
|
||||
'!!! Recursion error detected, but an error occurred locating the origin of recursion.',
|
||||
'*The following exception happened*',
|
||||
'*ValueError: The truth value of an array*',
|
||||
])
|
||||
|
||||
|
||||
def test_no_recursion_index_on_recursion_error():
|
||||
"""
|
||||
Ensure that we don't break in case we can't find the recursion index
|
||||
during a recursion error (#2486).
|
||||
"""
|
||||
try:
|
||||
class RecursionDepthError(object):
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self, '_' + attr)
|
||||
|
||||
RecursionDepthError().trigger
|
||||
except:
|
||||
from _pytest._code.code import ExceptionInfo
|
||||
exc_info = ExceptionInfo()
|
||||
if sys.version_info[:2] == (2, 6):
|
||||
assert "'RecursionDepthError' object has no attribute '___" in str(exc_info.getrepr())
|
||||
else:
|
||||
assert 'maximum recursion' in str(exc_info.getrepr())
|
||||
else:
|
||||
assert 0
|
||||
|
||||
@@ -657,6 +657,39 @@ class TestRequestBasic(object):
|
||||
"*1 error*" # XXX the whole module collection fails
|
||||
])
|
||||
|
||||
def test_request_subrequest_addfinalizer_exceptions(self, testdir):
|
||||
"""
|
||||
Ensure exceptions raised during teardown by a finalizer are suppressed
|
||||
until all finalizers are called, re-raising the first exception (#2440)
|
||||
"""
|
||||
testdir.makepyfile("""
|
||||
import pytest
|
||||
l = []
|
||||
def _excepts(where):
|
||||
raise Exception('Error in %s fixture' % where)
|
||||
@pytest.fixture
|
||||
def subrequest(request):
|
||||
return request
|
||||
@pytest.fixture
|
||||
def something(subrequest):
|
||||
subrequest.addfinalizer(lambda: l.append(1))
|
||||
subrequest.addfinalizer(lambda: l.append(2))
|
||||
subrequest.addfinalizer(lambda: _excepts('something'))
|
||||
@pytest.fixture
|
||||
def excepts(subrequest):
|
||||
subrequest.addfinalizer(lambda: _excepts('excepts'))
|
||||
subrequest.addfinalizer(lambda: l.append(3))
|
||||
def test_first(something, excepts):
|
||||
pass
|
||||
def test_second():
|
||||
assert l == [3, 2, 1]
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
'*Exception: Error in excepts fixture',
|
||||
'* 2 passed, 1 error in *',
|
||||
])
|
||||
|
||||
def test_request_getmodulepath(self, testdir):
|
||||
modcol = testdir.getmodulecol("def test_somefunc(): pass")
|
||||
item, = testdir.genitems([modcol])
|
||||
|
||||
@@ -956,3 +956,17 @@ class TestIssue925(object):
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines('*E*assert True == ((False == True) == True)')
|
||||
|
||||
|
||||
class TestIssue2121():
|
||||
def test_simple(self, testdir):
|
||||
testdir.tmpdir.join("tests/file.py").ensure().write("""
|
||||
def test_simple_failure():
|
||||
assert 1 + 1 == 3
|
||||
""")
|
||||
testdir.tmpdir.join("pytest.ini").write(py.std.textwrap.dedent("""
|
||||
[pytest]
|
||||
python_files = tests/**.py
|
||||
"""))
|
||||
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines('*E*assert (1 + 1) == 3')
|
||||
|
||||
@@ -369,6 +369,11 @@ class TestSession(object):
|
||||
assert len(colitems) == 1
|
||||
assert colitems[0].fspath == p
|
||||
|
||||
def get_reported_items(self, hookrec):
|
||||
"""Return pytest.Item instances reported by the pytest_collectreport hook"""
|
||||
calls = hookrec.getcalls('pytest_collectreport')
|
||||
return [x for call in calls for x in call.report.result
|
||||
if isinstance(x, pytest.Item)]
|
||||
|
||||
def test_collect_protocol_single_function(self, testdir):
|
||||
p = testdir.makepyfile("def test_func(): pass")
|
||||
@@ -386,9 +391,10 @@ class TestSession(object):
|
||||
("pytest_collectstart", "collector.fspath == p"),
|
||||
("pytest_make_collect_report", "collector.fspath == p"),
|
||||
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
||||
("pytest_collectreport", "report.nodeid.startswith(p.basename)"),
|
||||
("pytest_collectreport", "report.nodeid == ''")
|
||||
("pytest_collectreport", "report.result[0].name == 'test_func'"),
|
||||
])
|
||||
# ensure we are reporting the collection of the single test item (#2464)
|
||||
assert [x.name for x in self.get_reported_items(hookrec)] == ['test_func']
|
||||
|
||||
def test_collect_protocol_method(self, testdir):
|
||||
p = testdir.makepyfile("""
|
||||
@@ -407,6 +413,8 @@ class TestSession(object):
|
||||
assert items[0].name == "test_method"
|
||||
newid = items[0].nodeid
|
||||
assert newid == normid
|
||||
# ensure we are reporting the collection of the single test item (#2464)
|
||||
assert [x.name for x in self.get_reported_items(hookrec)] == ['test_method']
|
||||
|
||||
def test_collect_custom_nodes_multi_id(self, testdir):
|
||||
p = testdir.makepyfile("def test_func(): pass")
|
||||
@@ -436,9 +444,8 @@ class TestSession(object):
|
||||
"collector.__class__.__name__ == 'Module'"),
|
||||
("pytest_pycollect_makeitem", "name == 'test_func'"),
|
||||
("pytest_collectreport", "report.nodeid.startswith(p.basename)"),
|
||||
#("pytest_collectreport",
|
||||
# "report.fspath == %r" % str(rcol.fspath)),
|
||||
])
|
||||
assert len(self.get_reported_items(hookrec)) == 2
|
||||
|
||||
def test_collect_subdir_event_ordering(self, testdir):
|
||||
p = testdir.makepyfile("def test_func(): pass")
|
||||
@@ -495,11 +502,13 @@ class TestSession(object):
|
||||
def test_method(self):
|
||||
pass
|
||||
""")
|
||||
arg = p.basename + ("::TestClass::test_method")
|
||||
arg = p.basename + "::TestClass::test_method"
|
||||
items, hookrec = testdir.inline_genitems(arg)
|
||||
assert len(items) == 1
|
||||
item, = items
|
||||
assert item.nodeid.endswith("TestClass::()::test_method")
|
||||
# ensure we are reporting the collection of the single test item (#2464)
|
||||
assert [x.name for x in self.get_reported_items(hookrec)] == ['test_method']
|
||||
|
||||
class Test_getinitialnodes(object):
|
||||
def test_global_file(self, testdir, tmpdir):
|
||||
|
||||
@@ -449,3 +449,15 @@ def test_hook_proxy(testdir):
|
||||
'*test_foo4.py*',
|
||||
'*3 passed*',
|
||||
])
|
||||
|
||||
|
||||
def test_required_option_help(testdir):
|
||||
testdir.makeconftest("assert 0")
|
||||
x = testdir.mkdir("x")
|
||||
x.join("conftest.py").write(_pytest._code.Source("""
|
||||
def pytest_addoption(parser):
|
||||
parser.addoption("--xyz", action="store_true", required=True)
|
||||
"""))
|
||||
result = testdir.runpytest("-h", x)
|
||||
assert 'argument --xyz is required' not in result.stdout.str()
|
||||
assert 'general:' in result.stdout.str()
|
||||
|
||||
@@ -505,6 +505,47 @@ class TestDoctests(object):
|
||||
"--junit-xml=junit.xml")
|
||||
reprec.assertoutcome(failed=1)
|
||||
|
||||
def test_unicode_doctest(self, testdir):
|
||||
"""
|
||||
Test case for issue 2434: DecodeError on Python 2 when doctest contains non-ascii
|
||||
characters.
|
||||
"""
|
||||
p = testdir.maketxtfile(test_unicode_doctest="""
|
||||
.. doctest::
|
||||
|
||||
>>> print(
|
||||
... "Hi\\n\\nByé")
|
||||
Hi
|
||||
...
|
||||
Byé
|
||||
>>> 1/0 # Byé
|
||||
1
|
||||
""")
|
||||
result = testdir.runpytest(p)
|
||||
result.stdout.fnmatch_lines([
|
||||
'*UNEXPECTED EXCEPTION: ZeroDivisionError*',
|
||||
'*1 failed*',
|
||||
])
|
||||
|
||||
def test_unicode_doctest_module(self, testdir):
|
||||
"""
|
||||
Test case for issue 2434: DecodeError on Python 2 when doctest docstring
|
||||
contains non-ascii characters.
|
||||
"""
|
||||
p = testdir.makepyfile(test_unicode_doctest_module="""
|
||||
# -*- encoding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
def fix_bad_unicode(text):
|
||||
'''
|
||||
>>> print(fix_bad_unicode('único'))
|
||||
único
|
||||
'''
|
||||
return "único"
|
||||
""")
|
||||
result = testdir.runpytest(p, '--doctest-modules')
|
||||
result.stdout.fnmatch_lines(['* 1 passed *'])
|
||||
|
||||
|
||||
class TestLiterals(object):
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ from __future__ import absolute_import, division, print_function
|
||||
import warnings
|
||||
import re
|
||||
import py
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
from _pytest.recwarn import WarningsRecorder
|
||||
|
||||
@@ -75,7 +77,7 @@ class TestDeprecatedCall(object):
|
||||
def test_deprecated_call_raises(self):
|
||||
with pytest.raises(AssertionError) as excinfo:
|
||||
pytest.deprecated_call(self.dep, 3, 5)
|
||||
assert str(excinfo).find("did not produce") != -1
|
||||
assert 'Did not produce' in str(excinfo)
|
||||
|
||||
def test_deprecated_call(self):
|
||||
pytest.deprecated_call(self.dep, 0, 5)
|
||||
@@ -104,28 +106,69 @@ class TestDeprecatedCall(object):
|
||||
pytest.deprecated_call(self.dep_explicit, 0)
|
||||
pytest.deprecated_call(self.dep_explicit, 0)
|
||||
|
||||
def test_deprecated_call_as_context_manager_no_warning(self):
|
||||
with pytest.raises(pytest.fail.Exception, matches='^DID NOT WARN'):
|
||||
with pytest.deprecated_call():
|
||||
self.dep(1)
|
||||
|
||||
def test_deprecated_call_as_context_manager(self):
|
||||
with pytest.deprecated_call():
|
||||
self.dep(0)
|
||||
|
||||
def test_deprecated_call_pending(self):
|
||||
@pytest.mark.parametrize('mode', ['context_manager', 'call'])
|
||||
def test_deprecated_call_no_warning(self, mode):
|
||||
"""Ensure deprecated_call() raises the expected failure when its block/function does
|
||||
not raise a deprecation warning.
|
||||
"""
|
||||
def f():
|
||||
py.std.warnings.warn(PendingDeprecationWarning("hi"))
|
||||
pytest.deprecated_call(f)
|
||||
pass
|
||||
|
||||
msg = 'Did not produce DeprecationWarning or PendingDeprecationWarning'
|
||||
with pytest.raises(AssertionError, matches=msg):
|
||||
if mode == 'call':
|
||||
pytest.deprecated_call(f)
|
||||
else:
|
||||
with pytest.deprecated_call():
|
||||
f()
|
||||
|
||||
@pytest.mark.parametrize('warning_type', [PendingDeprecationWarning, DeprecationWarning])
|
||||
@pytest.mark.parametrize('mode', ['context_manager', 'call'])
|
||||
@pytest.mark.parametrize('call_f_first', [True, False])
|
||||
def test_deprecated_call_modes(self, warning_type, mode, call_f_first):
|
||||
"""Ensure deprecated_call() captures a deprecation warning as expected inside its
|
||||
block/function.
|
||||
"""
|
||||
def f():
|
||||
warnings.warn(warning_type("hi"))
|
||||
return 10
|
||||
|
||||
# ensure deprecated_call() can capture the warning even if it has already been triggered
|
||||
if call_f_first:
|
||||
assert f() == 10
|
||||
if mode == 'call':
|
||||
assert pytest.deprecated_call(f) == 10
|
||||
else:
|
||||
with pytest.deprecated_call():
|
||||
assert f() == 10
|
||||
|
||||
@pytest.mark.parametrize('mode', ['context_manager', 'call'])
|
||||
def test_deprecated_call_exception_is_raised(self, mode):
|
||||
"""If the block of the code being tested by deprecated_call() raises an exception,
|
||||
it must raise the exception undisturbed.
|
||||
"""
|
||||
def f():
|
||||
raise ValueError('some exception')
|
||||
|
||||
with pytest.raises(ValueError, match='some exception'):
|
||||
if mode == 'call':
|
||||
pytest.deprecated_call(f)
|
||||
else:
|
||||
with pytest.deprecated_call():
|
||||
f()
|
||||
|
||||
def test_deprecated_call_specificity(self):
|
||||
other_warnings = [Warning, UserWarning, SyntaxWarning, RuntimeWarning,
|
||||
FutureWarning, ImportWarning, UnicodeWarning]
|
||||
for warning in other_warnings:
|
||||
def f():
|
||||
py.std.warnings.warn(warning("hi"))
|
||||
warnings.warn(warning("hi"))
|
||||
|
||||
with pytest.raises(AssertionError):
|
||||
pytest.deprecated_call(f)
|
||||
with pytest.raises(AssertionError):
|
||||
with pytest.deprecated_call():
|
||||
f()
|
||||
|
||||
def test_deprecated_function_already_called(self, testdir):
|
||||
"""deprecated_call should be able to catch a call to a deprecated
|
||||
@@ -146,9 +189,12 @@ class TestDeprecatedCall(object):
|
||||
pytest.deprecated_call(deprecated_function)
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
# the 2 tests must pass, but the call to test_one() will generate a warning
|
||||
# in pytest's summary
|
||||
result.stdout.fnmatch_lines('*=== 2 passed, 1 warnings in *===')
|
||||
# for some reason in py26 catch_warnings manages to catch the deprecation warning
|
||||
# from deprecated_function(), even with default filters active (which ignore deprecation
|
||||
# warnings)
|
||||
py26 = sys.version_info[:2] == (2, 6)
|
||||
expected = '*=== 2 passed in *===' if not py26 else '*=== 2 passed, 1 warnings in *==='
|
||||
result.stdout.fnmatch_lines(expected)
|
||||
|
||||
|
||||
class TestWarns(object):
|
||||
|
||||
@@ -513,12 +513,12 @@ def test_pytest_no_tests_collected_exit_status(testdir):
|
||||
assert 1
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines('*collected 1 items*')
|
||||
result.stdout.fnmatch_lines('*collected 1 item*')
|
||||
result.stdout.fnmatch_lines('*1 passed*')
|
||||
assert result.ret == main.EXIT_OK
|
||||
|
||||
result = testdir.runpytest('-k nonmatch')
|
||||
result.stdout.fnmatch_lines('*collected 1 items*')
|
||||
result.stdout.fnmatch_lines('*collected 1 item*')
|
||||
result.stdout.fnmatch_lines('*1 deselected*')
|
||||
assert result.ret == main.EXIT_NOTESTSCOLLECTED
|
||||
|
||||
|
||||
@@ -204,6 +204,15 @@ class TestTerminal(object):
|
||||
assert result.ret == 2
|
||||
result.stdout.fnmatch_lines(['*KeyboardInterrupt*'])
|
||||
|
||||
def test_collect_single_item(self, testdir):
|
||||
"""Use singular 'item' when reporting a single test item"""
|
||||
testdir.makepyfile("""
|
||||
def test_foobar():
|
||||
pass
|
||||
""")
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines(['collected 1 item'])
|
||||
|
||||
|
||||
class TestCollectonly(object):
|
||||
def test_collectonly_basic(self, testdir):
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
# -*- coding: utf8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@@ -15,8 +20,8 @@ def pyfile_with_warnings(testdir, request):
|
||||
module_name: '''
|
||||
import warnings
|
||||
def foo():
|
||||
warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))
|
||||
warnings.warn(DeprecationWarning("functionality is deprecated"))
|
||||
warnings.warn(UserWarning("user warning"))
|
||||
warnings.warn(RuntimeWarning("runtime warning"))
|
||||
return 1
|
||||
''',
|
||||
test_name: '''
|
||||
@@ -38,11 +43,11 @@ def test_normal_flow(testdir, pyfile_with_warnings):
|
||||
|
||||
'*test_normal_flow.py::test_func',
|
||||
|
||||
'*normal_flow_module.py:3: PendingDeprecationWarning: functionality is pending deprecation',
|
||||
'* warnings.warn(PendingDeprecationWarning("functionality is pending deprecation"))',
|
||||
'*normal_flow_module.py:3: UserWarning: user warning',
|
||||
'* warnings.warn(UserWarning("user warning"))',
|
||||
|
||||
'*normal_flow_module.py:4: DeprecationWarning: functionality is deprecated',
|
||||
'* warnings.warn(DeprecationWarning("functionality is deprecated"))',
|
||||
'*normal_flow_module.py:4: RuntimeWarning: runtime warning',
|
||||
'* warnings.warn(RuntimeWarning("runtime warning"))',
|
||||
'* 1 passed, 2 warnings*',
|
||||
])
|
||||
assert result.stdout.str().count('test_normal_flow.py::test_func') == 1
|
||||
@@ -85,8 +90,8 @@ def test_as_errors(testdir, pyfile_with_warnings, method):
|
||||
''')
|
||||
result = testdir.runpytest(*args)
|
||||
result.stdout.fnmatch_lines([
|
||||
'E PendingDeprecationWarning: functionality is pending deprecation',
|
||||
'as_errors_module.py:3: PendingDeprecationWarning',
|
||||
'E UserWarning: user warning',
|
||||
'as_errors_module.py:3: UserWarning',
|
||||
'* 1 failed in *',
|
||||
])
|
||||
|
||||
@@ -106,3 +111,80 @@ def test_ignore(testdir, pyfile_with_warnings, method):
|
||||
])
|
||||
assert WARNINGS_SUMMARY_HEADER not in result.stdout.str()
|
||||
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 0),
|
||||
reason='warnings message is unicode is ok in python3')
|
||||
def test_unicode(testdir, pyfile_with_warnings):
|
||||
testdir.makepyfile('''
|
||||
# -*- coding: utf8 -*-
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fix():
|
||||
warnings.warn(u"测试")
|
||||
yield
|
||||
|
||||
def test_func(fix):
|
||||
pass
|
||||
''')
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
'*== %s ==*' % WARNINGS_SUMMARY_HEADER,
|
||||
'*test_unicode.py:8: UserWarning: \u6d4b\u8bd5*',
|
||||
'* 1 passed, 1 warnings*',
|
||||
])
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info >= (3, 0),
|
||||
reason='warnings message is broken as it is not str instance')
|
||||
def test_py2_unicode(testdir, pyfile_with_warnings):
|
||||
testdir.makepyfile('''
|
||||
# -*- coding: utf8 -*-
|
||||
import warnings
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fix():
|
||||
warnings.warn(u"测试")
|
||||
yield
|
||||
|
||||
def test_func(fix):
|
||||
pass
|
||||
''')
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
'*== %s ==*' % WARNINGS_SUMMARY_HEADER,
|
||||
|
||||
'*test_py2_unicode.py:8: UserWarning: \u6d4b\u8bd5',
|
||||
'*warnings.warn(u"\u6d4b\u8bd5")',
|
||||
'*warnings.py:*: UnicodeWarning: Warning is using unicode non*',
|
||||
'* 1 passed, 2 warnings*',
|
||||
])
|
||||
|
||||
|
||||
def test_works_with_filterwarnings(testdir):
|
||||
"""Ensure our warnings capture does not mess with pre-installed filters (#2430)."""
|
||||
testdir.makepyfile('''
|
||||
import warnings
|
||||
|
||||
class MyWarning(Warning):
|
||||
pass
|
||||
|
||||
warnings.filterwarnings("error", category=MyWarning)
|
||||
|
||||
class TestWarnings(object):
|
||||
def test_my_warning(self):
|
||||
try:
|
||||
warnings.warn(MyWarning("warn!"))
|
||||
assert False
|
||||
except MyWarning:
|
||||
assert True
|
||||
''')
|
||||
result = testdir.runpytest()
|
||||
result.stdout.fnmatch_lines([
|
||||
'*== 1 passed in *',
|
||||
])
|
||||
|
||||
8
tox.ini
8
tox.ini
@@ -46,6 +46,8 @@ commands=
|
||||
|
||||
|
||||
[testenv:linting]
|
||||
skipsdist=True
|
||||
usedevelop=True
|
||||
basepython = python2.7
|
||||
# needed to keep check-manifest working
|
||||
setenv =
|
||||
@@ -55,11 +57,9 @@ deps =
|
||||
# pygments required by rst-lint
|
||||
pygments
|
||||
restructuredtext_lint
|
||||
check-manifest
|
||||
commands =
|
||||
{envpython} scripts/check-manifest.py
|
||||
flake8 pytest.py _pytest testing
|
||||
rst-lint CHANGELOG.rst HOWTORELEASE.rst README.rst --encoding utf-8
|
||||
{envpython} scripts/check-rst.py
|
||||
|
||||
[testenv:py27-xdist]
|
||||
deps=pytest-xdist>=1.13
|
||||
@@ -182,7 +182,7 @@ python_files=test_*.py *_test.py testing/*/*.py
|
||||
python_classes=Test Acceptance
|
||||
python_functions=test
|
||||
norecursedirs = .tox ja .hg cx_freeze_source
|
||||
filterwarnings= error
|
||||
filterwarnings=
|
||||
# produced by path.local
|
||||
ignore:bad escape.*:DeprecationWarning:re
|
||||
# produced by path.readlines
|
||||
|
||||
Reference in New Issue
Block a user