Merge branch 'release-3.1'

This commit is contained in:
Bruno Oliveira 2017-05-22 19:10:06 -03:00
commit d343f9497c
135 changed files with 2944 additions and 1299 deletions

3
.gitignore vendored
View File

@ -18,6 +18,9 @@ include/
*~ *~
.hypothesis/ .hypothesis/
# autogenerated
_pytest/_version.py
# setuptools
.eggs/ .eggs/
doc/*/_build doc/*/_build

View File

@ -14,6 +14,7 @@ Andrzej Ostrowski
Andy Freeland Andy Freeland
Anthon van der Neut Anthon van der Neut
Antony Lee Antony Lee
Anthony Sottile
Armin Rigo Armin Rigo
Aron Curzon Aron Curzon
Aviv Palivoda Aviv Palivoda
@ -47,6 +48,7 @@ David Vierra
Denis Kirisov Denis Kirisov
Diego Russo Diego Russo
Dmitry Dygalo Dmitry Dygalo
Dmitry Pribysh
Duncan Betts Duncan Betts
Edison Gustavo Muenz Edison Gustavo Muenz
Edoardo Batini Edoardo Batini
@ -78,6 +80,7 @@ Javier Romero
Jeff Widman Jeff Widman
John Towler John Towler
Jon Sonesen Jon Sonesen
Jonas Obrist
Jordan Guymon Jordan Guymon
Joshua Bronson Joshua Bronson
Jurko Gospodnetić Jurko Gospodnetić
@ -93,6 +96,8 @@ Lukas Bednar
Luke Murphy Luke Murphy
Maciek Fijalkowski Maciek Fijalkowski
Maho Maho
Mandeep Bhutani
Manuel Krebber
Marc Schlaich Marc Schlaich
Marcin Bachry Marcin Bachry
Mark Abramowitz Mark Abramowitz
@ -102,6 +107,7 @@ Martin K. Scherer
Martin Prusse Martin Prusse
Mathieu Clabaut Mathieu Clabaut
Matt Bachmann Matt Bachmann
Matt Duck
Matt Williams Matt Williams
Matthias Hafner Matthias Hafner
mbyt mbyt
@ -109,6 +115,7 @@ Michael Aquilina
Michael Birtwell Michael Birtwell
Michael Droettboom Michael Droettboom
Michael Seifert Michael Seifert
Michal Wajszczuk
Mike Lundy Mike Lundy
Ned Batchelder Ned Batchelder
Neven Mundar Neven Mundar
@ -127,6 +134,7 @@ Ralf Schmitt
Ran Benita Ran Benita
Raphael Pierzina Raphael Pierzina
Raquel Alegre Raquel Alegre
Ravi Chandra
Roberto Polli Roberto Polli
Romain Dorgueil Romain Dorgueil
Roman Bolshakov Roman Bolshakov
@ -150,6 +158,7 @@ Trevor Bekolay
Tyler Goodlet Tyler Goodlet
Vasily Kuznetsov Vasily Kuznetsov
Victor Uriarte Victor Uriarte
Vlad Dragos
Vidar T. Fauske Vidar T. Fauske
Vitaly Lashmanov Vitaly Lashmanov
Wouter van Ackooy Wouter van Ackooy

View File

@ -1,6 +1,101 @@
3.0.8 (unreleased) 3.1.0 (2017-05-20)
================== ==================
New Features
------------
* The ``pytest-warnings`` plugin has been integrated into the core, so now ``pytest`` automatically
captures and displays warnings at the end of the test session.
Thanks `@nicoddemus`_ for the PR.
* 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`_).
* ``pytest.warns`` now checks for subclass relationship rather than
class equality. Thanks `@lesteve`_ for the PR (`#2166`_)
* ``pytest.raises`` now asserts that the error message matches a text or regex
with the ``match`` keyword argument. Thanks `@Kriechi`_ for the PR.
* ``pytest.param`` can be used to declare test parameter sets with marks and test ids.
Thanks `@RonnyPfannschmidt`_ for the PR.
Changes
-------
* remove all internal uses of pytest_namespace hooks,
this is to prepare the removal of preloadconfig in pytest 4.0
Thanks to `@RonnyPfannschmidt`_ for the PR.
* pytest now warns when a callable ids raises in a parametrized test. Thanks `@fogo`_ for the PR.
* It is now possible to skip test classes from being collected by setting a
``__test__`` attribute to ``False`` in the class body (`#2007`_). Thanks
to `@syre`_ for the report and `@lwm`_ for the PR.
* Change junitxml.py to produce reports that comply with Junitxml schema.
If the same test fails with failure in call and then errors in teardown
we split testcase element into two, one containing the error and the other
the failure. (`#2228`_) Thanks to `@kkoukiou`_ for the PR.
* Testcase reports with a ``url`` attribute will now properly write this to junitxml.
Thanks `@fushi`_ for the PR (`#1874`_).
* Remove common items from dict comparision output when verbosity=1. Also update
the truncation message to make it clearer that pytest truncates all
assertion messages if verbosity < 2 (`#1512`_).
Thanks `@mattduck`_ for the PR
* ``--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.
* fix `#2208`_: ensure a iteration limit for _pytest.compat.get_real_func.
Thanks `@RonnyPfannschmidt`_ for the report and PR.
* Hooks are now verified after collection is complete, rather than right after loading installed plugins. This
makes it easy to write hooks for plugins which will be loaded during collection, for example using the
``pytest_plugins`` special variable (`#1821`_).
Thanks `@nicoddemus`_ for the PR.
* Modify ``pytest_make_parametrize_id()`` hook to accept ``argname`` as an
additional parameter.
Thanks `@unsignedint`_ for the PR.
* 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.
* fix `#2308`_: When using both ``--lf`` and ``--ff``, only the last failed tests are run.
Thanks `@ojii`_ for the PR.
* Replace minor/patch level version numbers in the documentation with placeholders.
This significantly reduces change-noise as different contributors regnerate
the documentation on different platforms.
Thanks `@RonnyPfannschmidt`_ for the PR.
* fix `#2391`_: consider pytest_plugins on all plugin modules
Thansks `@RonnyPfannschmidt`_ for the PR.
Bug Fixes
---------
* Fix ``AttributeError`` on ``sys.stdout.buffer`` / ``sys.stderr.buffer``
while using ``capsys`` fixture in python 3. (`#1407`_).
Thanks to `@asottile`_.
* Change capture.py's ``DontReadFromInput`` class to throw ``io.UnsupportedOperation`` errors rather * Change capture.py's ``DontReadFromInput`` class to throw ``io.UnsupportedOperation`` errors rather
than ValueErrors in the ``fileno`` method (`#2276`_). than ValueErrors in the ``fileno`` method (`#2276`_).
Thanks `@metasyn`_ for the PR. Thanks `@metasyn`_ for the PR.
@ -8,7 +103,7 @@
* Fix exception formatting while importing modules when the exception message * Fix exception formatting while importing modules when the exception message
contains non-ascii characters (`#2336`_). contains non-ascii characters (`#2336`_).
Thanks `@fabioz`_ for the report and `@nicoddemus`_ for the PR. Thanks `@fabioz`_ for the report and `@nicoddemus`_ for the PR.
* Added documentation related to issue (`#1937`_) * Added documentation related to issue (`#1937`_)
Thanks `@skylarjhdownes`_ for the PR. Thanks `@skylarjhdownes`_ for the PR.
@ -18,26 +113,45 @@
* Show the correct error message when collect "parametrize" func with wrong args (`#2383`_). * Show the correct error message when collect "parametrize" func with wrong args (`#2383`_).
Thanks `@The-Compiler`_ for the report and `@robin0371`_ for the PR. Thanks `@The-Compiler`_ for the report and `@robin0371`_ for the PR.
*
* .. _@davidszotten: https://github.com/davidszotten
*
.. _@skylarjhdownes: https://github.com/skylarjhdownes
.. _@fabioz: https://github.com/fabioz .. _@fabioz: https://github.com/fabioz
.. _@metasyn: https://github.com/metasyn .. _@fogo: https://github.com/fogo
.. _@fushi: https://github.com/fushi
.. _@Kodiologist: https://github.com/Kodiologist .. _@Kodiologist: https://github.com/Kodiologist
.. _@Kriechi: https://github.com/Kriechi
.. _@mandeep: https://github.com/mandeep
.. _@mattduck: https://github.com/mattduck
.. _@metasyn: https://github.com/metasyn
.. _@MichalTHEDUDE: https://github.com/MichalTHEDUDE
.. _@ojii: https://github.com/ojii
.. _@reutsharabani: https://github.com/reutsharabani
.. _@robin0371: https://github.com/robin0371 .. _@robin0371: https://github.com/robin0371
.. _@skylarjhdownes: https://github.com/skylarjhdownes
.. _@unsignedint: https://github.com/unsignedint
.. _@wheerd: https://github.com/wheerd
.. _#1407: https://github.com/pytest-dev/pytest/issues/1407
.. _#1512: https://github.com/pytest-dev/pytest/issues/1512
.. _#1821: https://github.com/pytest-dev/pytest/issues/1821
.. _#1874: https://github.com/pytest-dev/pytest/pull/1874
.. _#1937: https://github.com/pytest-dev/pytest/issues/1937 .. _#1937: https://github.com/pytest-dev/pytest/issues/1937
.. _#1952: https://github.com/pytest-dev/pytest/pull/1952
.. _#2007: https://github.com/pytest-dev/pytest/issues/2007
.. _#2013: https://github.com/pytest-dev/pytest/issues/2013
.. _#2101: https://github.com/pytest-dev/pytest/pull/2101
.. _#2166: https://github.com/pytest-dev/pytest/pull/2166
.. _#2208: https://github.com/pytest-dev/pytest/issues/2208
.. _#2228: https://github.com/pytest-dev/pytest/issues/2228
.. _#2276: https://github.com/pytest-dev/pytest/issues/2276 .. _#2276: https://github.com/pytest-dev/pytest/issues/2276
.. _#2308: https://github.com/pytest-dev/pytest/issues/2308
.. _#2336: https://github.com/pytest-dev/pytest/issues/2336 .. _#2336: https://github.com/pytest-dev/pytest/issues/2336
.. _#2369: https://github.com/pytest-dev/pytest/issues/2369 .. _#2369: https://github.com/pytest-dev/pytest/issues/2369
.. _#2383: https://github.com/pytest-dev/pytest/issues/2383 .. _#2383: https://github.com/pytest-dev/pytest/issues/2383
.. _#2391: https://github.com/pytest-dev/pytest/issues/2391
.. _#533: https://github.com/pytest-dev/pytest/issues/533
3.0.7 (2017-03-14) 3.0.7 (2017-03-14)
@ -191,6 +305,7 @@
* Cope gracefully with a .pyc file with no matching .py file (`#2038`_). Thanks * Cope gracefully with a .pyc file with no matching .py file (`#2038`_). Thanks
`@nedbat`_. `@nedbat`_.
.. _@syre: https://github.com/syre
.. _@adler-j: https://github.com/adler-j .. _@adler-j: https://github.com/adler-j
.. _@d-b-w: https://bitbucket.org/d-b-w/ .. _@d-b-w: https://bitbucket.org/d-b-w/
.. _@DuncanBetts: https://github.com/DuncanBetts .. _@DuncanBetts: https://github.com/DuncanBetts
@ -298,6 +413,7 @@
.. _@raquel-ucl: https://github.com/raquel-ucl .. _@raquel-ucl: https://github.com/raquel-ucl
.. _@axil: https://github.com/axil .. _@axil: https://github.com/axil
.. _@tgoodlet: https://github.com/tgoodlet .. _@tgoodlet: https://github.com/tgoodlet
.. _@vlad-dragos: https://github.com/vlad-dragos
.. _#1853: https://github.com/pytest-dev/pytest/issues/1853 .. _#1853: https://github.com/pytest-dev/pytest/issues/1853
.. _#1905: https://github.com/pytest-dev/pytest/issues/1905 .. _#1905: https://github.com/pytest-dev/pytest/issues/1905

View File

@ -206,12 +206,12 @@ but here is a simple overview:
#. Run all the tests #. Run all the tests
You need to have Python 2.7 and 3.5 available in your system. Now You need to have Python 2.7 and 3.6 available in your system. Now
running tests is as simple as issuing this command:: running tests is as simple as issuing this command::
$ tox -e linting,py27,py35 $ tox -e linting,py27,py36
This command will run tests via the "tox" tool against Python 2.7 and 3.5 This command will run tests via the "tox" tool against Python 2.7 and 3.6
and also perform "lint" coding-style checks. and also perform "lint" coding-style checks.
#. You can now edit your local working copy. #. You can now edit your local working copy.
@ -223,9 +223,9 @@ but here is a simple overview:
$ tox -e py27 -- --pdb $ tox -e py27 -- --pdb
Or to only run tests in a particular test module on Python 3.5:: Or to only run tests in a particular test module on Python 3.6::
$ tox -e py35 -- testing/test_config.py $ tox -e py36 -- testing/test_config.py
#. Commit and push once your tests pass and you are happy with your change(s):: #. Commit and push once your tests pass and you are happy with your change(s)::

View File

@ -1,29 +1,30 @@
How to release pytest How to release pytest
-------------------------------------------- --------------------------------------------
Note: this assumes you have already registered on pypi. .. important::
1. Bump version numbers in ``_pytest/__init__.py`` (``setup.py`` reads it). pytest releases must be prepared on **linux** because the docs and examples expect
to be executed in that platform.
2. Check and finalize ``CHANGELOG.rst``. #. Install development dependencies in a virtual environment with::
3. Write ``doc/en/announce/release-VERSION.txt`` and include pip3 install -r tasks/requirements.txt
it in ``doc/en/announce/index.txt``. Run this command to list names of authors involved::
git log $(git describe --abbrev=0 --tags)..HEAD --format='%aN' | sort -u #. 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.
4. Regenerate the docs examples using tox:: #. Check and finalize ``CHANGELOG.rst`` (will be automated soon).
tox -e regen #. Execute to automatically generate docs, announcements and upload a package to
your ``devpi`` staging server::
5. At this point, open a PR named ``release-X`` so others can help find regressions or provide suggestions. invoke generate.pre_release <VERSION> <DEVPI USER> --password <DEVPI PASSWORD>
6. Use devpi for uploading a release tarball to a staging area:: 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!
devpi use https://devpi.net/USER/dev #. Run from multiple machines::
devpi upload --formats sdist,bdist_wheel
7. Run from multiple machines::
devpi use https://devpi.net/USER/dev devpi use https://devpi.net/USER/dev
devpi test pytest==VERSION devpi test pytest==VERSION
@ -31,27 +32,27 @@ Note: this assumes you have already registered on pypi.
Alternatively, you can use `devpi-cloud-tester <https://github.com/nicoddemus/devpi-cloud-tester>`_ to test 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``). the package on AppVeyor and Travis (follow instructions on the ``README``).
8. Check that tests pass for relevant combinations with:: #. Check that tests pass for relevant combinations with::
devpi list pytest devpi list pytest
or look at failures with "devpi list -f pytest". or look at failures with "devpi list -f pytest".
9. Feeling confident? Publish to pypi:: #. Feeling confident? Publish to pypi::
devpi push pytest==VERSION pypi:NAME devpi push pytest==VERSION pypi:NAME
where NAME is the name of pypi.python.org as configured in your ``~/.pypirc`` where 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>`_. file `for devpi <http://doc.devpi.net/latest/quickstart-releaseprocess.html?highlight=pypirc#devpi-push-releasing-to-an-external-index>`_.
10. Tag the release:: #. Tag the release::
git tag VERSION <hash> git tag VERSION <hash>
git push origin VERSION git push origin VERSION
Make sure ``<hash>`` is **exactly** the git hash at the time the package was created. Make sure ``<hash>`` is **exactly** the git hash at the time the package was created.
11. Send release announcement to mailing lists: #. Send release announcement to mailing lists:
- pytest-dev@python.org - pytest-dev@python.org
- python-announce-list@python.org - python-announce-list@python.org
@ -59,7 +60,7 @@ Note: this assumes you have already registered on pypi.
And announce the release on Twitter, making sure to add the hashtag ``#pytest``. And announce the release on Twitter, making sure to add the hashtag ``#pytest``.
12. **After the release** #. **After the release**
a. **patch release (2.8.3)**: a. **patch release (2.8.3)**:
@ -81,5 +82,3 @@ Note: this assumes you have already registered on pypi.
9. Push ``master`` and ``features``. 9. Push ``master`` and ``features``.
c. **major release (3.0.0)**: same steps as that of a **minor release** c. **major release (3.0.0)**: same steps as that of a **minor release**

View File

@ -20,6 +20,7 @@ recursive-include extra *.py
graft testing graft testing
graft doc graft doc
prune doc/en/_build prune doc/en/_build
graft tasks
exclude _pytest/impl exclude _pytest/impl

View File

@ -1,2 +1,8 @@
# __all__ = ['__version__']
__version__ = '3.0.8.dev'
try:
from ._version import version as __version__
except ImportError:
# broken installation, we don't even try
# unknown only works because we do poor mans version compare
__version__ = 'unknown'

View File

@ -57,7 +57,7 @@ If things do not work right away:
which should throw a KeyError: 'COMPLINE' (which is properly set by the which should throw a KeyError: 'COMPLINE' (which is properly set by the
global argcomplete script). global argcomplete script).
""" """
from __future__ import absolute_import, division, print_function
import sys import sys
import os import os
from glob import glob from glob import glob

View File

@ -1,4 +1,5 @@
""" python inspection/code generation API """ """ python inspection/code generation API """
from __future__ import absolute_import, division, print_function
from .code import Code # noqa from .code import Code # noqa
from .code import ExceptionInfo # noqa from .code import ExceptionInfo # noqa
from .code import Frame # noqa from .code import Frame # noqa

View File

@ -2,6 +2,7 @@
# CHANGES: # CHANGES:
# - some_str is replaced, trying to create unicode strings # - some_str is replaced, trying to create unicode strings
# #
from __future__ import absolute_import, division, print_function
import types import types
def format_exception_only(etype, value): def format_exception_only(etype, value):

View File

@ -1,3 +1,4 @@
from __future__ import absolute_import, division, print_function
import sys import sys
from inspect import CO_VARARGS, CO_VARKEYWORDS from inspect import CO_VARARGS, CO_VARKEYWORDS
import re import re

View File

@ -1,4 +1,4 @@
from __future__ import generators from __future__ import absolute_import, division, generators, print_function
from bisect import bisect_right from bisect import bisect_right
import sys import sys

View File

@ -2,7 +2,7 @@
imports symbols from vendored "pluggy" if available, otherwise imports symbols from vendored "pluggy" if available, otherwise
falls back to importing "pluggy" from the default namespace. falls back to importing "pluggy" from the default namespace.
""" """
from __future__ import absolute_import, division, print_function
try: try:
from _pytest.vendored_packages.pluggy import * # noqa from _pytest.vendored_packages.pluggy import * # noqa
from _pytest.vendored_packages.pluggy import __version__ # noqa from _pytest.vendored_packages.pluggy import __version__ # noqa

View File

@ -1,12 +1,13 @@
""" """
support for presenting detailed information in failing assertions. support for presenting detailed information in failing assertions.
""" """
from __future__ import absolute_import, division, print_function
import py import py
import os
import sys import sys
from _pytest.assertion import util from _pytest.assertion import util
from _pytest.assertion import rewrite from _pytest.assertion import rewrite
from _pytest.assertion import truncate
def pytest_addoption(parser): def pytest_addoption(parser):
@ -24,9 +25,6 @@ def pytest_addoption(parser):
expression information.""") expression information.""")
def pytest_namespace():
return {'register_assert_rewrite': register_assert_rewrite}
def register_assert_rewrite(*names): def register_assert_rewrite(*names):
"""Register one or more module names to be rewritten on import. """Register one or more module names to be rewritten on import.
@ -100,12 +98,6 @@ def pytest_collection(session):
assertstate.hook.set_session(session) assertstate.hook.set_session(session)
def _running_on_ci():
"""Check if we're currently running on a CI system."""
env_vars = ['CI', 'BUILD_NUMBER']
return any(var in os.environ for var in env_vars)
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
"""Setup the pytest_assertrepr_compare hook """Setup the pytest_assertrepr_compare hook
@ -119,8 +111,8 @@ def pytest_runtest_setup(item):
This uses the first result from the hook and then ensures the This uses the first result from the hook and then ensures the
following: following:
* Overly verbose explanations are dropped unless -vv was used or * Overly verbose explanations are truncated unless configured otherwise
running on a CI. (eg. if running in verbose mode).
* Embedded newlines are escaped to help util.format_explanation() * Embedded newlines are escaped to help util.format_explanation()
later. later.
* If the rewrite mode is used embedded %-characters are replaced * If the rewrite mode is used embedded %-characters are replaced
@ -133,14 +125,7 @@ def pytest_runtest_setup(item):
config=item.config, op=op, left=left, right=right) config=item.config, op=op, left=left, right=right)
for new_expl in hook_result: for new_expl in hook_result:
if new_expl: if new_expl:
if (sum(len(p) for p in new_expl[1:]) > 80*8 and new_expl = truncate.truncate_if_required(new_expl, item)
item.config.option.verbose < 2 and
not _running_on_ci()):
show_max = 10
truncated_lines = len(new_expl) - show_max
new_expl[show_max:] = [py.builtin._totext(
'Detailed information truncated (%d more lines)'
', use "-vv" to show' % truncated_lines)]
new_expl = [line.replace("\n", "\\n") for line in new_expl] new_expl = [line.replace("\n", "\\n") for line in new_expl]
res = py.builtin._totext("\n~").join(new_expl) res = py.builtin._totext("\n~").join(new_expl)
if item.config.getvalue("assertmode") == "rewrite": if item.config.getvalue("assertmode") == "rewrite":

View File

@ -1,5 +1,5 @@
"""Rewrite assertion AST to produce nice error messages""" """Rewrite assertion AST to produce nice error messages"""
from __future__ import absolute_import, division, print_function
import ast import ast
import _ast import _ast
import errno import errno
@ -337,7 +337,7 @@ def _rewrite_test(config, fn):
return None, None return None, None
rewrite_asserts(tree, fn, config) rewrite_asserts(tree, fn, config)
try: try:
co = compile(tree, fn.strpath, "exec") co = compile(tree, fn.strpath, "exec", dont_inherit=True)
except SyntaxError: except SyntaxError:
# It's possible that this error is from some bug in the # It's possible that this error is from some bug in the
# assertion rewriting, but I don't know of a fast way to tell. # assertion rewriting, but I don't know of a fast way to tell.

View File

@ -0,0 +1,102 @@
"""
Utilities for truncating assertion output.
Current default behaviour is to truncate assertion explanations at
~8 terminal lines, unless running in "-vv" mode or running on CI.
"""
from __future__ import absolute_import, division, print_function
import os
import py
DEFAULT_MAX_LINES = 8
DEFAULT_MAX_CHARS = 8 * 80
USAGE_MSG = "use '-vv' to show"
def truncate_if_required(explanation, item, max_length=None):
"""
Truncate this assertion explanation if the given test item is eligible.
"""
if _should_truncate_item(item):
return _truncate_explanation(explanation)
return explanation
def _should_truncate_item(item):
"""
Whether or not this test item is eligible for truncation.
"""
verbose = item.config.option.verbose
return verbose < 2 and not _running_on_ci()
def _running_on_ci():
"""Check if we're currently running on a CI system."""
env_vars = ['CI', 'BUILD_NUMBER']
return any(var in os.environ for var in env_vars)
def _truncate_explanation(input_lines, max_lines=None, max_chars=None):
"""
Truncate given list of strings that makes up the assertion explanation.
Truncates to either 8 lines, or 640 characters - whichever the input reaches
first. The remaining lines will be replaced by a usage message.
"""
if max_lines is None:
max_lines = DEFAULT_MAX_LINES
if max_chars is None:
max_chars = DEFAULT_MAX_CHARS
# Check if truncation required
input_char_count = len("".join(input_lines))
if len(input_lines) <= max_lines and input_char_count <= max_chars:
return input_lines
# Truncate first to max_lines, and then truncate to max_chars if max_chars
# is exceeded.
truncated_explanation = input_lines[:max_lines]
truncated_explanation = _truncate_by_char_count(truncated_explanation, max_chars)
# Add ellipsis to final line
truncated_explanation[-1] = truncated_explanation[-1] + "..."
# Append useful message to explanation
truncated_line_count = len(input_lines) - len(truncated_explanation)
truncated_line_count += 1 # Account for the part-truncated final line
msg = '...Full output truncated'
if truncated_line_count == 1:
msg += ' ({0} line hidden)'.format(truncated_line_count)
else:
msg += ' ({0} lines hidden)'.format(truncated_line_count)
msg += ", {0}" .format(USAGE_MSG)
truncated_explanation.extend([
py.builtin._totext(""),
py.builtin._totext(msg),
])
return truncated_explanation
def _truncate_by_char_count(input_lines, max_chars):
# Check if truncation required
if len("".join(input_lines)) <= max_chars:
return input_lines
# Find point at which input length exceeds total allowed length
iterated_char_count = 0
for iterated_index, input_line in enumerate(input_lines):
if iterated_char_count + len(input_line) > max_chars:
break
iterated_char_count += len(input_line)
# Create truncated explanation with modified final line
truncated_result = input_lines[:iterated_index]
final_line = input_lines[iterated_index]
if final_line:
final_line_truncate_point = max_chars - iterated_char_count
final_line = final_line[:final_line_truncate_point]
truncated_result.append(final_line)
return truncated_result

View File

@ -1,4 +1,5 @@
"""Utilities for assertion debugging""" """Utilities for assertion debugging"""
from __future__ import absolute_import, division, print_function
import pprint import pprint
import _pytest._code import _pytest._code
@ -8,7 +9,7 @@ try:
except ImportError: except ImportError:
Sequence = list Sequence = list
BuiltinAssertionError = py.builtin.builtins.AssertionError
u = py.builtin._totext u = py.builtin._totext
# The _reprcompare attribute on the util module is used by the new assertion # The _reprcompare attribute on the util module is used by the new assertion
@ -256,8 +257,8 @@ def _compare_eq_dict(left, right, verbose=False):
explanation = [] explanation = []
common = set(left).intersection(set(right)) common = set(left).intersection(set(right))
same = dict((k, left[k]) for k in common if left[k] == right[k]) same = dict((k, left[k]) for k in common if left[k] == right[k])
if same and not verbose: if same and verbose < 2:
explanation += [u('Omitting %s identical items, use -v to show') % explanation += [u('Omitting %s identical items, use -vv to show') %
len(same)] len(same)]
elif same: elif same:
explanation += [u('Common items:')] explanation += [u('Common items:')]

View File

@ -4,7 +4,7 @@ merged implementation of the cache provider
the name cache was not chosen to ensure pluggy automatically the name cache was not chosen to ensure pluggy automatically
ignores the external pytest-cache ignores the external pytest-cache
""" """
from __future__ import absolute_import, division, print_function
import py import py
import pytest import pytest
import json import json
@ -139,11 +139,11 @@ class LFPlugin:
# running a subset of all tests with recorded failures outside # running a subset of all tests with recorded failures outside
# of the set of tests currently executing # of the set of tests currently executing
pass pass
elif self.config.getvalue("failedfirst"): elif self.config.getvalue("lf"):
items[:] = previously_failed + previously_passed
else:
items[:] = previously_failed items[:] = previously_failed
config.hook.pytest_deselected(items=previously_passed) config.hook.pytest_deselected(items=previously_passed)
else:
items[:] = previously_failed + previously_passed
def pytest_sessionfinish(self, session): def pytest_sessionfinish(self, session):
config = self.config config = self.config

View File

@ -2,18 +2,18 @@
per-test stdout/stderr capturing mechanism. per-test stdout/stderr capturing mechanism.
""" """
from __future__ import with_statement from __future__ import absolute_import, division, print_function
import contextlib import contextlib
import sys import sys
import os import os
from io import UnsupportedOperation
from tempfile import TemporaryFile from tempfile import TemporaryFile
import py import py
import pytest import pytest
from _pytest.compat import CaptureIO
from py.io import TextIO
from io import UnsupportedOperation
unicode = py.builtin.text unicode = py.builtin.text
patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'} patchsysdict = {0: 'stdin', 1: 'stdout', 2: 'stderr'}
@ -403,7 +403,7 @@ class SysCapture:
if name == "stdin": if name == "stdin":
tmpfile = DontReadFromInput() tmpfile = DontReadFromInput()
else: else:
tmpfile = TextIO() tmpfile = CaptureIO()
self.tmpfile = tmpfile self.tmpfile = tmpfile
def start(self): def start(self):
@ -449,7 +449,8 @@ class DontReadFromInput:
__iter__ = read __iter__ = read
def fileno(self): def fileno(self):
raise UnsupportedOperation("redirected Stdin is pseudofile, has no fileno()") raise UnsupportedOperation("redirected stdin is pseudofile, "
"has no fileno()")
def isatty(self): def isatty(self):
return False return False

View File

@ -1,6 +1,7 @@
""" """
python version compatibility code python version compatibility code
""" """
from __future__ import absolute_import, division, print_function
import sys import sys
import inspect import inspect
import types import types
@ -123,7 +124,7 @@ if sys.version_info[:2] == (2, 6):
if _PY3: if _PY3:
import codecs import codecs
imap = map
STRING_TYPES = bytes, str STRING_TYPES = bytes, str
def _escape_strings(val): def _escape_strings(val):
@ -157,6 +158,8 @@ if _PY3:
else: else:
STRING_TYPES = bytes, str, unicode STRING_TYPES = bytes, str, unicode
from itertools import imap # NOQA
def _escape_strings(val): def _escape_strings(val):
"""In py2 bytes and str are the same type, so return if it's a bytes """In py2 bytes and str are the same type, so return if it's a bytes
object, return it unchanged if it is a full ascii string, object, return it unchanged if it is a full ascii string,
@ -179,8 +182,18 @@ def get_real_func(obj):
""" gets the real function object of the (possibly) wrapped object by """ gets the real function object of the (possibly) wrapped object by
functools.wraps or functools.partial. functools.wraps or functools.partial.
""" """
while hasattr(obj, "__wrapped__"): start_obj = obj
obj = obj.__wrapped__ for i in range(100):
new_obj = getattr(obj, '__wrapped__', None)
if new_obj is None:
break
obj = new_obj
else:
raise ValueError(
("could not find real function of {start}"
"\nstopped at {current}").format(
start=py.io.saferepr(start_obj),
current=py.io.saferepr(obj)))
if isinstance(obj, functools.partial): if isinstance(obj, functools.partial):
obj = obj.func obj = obj.func
return obj return obj
@ -210,7 +223,7 @@ def safe_getattr(object, name, default):
""" Like getattr but return default upon any Exception. """ Like getattr but return default upon any Exception.
Attribute access can potentially fail for 'evil' Python objects. Attribute access can potentially fail for 'evil' Python objects.
See issue214 See issue #214.
""" """
try: try:
return getattr(object, name, default) return getattr(object, name, default)
@ -242,3 +255,51 @@ else:
v = unicode(v) v = unicode(v)
errors = 'replace' errors = 'replace'
return v.encode('utf-8', errors) return v.encode('utf-8', errors)
COLLECT_FAKEMODULE_ATTRIBUTES = (
'Collector',
'Module',
'Generator',
'Function',
'Instance',
'Session',
'Item',
'Class',
'File',
'_fillfuncargs',
)
def _setup_collect_fakemodule():
from types import ModuleType
import pytest
pytest.collect = ModuleType('pytest.collect')
pytest.collect.__all__ = [] # used for setns
for attr in COLLECT_FAKEMODULE_ATTRIBUTES:
setattr(pytest.collect, attr, getattr(pytest, attr))
if _PY2:
from py.io import TextIO as CaptureIO
else:
import io
class CaptureIO(io.TextIOWrapper):
def __init__(self):
super(CaptureIO, self).__init__(
io.BytesIO(),
encoding='UTF-8', newline='', write_through=True,
)
def getvalue(self):
return self.buffer.getvalue().decode('UTF-8')
class FuncargnamesCompatAttr(object):
""" helper class so that Metafunc, Function and FixtureRequest
don't need to each define the "funcargnames" compatibility attribute.
"""
@property
def funcargnames(self):
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
return self.fixturenames

View File

@ -1,4 +1,5 @@
""" command line options, ini-file and conftest.py processing. """ """ command line options, ini-file and conftest.py processing. """
from __future__ import absolute_import, division, print_function
import argparse import argparse
import shlex import shlex
import traceback import traceback
@ -7,7 +8,8 @@ import warnings
import py import py
# DON't import pytest here because it causes import cycle troubles # DON't import pytest here because it causes import cycle troubles
import sys, os import sys
import os
import _pytest._code import _pytest._code
import _pytest.hookspec # the extension point definitions import _pytest.hookspec # the extension point definitions
import _pytest.assertion import _pytest.assertion
@ -53,7 +55,6 @@ def main(args=None, plugins=None):
return 4 return 4
else: else:
try: try:
config.pluginmanager.check_pending()
return config.hook.pytest_cmdline_main(config=config) return config.hook.pytest_cmdline_main(config=config)
finally: finally:
config._ensure_unconfigure() config._ensure_unconfigure()
@ -98,7 +99,8 @@ default_plugins = (
"mark main terminal runner python fixtures debugging unittest capture skipping " "mark main terminal runner python fixtures debugging unittest capture skipping "
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion " "tmpdir monkeypatch recwarn pastebin helpconfig nose assertion "
"junitxml resultlog doctest cacheprovider freeze_support " "junitxml resultlog doctest cacheprovider freeze_support "
"setuponly setupplan").split() "setuponly setupplan warnings").split()
builtin_plugins = set(default_plugins) builtin_plugins = set(default_plugins)
builtin_plugins.add("pytester") builtin_plugins.add("pytester")
@ -251,6 +253,9 @@ class PytestPluginManager(PluginManager):
if ret: if ret:
self.hook.pytest_plugin_registered.call_historic( self.hook.pytest_plugin_registered.call_historic(
kwargs=dict(plugin=plugin, manager=self)) kwargs=dict(plugin=plugin, manager=self))
if isinstance(plugin, types.ModuleType):
self.consider_module(plugin)
return ret return ret
def getplugin(self, name): def getplugin(self, name):
@ -395,8 +400,7 @@ class PytestPluginManager(PluginManager):
self.import_plugin(arg) self.import_plugin(arg)
def consider_conftest(self, conftestmodule): def consider_conftest(self, conftestmodule):
if self.register(conftestmodule, name=conftestmodule.__file__): self.register(conftestmodule, name=conftestmodule.__file__)
self.consider_module(conftestmodule)
def consider_env(self): def consider_env(self):
self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS")) self._import_plugin_specs(os.environ.get("PYTEST_PLUGINS"))
@ -414,7 +418,8 @@ class PytestPluginManager(PluginManager):
# "terminal" or "capture". Those plugins are registered under their # "terminal" or "capture". Those plugins are registered under their
# basename for historic purposes but must be imported with the # basename for historic purposes but must be imported with the
# _pytest prefix. # _pytest prefix.
assert isinstance(modname, str), "module name as string required, got %r" % modname assert isinstance(modname, (py.builtin.text, str)), "module name as text required, got %r" % modname
modname = str(modname)
if self.get_plugin(modname) is not None: if self.get_plugin(modname) is not None:
return return
if modname in builtin_plugins: if modname in builtin_plugins:
@ -439,7 +444,6 @@ class PytestPluginManager(PluginManager):
else: else:
mod = sys.modules[importspec] mod = sys.modules[importspec]
self.register(mod, modname) self.register(mod, modname)
self.consider_module(mod)
def _get_plugin_specs_as_list(specs): def _get_plugin_specs_as_list(specs):
@ -910,11 +914,11 @@ class Config(object):
fin = self._cleanup.pop() fin = self._cleanup.pop()
fin() fin()
def warn(self, code, message, fslocation=None): def warn(self, code, message, fslocation=None, nodeid=None):
""" generate a warning for this test session. """ """ generate a warning for this test session. """
self.hook.pytest_logwarning.call_historic(kwargs=dict( self.hook.pytest_logwarning.call_historic(kwargs=dict(
code=code, message=message, code=code, message=message,
fslocation=fslocation, nodeid=None)) fslocation=fslocation, nodeid=nodeid))
def get_terminal_writer(self): def get_terminal_writer(self):
return self.pluginmanager.get_plugin("terminalreporter")._tw return self.pluginmanager.get_plugin("terminalreporter")._tw

View File

@ -1,9 +1,8 @@
""" interactive debugging with PDB, the Python Debugger. """ """ interactive debugging with PDB, the Python Debugger. """
from __future__ import absolute_import from __future__ import absolute_import, division, print_function
import pdb import pdb
import sys import sys
import pytest
def pytest_addoption(parser): def pytest_addoption(parser):
@ -16,19 +15,17 @@ def pytest_addoption(parser):
help="start a custom interactive Python debugger on errors. " help="start a custom interactive Python debugger on errors. "
"For example: --pdbcls=IPython.terminal.debugger:TerminalPdb") "For example: --pdbcls=IPython.terminal.debugger:TerminalPdb")
def pytest_namespace():
return {'set_trace': pytestPDB().set_trace}
def pytest_configure(config): def pytest_configure(config):
if config.getvalue("usepdb") or config.getvalue("usepdb_cls"): if config.getvalue("usepdb_cls"):
modname, classname = config.getvalue("usepdb_cls").split(":")
__import__(modname)
pdb_cls = getattr(sys.modules[modname], classname)
else:
pdb_cls = pdb.Pdb
if config.getvalue("usepdb"):
config.pluginmanager.register(PdbInvoke(), 'pdbinvoke') config.pluginmanager.register(PdbInvoke(), 'pdbinvoke')
if config.getvalue("usepdb_cls"):
modname, classname = config.getvalue("usepdb_cls").split(":")
__import__(modname)
pdb_cls = getattr(sys.modules[modname], classname)
else:
pdb_cls = pdb.Pdb
pytestPDB._pdb_cls = pdb_cls
old = (pdb.set_trace, pytestPDB._pluginmanager) old = (pdb.set_trace, pytestPDB._pluginmanager)
@ -37,9 +34,10 @@ def pytest_configure(config):
pytestPDB._config = None pytestPDB._config = None
pytestPDB._pdb_cls = pdb.Pdb pytestPDB._pdb_cls = pdb.Pdb
pdb.set_trace = pytest.set_trace pdb.set_trace = pytestPDB.set_trace
pytestPDB._pluginmanager = config.pluginmanager pytestPDB._pluginmanager = config.pluginmanager
pytestPDB._config = config pytestPDB._config = config
pytestPDB._pdb_cls = pdb_cls
config._cleanup.append(fin) config._cleanup.append(fin)
class pytestPDB: class pytestPDB:
@ -48,19 +46,20 @@ class pytestPDB:
_config = None _config = None
_pdb_cls = pdb.Pdb _pdb_cls = pdb.Pdb
def set_trace(self): @classmethod
def set_trace(cls):
""" invoke PDB set_trace debugging, dropping any IO capturing. """ """ invoke PDB set_trace debugging, dropping any IO capturing. """
import _pytest.config import _pytest.config
frame = sys._getframe().f_back frame = sys._getframe().f_back
if self._pluginmanager is not None: if cls._pluginmanager is not None:
capman = self._pluginmanager.getplugin("capturemanager") capman = cls._pluginmanager.getplugin("capturemanager")
if capman: if capman:
capman.suspendcapture(in_=True) capman.suspendcapture(in_=True)
tw = _pytest.config.create_terminal_writer(self._config) tw = _pytest.config.create_terminal_writer(cls._config)
tw.line() tw.line()
tw.sep(">", "PDB set_trace (IO-capturing turned off)") tw.sep(">", "PDB set_trace (IO-capturing turned off)")
self._pluginmanager.hook.pytest_enter_pdb(config=self._config) cls._pluginmanager.hook.pytest_enter_pdb(config=cls._config)
self._pdb_cls().set_trace(frame) cls._pdb_cls().set_trace(frame)
class PdbInvoke: class PdbInvoke:
@ -74,7 +73,7 @@ class PdbInvoke:
def pytest_internalerror(self, excrepr, excinfo): def pytest_internalerror(self, excrepr, excinfo):
for line in str(excrepr).split("\n"): for line in str(excrepr).split("\n"):
sys.stderr.write("INTERNALERROR> %s\n" %line) sys.stderr.write("INTERNALERROR> %s\n" % line)
sys.stderr.flush() sys.stderr.flush()
tb = _postmortem_traceback(excinfo) tb = _postmortem_traceback(excinfo)
post_mortem(tb) post_mortem(tb)

View File

@ -5,7 +5,7 @@ that is planned to be removed in the next pytest release.
Keeping it in a central location makes it easy to track what is deprecated and should Keeping it in a central location makes it easy to track what is deprecated and should
be removed when the time comes. be removed when the time comes.
""" """
from __future__ import absolute_import, division, print_function
MAIN_STR_ARGS = 'passing a string to pytest.main() is deprecated, ' \ MAIN_STR_ARGS = 'passing a string to pytest.main() is deprecated, ' \
'pass a list of arguments instead.' 'pass a list of arguments instead.'

View File

@ -1,5 +1,5 @@
""" discover and run doctests in modules and test files.""" """ discover and run doctests in modules and test files."""
from __future__ import absolute_import from __future__ import absolute_import, division, print_function
import traceback import traceback
@ -25,6 +25,7 @@ DOCTEST_REPORT_CHOICES = (
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addini('doctest_optionflags', 'option flags for doctests', parser.addini('doctest_optionflags', 'option flags for doctests',
type="args", default=["ELLIPSIS"]) type="args", default=["ELLIPSIS"])
parser.addini("doctest_encoding", 'encoding used for doctest files', default="utf-8")
group = parser.getgroup("collect") group = parser.getgroup("collect")
group.addoption("--doctest-modules", group.addoption("--doctest-modules",
action="store_true", default=False, action="store_true", default=False,
@ -162,7 +163,6 @@ def get_optionflags(parent):
flag_acc |= flag_lookup_table[flag] flag_acc |= flag_lookup_table[flag]
return flag_acc return flag_acc
class DoctestTextfile(pytest.Module): class DoctestTextfile(pytest.Module):
obj = None obj = None
@ -171,7 +171,8 @@ class DoctestTextfile(pytest.Module):
# inspired by doctest.testfile; ideally we would use it directly, # inspired by doctest.testfile; ideally we would use it directly,
# but it doesn't support passing a custom checker # but it doesn't support passing a custom checker
text = self.fspath.read() encoding = self.config.getini("doctest_encoding")
text = self.fspath.read_text(encoding)
filename = str(self.fspath) filename = str(self.fspath)
name = self.fspath.basename name = self.fspath.basename
globs = {'__name__': '__main__'} globs = {'__name__': '__main__'}

View File

@ -1,9 +1,9 @@
from __future__ import absolute_import, division, print_function
import sys import sys
from py._code.code import FormattedExcinfo from py._code.code import FormattedExcinfo
import py import py
import pytest
import warnings import warnings
import inspect import inspect
@ -16,8 +16,16 @@ from _pytest.compat import (
getlocation, getfuncargnames, getlocation, getfuncargnames,
safe_getattr, safe_getattr,
) )
from _pytest.runner import fail
from _pytest.compat import FuncargnamesCompatAttr
def pytest_sessionstart(session): def pytest_sessionstart(session):
import _pytest.python
scopename2class.update({
'class': _pytest.python.Class,
'module': _pytest.python.Module,
'function': _pytest.main.Item,
})
session._fixturemanager = FixtureManager(session) session._fixturemanager = FixtureManager(session)
@ -44,19 +52,6 @@ def scopeproperty(name=None, doc=None):
return decoratescope return decoratescope
def pytest_namespace():
scopename2class.update({
'class': pytest.Class,
'module': pytest.Module,
'function': pytest.Item,
})
return {
'fixture': fixture,
'yield_fixture': yield_fixture,
'collect': {'_fillfuncargs': fillfixtures}
}
def get_scope_node(node, scope): def get_scope_node(node, scope):
cls = scopename2class.get(scope) cls = scopename2class.get(scope)
if cls is None: if cls is None:
@ -104,7 +99,7 @@ def add_funcarg_pseudo_fixture_def(collector, metafunc, fixturemanager):
if scope != "function": if scope != "function":
node = get_scope_node(collector, scope) node = get_scope_node(collector, scope)
if node is None: if node is None:
assert scope == "class" and isinstance(collector, pytest.Module) assert scope == "class" and isinstance(collector, _pytest.python.Module)
# use module-level collector for class-scope (for now) # use module-level collector for class-scope (for now)
node = collector node = collector
if node and argname in node._name2pseudofixturedef: if node and argname in node._name2pseudofixturedef:
@ -220,17 +215,6 @@ def slice_items(items, ignore, scoped_argkeys_cache):
return items, None, None, None return items, None, None, None
class FuncargnamesCompatAttr:
""" helper class so that Metafunc, Function and FixtureRequest
don't need to each define the "funcargnames" compatibility attribute.
"""
@property
def funcargnames(self):
""" alias attribute for ``fixturenames`` for pre-2.3 compatibility"""
return self.fixturenames
def fillfixtures(function): def fillfixtures(function):
""" fill missing funcargs for a test function. """ """ fill missing funcargs for a test function. """
try: try:
@ -326,7 +310,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
@scopeproperty("class") @scopeproperty("class")
def cls(self): def cls(self):
""" class (can be None) where the test function was collected. """ """ class (can be None) where the test function was collected. """
clscol = self._pyfuncitem.getparent(pytest.Class) clscol = self._pyfuncitem.getparent(_pytest.python.Class)
if clscol: if clscol:
return clscol.obj return clscol.obj
@ -344,7 +328,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
@scopeproperty() @scopeproperty()
def module(self): def module(self):
""" python module object where the test function was collected. """ """ python module object where the test function was collected. """
return self._pyfuncitem.getparent(pytest.Module).obj return self._pyfuncitem.getparent(_pytest.python.Module).obj
@scopeproperty() @scopeproperty()
def fspath(self): def fspath(self):
@ -507,7 +491,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
source_lineno, source_lineno,
) )
) )
pytest.fail(msg) fail(msg)
else: else:
# indices might not be set if old-style metafunc.addcall() was used # indices might not be set if old-style metafunc.addcall() was used
param_index = funcitem.callspec.indices.get(argname, 0) param_index = funcitem.callspec.indices.get(argname, 0)
@ -540,10 +524,10 @@ class FixtureRequest(FuncargnamesCompatAttr):
if scopemismatch(invoking_scope, requested_scope): if scopemismatch(invoking_scope, requested_scope):
# try to report something helpful # try to report something helpful
lines = self._factorytraceback() lines = self._factorytraceback()
pytest.fail("ScopeMismatch: You tried to access the %r scoped " fail("ScopeMismatch: You tried to access the %r scoped "
"fixture %r with a %r scoped request object, " "fixture %r with a %r scoped request object, "
"involved factories\n%s" %( "involved factories\n%s" % (
(requested_scope, argname, invoking_scope, "\n".join(lines))), (requested_scope, argname, invoking_scope, "\n".join(lines))),
pytrace=False) pytrace=False)
def _factorytraceback(self): def _factorytraceback(self):
@ -553,7 +537,7 @@ class FixtureRequest(FuncargnamesCompatAttr):
fs, lineno = getfslineno(factory) fs, lineno = getfslineno(factory)
p = self._pyfuncitem.session.fspath.bestrelpath(fs) p = self._pyfuncitem.session.fspath.bestrelpath(fs)
args = _format_args(factory) args = _format_args(factory)
lines.append("%s:%d: def %s%s" %( lines.append("%s:%d: def %s%s" % (
p, lineno, factory.__name__, args)) p, lineno, factory.__name__, args))
return lines return lines
@ -698,8 +682,9 @@ def fail_fixturefunc(fixturefunc, msg):
fs, lineno = getfslineno(fixturefunc) fs, lineno = getfslineno(fixturefunc)
location = "%s:%s" % (fs, lineno+1) location = "%s:%s" % (fs, lineno+1)
source = _pytest._code.Source(fixturefunc) source = _pytest._code.Source(fixturefunc)
pytest.fail(msg + ":\n\n" + str(source.indent()) + "\n" + location, fail(msg + ":\n\n" + str(source.indent()) + "\n" + location,
pytrace=False) pytrace=False)
def call_fixture_func(fixturefunc, request, kwargs): def call_fixture_func(fixturefunc, request, kwargs):
yieldctx = is_generator(fixturefunc) yieldctx = is_generator(fixturefunc)
@ -1080,7 +1065,7 @@ class FixtureManager:
continue continue
marker = defaultfuncargprefixmarker marker = defaultfuncargprefixmarker
from _pytest import deprecated from _pytest import deprecated
self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name)) self.config.warn('C1', deprecated.FUNCARG_PREFIX.format(name=name), nodeid=nodeid)
name = name[len(self._argprefix):] name = name[len(self._argprefix):]
elif not isinstance(marker, FixtureFunctionMarker): elif not isinstance(marker, FixtureFunctionMarker):
# magic globals with __getattr__ might have got us a wrong # magic globals with __getattr__ might have got us a wrong

View File

@ -2,9 +2,8 @@
Provides a function to report all internal modules for using freezing tools Provides a function to report all internal modules for using freezing tools
pytest pytest
""" """
from __future__ import absolute_import, division, print_function
def pytest_namespace():
return {'freeze_includes': freeze_includes}
def freeze_includes(): def freeze_includes():
@ -42,4 +41,4 @@ def _iter_all_modules(package, prefix=''):
for m in _iter_all_modules(os.path.join(path, name), prefix=name + '.'): for m in _iter_all_modules(os.path.join(path, name), prefix=name + '.'):
yield prefix + m yield prefix + m
else: else:
yield prefix + name yield prefix + name

View File

@ -1,4 +1,6 @@
""" version info, help messages, tracing configuration. """ """ version info, help messages, tracing configuration. """
from __future__ import absolute_import, division, print_function
import py import py
import pytest import pytest
import os, sys import os, sys

View File

@ -16,7 +16,9 @@ def pytest_addhooks(pluginmanager):
@hookspec(historic=True) @hookspec(historic=True)
def pytest_namespace(): def pytest_namespace():
"""return dict of name->object to be made globally available in """
DEPRECATED: this hook causes direct monkeypatching on pytest, its use is strongly discouraged
return dict of name->object to be made globally available in
the pytest namespace. This hook is called at plugin registration the pytest namespace. This hook is called at plugin registration
time. time.
""" """
@ -157,9 +159,10 @@ def pytest_generate_tests(metafunc):
""" generate (multiple) parametrized calls to a test function.""" """ generate (multiple) parametrized calls to a test function."""
@hookspec(firstresult=True) @hookspec(firstresult=True)
def pytest_make_parametrize_id(config, val): def pytest_make_parametrize_id(config, val, argname):
"""Return a user-friendly string representation of the given ``val`` that will be used """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``. by @pytest.mark.parametrize calls. Return None if the hook doesn't know about ``val``.
The parameter name is available as ``argname``, if required.
""" """
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------

View File

@ -4,9 +4,11 @@
Based on initial code from Ross Lawley. Based on initial code from Ross Lawley.
Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/
src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
""" """
# Output conforms to https://github.com/jenkinsci/xunit-plugin/blob/master/ from __future__ import absolute_import, division, print_function
# src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd
import functools import functools
import py import py
@ -105,6 +107,8 @@ class _NodeReporter(object):
} }
if testreport.location[1] is not None: if testreport.location[1] is not None:
attrs["line"] = testreport.location[1] attrs["line"] = testreport.location[1]
if hasattr(testreport, "url"):
attrs["url"] = testreport.url
self.attrs = attrs self.attrs = attrs
def to_xml(self): def to_xml(self):
@ -222,13 +226,14 @@ def pytest_addoption(parser):
metavar="str", metavar="str",
default=None, default=None,
help="prepend prefix to classnames in junit-xml output") help="prepend prefix to classnames in junit-xml output")
parser.addini("junit_suite_name", "Test suite name for JUnit report", default="pytest")
def pytest_configure(config): def pytest_configure(config):
xmlpath = config.option.xmlpath xmlpath = config.option.xmlpath
# prevent opening xmllog on slave nodes (xdist) # prevent opening xmllog on slave nodes (xdist)
if xmlpath and not hasattr(config, 'slaveinput'): if xmlpath and not hasattr(config, 'slaveinput'):
config._xml = LogXML(xmlpath, config.option.junitprefix) config._xml = LogXML(xmlpath, config.option.junitprefix, config.getini("junit_suite_name"))
config.pluginmanager.register(config._xml) config.pluginmanager.register(config._xml)
@ -255,10 +260,11 @@ def mangle_test_address(address):
class LogXML(object): class LogXML(object):
def __init__(self, logfile, prefix): def __init__(self, logfile, prefix, suite_name="pytest"):
logfile = os.path.expanduser(os.path.expandvars(logfile)) logfile = os.path.expanduser(os.path.expandvars(logfile))
self.logfile = os.path.normpath(os.path.abspath(logfile)) self.logfile = os.path.normpath(os.path.abspath(logfile))
self.prefix = prefix self.prefix = prefix
self.suite_name = suite_name
self.stats = dict.fromkeys([ self.stats = dict.fromkeys([
'error', 'error',
'passed', 'passed',
@ -268,6 +274,9 @@ class LogXML(object):
self.node_reporters = {} # nodeid -> _NodeReporter self.node_reporters = {} # nodeid -> _NodeReporter
self.node_reporters_ordered = [] self.node_reporters_ordered = []
self.global_properties = [] self.global_properties = []
# List of reports that failed on call but teardown is pending.
self.open_reports = []
self.cnt_double_fail_tests = 0
def finalize(self, report): def finalize(self, report):
nodeid = getattr(report, 'nodeid', report) nodeid = getattr(report, 'nodeid', report)
@ -327,14 +336,33 @@ class LogXML(object):
-> teardown node2 -> teardown node2
-> teardown node1 -> teardown node1
""" """
close_report = None
if report.passed: if report.passed:
if report.when == "call": # ignore setup/teardown if report.when == "call": # ignore setup/teardown
reporter = self._opentestcase(report) reporter = self._opentestcase(report)
reporter.append_pass(report) reporter.append_pass(report)
elif report.failed: elif report.failed:
if report.when == "teardown":
# The following vars are needed when xdist plugin is used
report_wid = getattr(report, "worker_id", None)
report_ii = getattr(report, "item_index", None)
close_report = next(
(rep for rep in self.open_reports
if (rep.nodeid == report.nodeid and
getattr(rep, "item_index", None) == report_ii and
getattr(rep, "worker_id", None) == report_wid
)
), None)
if close_report:
# We need to open new testcase in case we have failure in
# call and error in teardown in order to follow junit
# schema
self.finalize(close_report)
self.cnt_double_fail_tests += 1
reporter = self._opentestcase(report) reporter = self._opentestcase(report)
if report.when == "call": if report.when == "call":
reporter.append_failure(report) reporter.append_failure(report)
self.open_reports.append(report)
else: else:
reporter.append_error(report) reporter.append_error(report)
elif report.skipped: elif report.skipped:
@ -345,6 +373,17 @@ class LogXML(object):
reporter = self._opentestcase(report) reporter = self._opentestcase(report)
reporter.write_captured_output(report) reporter.write_captured_output(report)
self.finalize(report) self.finalize(report)
report_wid = getattr(report, "worker_id", None)
report_ii = getattr(report, "item_index", None)
close_report = next(
(rep for rep in self.open_reports
if (rep.nodeid == report.nodeid and
getattr(rep, "item_index", None) == report_ii and
getattr(rep, "worker_id", None) == report_wid
)
), None)
if close_report:
self.open_reports.remove(close_report)
def update_testcase_duration(self, report): def update_testcase_duration(self, report):
"""accumulates total duration for nodeid from given report and updates """accumulates total duration for nodeid from given report and updates
@ -377,14 +416,15 @@ class LogXML(object):
suite_stop_time = time.time() suite_stop_time = time.time()
suite_time_delta = suite_stop_time - self.suite_start_time suite_time_delta = suite_stop_time - self.suite_start_time
numtests = self.stats['passed'] + self.stats['failure'] + self.stats['skipped'] + self.stats['error'] numtests = (self.stats['passed'] + self.stats['failure'] +
self.stats['skipped'] + self.stats['error'] -
self.cnt_double_fail_tests)
logfile.write('<?xml version="1.0" encoding="utf-8"?>') logfile.write('<?xml version="1.0" encoding="utf-8"?>')
logfile.write(Junit.testsuite( logfile.write(Junit.testsuite(
self._get_global_properties_node(), self._get_global_properties_node(),
[x.to_xml() for x in self.node_reporters_ordered], [x.to_xml() for x in self.node_reporters_ordered],
name="pytest", name=self.suite_name,
errors=self.stats['error'], errors=self.stats['error'],
failures=self.stats['failure'], failures=self.stats['failure'],
skips=self.stats['skipped'], skips=self.stats['skipped'],

View File

@ -1,4 +1,6 @@
""" core implementation of testing process: init, session, runtest loop. """ """ core implementation of testing process: init, session, runtest loop. """
from __future__ import absolute_import, division, print_function
import functools import functools
import os import os
import sys import sys
@ -6,14 +8,13 @@ import sys
import _pytest import _pytest
import _pytest._code import _pytest._code
import py import py
import pytest
try: try:
from collections import MutableMapping as MappingMixin from collections import MutableMapping as MappingMixin
except ImportError: except ImportError:
from UserDict import DictMixin as MappingMixin from UserDict import DictMixin as MappingMixin
from _pytest.config import directory_arg from _pytest.config import directory_arg, UsageError, hookimpl
from _pytest.runner import collect_one_node from _pytest.runner import collect_one_node, exit
tracebackcutdir = py.path.local(_pytest.__file__).dirpath() tracebackcutdir = py.path.local(_pytest.__file__).dirpath()
@ -25,9 +26,10 @@ EXIT_INTERNALERROR = 3
EXIT_USAGEERROR = 4 EXIT_USAGEERROR = 4
EXIT_NOTESTSCOLLECTED = 5 EXIT_NOTESTSCOLLECTED = 5
def pytest_addoption(parser): def pytest_addoption(parser):
parser.addini("norecursedirs", "directory patterns to avoid for recursion", parser.addini("norecursedirs", "directory patterns to avoid for recursion",
type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg']) type="args", default=['.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv'])
parser.addini("testpaths", "directories to search for tests when no files or directories are given in the command line.", parser.addini("testpaths", "directories to search for tests when no files or directories are given in the command line.",
type="args", default=[]) type="args", default=[])
#parser.addini("dirpatterns", #parser.addini("dirpatterns",
@ -75,13 +77,18 @@ def pytest_addoption(parser):
help="base temporary directory for this test run.") help="base temporary directory for this test run.")
def pytest_namespace(): def pytest_namespace():
collect = dict(Item=Item, Collector=Collector, File=File, Session=Session) """keeping this one works around a deeper startup issue in pytest
return dict(collect=collect)
i tried to find it for a while but the amount of time turned unsustainable,
so i put a hack in to revisit later
"""
return {}
def pytest_configure(config): def pytest_configure(config):
pytest.config = config # compatibility __import__('pytest').config = config # compatibiltiy
def wrap_session(config, doit): def wrap_session(config, doit):
@ -96,12 +103,11 @@ def wrap_session(config, doit):
config.hook.pytest_sessionstart(session=session) config.hook.pytest_sessionstart(session=session)
initstate = 2 initstate = 2
session.exitstatus = doit(config, session) or 0 session.exitstatus = doit(config, session) or 0
except pytest.UsageError: except UsageError:
raise raise
except KeyboardInterrupt: except KeyboardInterrupt:
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo()
if initstate < 2 and isinstance( if initstate < 2 and isinstance(excinfo.value, exit.Exception):
excinfo.value, pytest.exit.Exception):
sys.stderr.write('{0}: {1}\n'.format( sys.stderr.write('{0}: {1}\n'.format(
excinfo.typename, excinfo.value.msg)) excinfo.typename, excinfo.value.msg))
config.hook.pytest_keyboard_interrupt(excinfo=excinfo) config.hook.pytest_keyboard_interrupt(excinfo=excinfo)
@ -123,9 +129,11 @@ def wrap_session(config, doit):
config._ensure_unconfigure() config._ensure_unconfigure()
return session.exitstatus return session.exitstatus
def pytest_cmdline_main(config): def pytest_cmdline_main(config):
return wrap_session(config, _main) return wrap_session(config, _main)
def _main(config, session): def _main(config, session):
""" default command line protocol for initialization, session, """ default command line protocol for initialization, session,
running tests and reporting. """ running tests and reporting. """
@ -137,9 +145,11 @@ def _main(config, session):
elif session.testscollected == 0: elif session.testscollected == 0:
return EXIT_NOTESTSCOLLECTED return EXIT_NOTESTSCOLLECTED
def pytest_collection(session): def pytest_collection(session):
return session.perform_collect() return session.perform_collect()
def pytest_runtestloop(session): def pytest_runtestloop(session):
if (session.testsfailed and if (session.testsfailed and
not session.config.option.continue_on_collection_errors): not session.config.option.continue_on_collection_errors):
@ -156,6 +166,7 @@ def pytest_runtestloop(session):
raise session.Interrupted(session.shouldstop) raise session.Interrupted(session.shouldstop)
return True return True
def pytest_ignore_collect(path, config): def pytest_ignore_collect(path, config):
p = path.dirpath() p = path.dirpath()
ignore_paths = config._getconftest_pathlist("collect_ignore", path=p) ignore_paths = config._getconftest_pathlist("collect_ignore", path=p)
@ -203,7 +214,7 @@ class _CompatProperty(object):
# "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format( # "usage of {owner!r}.{name} is deprecated, please use pytest.{name} instead".format(
# name=self.name, owner=type(owner).__name__), # name=self.name, owner=type(owner).__name__),
# PendingDeprecationWarning, stacklevel=2) # PendingDeprecationWarning, stacklevel=2)
return getattr(pytest, self.name) return getattr(__import__('pytest'), self.name)
@ -287,7 +298,7 @@ class Node(object):
def _getcustomclass(self, name): def _getcustomclass(self, name):
maybe_compatprop = getattr(type(self), name) maybe_compatprop = getattr(type(self), name)
if isinstance(maybe_compatprop, _CompatProperty): if isinstance(maybe_compatprop, _CompatProperty):
return getattr(pytest, name) return getattr(__import__('pytest'), name)
else: else:
cls = getattr(self, name) cls = getattr(self, name)
# TODO: reenable in the features branch # TODO: reenable in the features branch
@ -307,9 +318,6 @@ class Node(object):
fslocation = getattr(self, "location", None) fslocation = getattr(self, "location", None)
if fslocation is None: if fslocation is None:
fslocation = getattr(self, "fspath", None) fslocation = getattr(self, "fspath", None)
else:
fslocation = "%s:%s" % (fslocation[0], fslocation[1] + 1)
self.ihook.pytest_logwarning.call_historic(kwargs=dict( self.ihook.pytest_logwarning.call_historic(kwargs=dict(
code=code, message=message, code=code, message=message,
nodeid=self.nodeid, fslocation=fslocation)) nodeid=self.nodeid, fslocation=fslocation))
@ -370,9 +378,9 @@ class Node(object):
``marker`` can be a string or pytest.mark.* instance. ``marker`` can be a string or pytest.mark.* instance.
""" """
from _pytest.mark import MarkDecorator from _pytest.mark import MarkDecorator, MARK_GEN
if isinstance(marker, py.builtin._basestring): if isinstance(marker, py.builtin._basestring):
marker = MarkDecorator(marker) marker = getattr(MARK_GEN, marker)
elif not isinstance(marker, MarkDecorator): elif not isinstance(marker, MarkDecorator):
raise ValueError("is not a string or pytest.mark.* Marker") raise ValueError("is not a string or pytest.mark.* Marker")
self.keywords[marker.name] = marker self.keywords[marker.name] = marker
@ -471,10 +479,6 @@ class Collector(Node):
return str(exc.args[0]) return str(exc.args[0])
return self._repr_failure_py(excinfo, style="short") return self._repr_failure_py(excinfo, style="short")
def _memocollect(self):
""" internal helper method to cache results of calling collect(). """
return self._memoizedcall('_collected', lambda: list(self.collect()))
def _prunetraceback(self, excinfo): def _prunetraceback(self, excinfo):
if hasattr(self, 'fspath'): if hasattr(self, 'fspath'):
traceback = excinfo.traceback traceback = excinfo.traceback
@ -562,12 +566,12 @@ class Session(FSCollector):
def _makeid(self): def _makeid(self):
return "" return ""
@pytest.hookimpl(tryfirst=True) @hookimpl(tryfirst=True)
def pytest_collectstart(self): def pytest_collectstart(self):
if self.shouldstop: if self.shouldstop:
raise self.Interrupted(self.shouldstop) raise self.Interrupted(self.shouldstop)
@pytest.hookimpl(tryfirst=True) @hookimpl(tryfirst=True)
def pytest_runtest_logreport(self, report): def pytest_runtest_logreport(self, report):
if report.failed and not hasattr(report, 'wasxfail'): if report.failed and not hasattr(report, 'wasxfail'):
self.testsfailed += 1 self.testsfailed += 1
@ -598,6 +602,7 @@ class Session(FSCollector):
hook = self.config.hook hook = self.config.hook
try: try:
items = self._perform_collect(args, genitems) items = self._perform_collect(args, genitems)
self.config.pluginmanager.check_pending()
hook.pytest_collection_modifyitems(session=self, hook.pytest_collection_modifyitems(session=self,
config=self.config, items=items) config=self.config, items=items)
finally: finally:
@ -626,8 +631,8 @@ class Session(FSCollector):
for arg, exc in self._notfound: for arg, exc in self._notfound:
line = "(no name %r in any of %r)" % (arg, exc.args[0]) line = "(no name %r in any of %r)" % (arg, exc.args[0])
errors.append("not found: %s\n%s" % (arg, line)) errors.append("not found: %s\n%s" % (arg, line))
#XXX: test this # XXX: test this
raise pytest.UsageError(*errors) raise UsageError(*errors)
if not genitems: if not genitems:
return rep.result return rep.result
else: else:
@ -655,7 +660,7 @@ class Session(FSCollector):
names = self._parsearg(arg) names = self._parsearg(arg)
path = names.pop(0) path = names.pop(0)
if path.check(dir=1): if path.check(dir=1):
assert not names, "invalid arg %r" %(arg,) assert not names, "invalid arg %r" % (arg,)
for path in path.visit(fil=lambda x: x.check(file=1), for path in path.visit(fil=lambda x: x.check(file=1),
rec=self._recurse, bf=True, sort=True): rec=self._recurse, bf=True, sort=True):
for x in self._collectfile(path): for x in self._collectfile(path):
@ -714,9 +719,11 @@ class Session(FSCollector):
path = self.config.invocation_dir.join(relpath, abs=True) path = self.config.invocation_dir.join(relpath, abs=True)
if not path.check(): if not path.check():
if self.config.option.pyargs: if self.config.option.pyargs:
raise pytest.UsageError("file or package not found: " + arg + " (missing __init__.py?)") raise UsageError(
"file or package not found: " + arg +
" (missing __init__.py?)")
else: else:
raise pytest.UsageError("file not found: " + arg) raise UsageError("file not found: " + arg)
parts[0] = path parts[0] = path
return parts return parts
@ -739,11 +746,11 @@ class Session(FSCollector):
nextnames = names[1:] nextnames = names[1:]
resultnodes = [] resultnodes = []
for node in matching: for node in matching:
if isinstance(node, pytest.Item): if isinstance(node, Item):
if not names: if not names:
resultnodes.append(node) resultnodes.append(node)
continue continue
assert isinstance(node, pytest.Collector) assert isinstance(node, Collector)
rep = collect_one_node(node) rep = collect_one_node(node)
if rep.passed: if rep.passed:
has_matched = False has_matched = False
@ -761,11 +768,11 @@ class Session(FSCollector):
def genitems(self, node): def genitems(self, node):
self.trace("genitems", node) self.trace("genitems", node)
if isinstance(node, pytest.Item): if isinstance(node, Item):
node.ihook.pytest_itemcollected(item=node) node.ihook.pytest_itemcollected(item=node)
yield node yield node
else: else:
assert isinstance(node, pytest.Collector) assert isinstance(node, Collector)
rep = collect_one_node(node) rep = collect_one_node(node)
if rep.passed: if rep.passed:
for subnode in rep.result: for subnode in rep.result:

View File

@ -1,5 +1,64 @@
""" generic mechanism for marking and selecting python functions. """ """ generic mechanism for marking and selecting python functions. """
from __future__ import absolute_import, division, print_function
import inspect import inspect
from collections import namedtuple
from operator import attrgetter
from .compat import imap
def alias(name):
return property(attrgetter(name), doc='alias for ' + name)
class ParameterSet(namedtuple('ParameterSet', 'values, marks, id')):
@classmethod
def param(cls, *values, **kw):
marks = kw.pop('marks', ())
if isinstance(marks, MarkDecorator):
marks = marks,
else:
assert isinstance(marks, (tuple, list, set))
def param_extract_id(id=None):
return id
id = param_extract_id(**kw)
return cls(values, marks, id)
@classmethod
def extract_from(cls, parameterset, legacy_force_tuple=False):
"""
:param parameterset:
a legacy style parameterset that may or may not be a tuple,
and may or may not be wrapped into a mess of mark objects
:param legacy_force_tuple:
enforce tuple wrapping so single argument tuple values
don't get decomposed and break tests
"""
if isinstance(parameterset, cls):
return parameterset
if not isinstance(parameterset, MarkDecorator) and legacy_force_tuple:
return cls.param(parameterset)
newmarks = []
argval = parameterset
while isinstance(argval, MarkDecorator):
newmarks.append(MarkDecorator(Mark(
argval.markname, argval.args[:-1], argval.kwargs)))
argval = argval.args[-1]
assert not isinstance(argval, ParameterSet)
if legacy_force_tuple:
argval = argval,
return cls(argval, marks=newmarks, id=None)
@property
def deprecated_arg_dict(self):
return dict((mark.name, mark) for mark in self.marks)
class MarkerError(Exception): class MarkerError(Exception):
@ -7,8 +66,8 @@ class MarkerError(Exception):
"""Error in use of a pytest marker/attribute.""" """Error in use of a pytest marker/attribute."""
def pytest_namespace(): def param(*values, **kw):
return {'mark': MarkGenerator()} return ParameterSet.param(*values, **kw)
def pytest_addoption(parser): def pytest_addoption(parser):
@ -162,9 +221,13 @@ def matchkeyword(colitem, keywordexpr):
def pytest_configure(config): def pytest_configure(config):
import pytest config._old_mark_config = MARK_GEN._config
if config.option.strict: if config.option.strict:
pytest.mark._config = config MARK_GEN._config = config
def pytest_unconfigure(config):
MARK_GEN._config = getattr(config, '_old_mark_config', None)
class MarkGenerator: class MarkGenerator:
@ -178,13 +241,15 @@ class MarkGenerator:
will set a 'slowtest' :class:`MarkInfo` object will set a 'slowtest' :class:`MarkInfo` object
on the ``test_function`` object. """ on the ``test_function`` object. """
_config = None
def __getattr__(self, name): def __getattr__(self, name):
if name[0] == "_": if name[0] == "_":
raise AttributeError("Marker name must NOT start with underscore") raise AttributeError("Marker name must NOT start with underscore")
if hasattr(self, '_config'): if self._config is not None:
self._check(name) self._check(name)
return MarkDecorator(name) return MarkDecorator(Mark(name, (), {}))
def _check(self, name): def _check(self, name):
try: try:
@ -200,6 +265,7 @@ class MarkGenerator:
if name not in self._markers: if name not in self._markers:
raise AttributeError("%r not a registered marker" % (name,)) raise AttributeError("%r not a registered marker" % (name,))
def istestfunc(func): def istestfunc(func):
return hasattr(func, "__call__") and \ return hasattr(func, "__call__") and \
getattr(func, "__name__", "<lambda>") != "<lambda>" getattr(func, "__name__", "<lambda>") != "<lambda>"
@ -237,19 +303,23 @@ class MarkDecorator:
additional keyword or positional arguments. additional keyword or positional arguments.
""" """
def __init__(self, name, args=None, kwargs=None): def __init__(self, mark):
self.name = name assert isinstance(mark, Mark), repr(mark)
self.args = args or () self.mark = mark
self.kwargs = kwargs or {}
name = alias('mark.name')
args = alias('mark.args')
kwargs = alias('mark.kwargs')
@property @property
def markname(self): def markname(self):
return self.name # for backward-compat (2.4.1 had this attr) return self.name # for backward-compat (2.4.1 had this attr)
def __eq__(self, other):
return self.mark == other.mark
def __repr__(self): def __repr__(self):
d = self.__dict__.copy() return "<MarkDecorator %r>" % (self.mark,)
name = d.pop('name')
return "<MarkDecorator %r %r>" % (name, d)
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
""" if passed a single callable argument: decorate it with mark info. """ if passed a single callable argument: decorate it with mark info.
@ -272,57 +342,50 @@ class MarkDecorator:
else: else:
holder = getattr(func, self.name, None) holder = getattr(func, self.name, None)
if holder is None: if holder is None:
holder = MarkInfo( holder = MarkInfo(self.mark)
self.name, self.args, self.kwargs
)
setattr(func, self.name, holder) setattr(func, self.name, holder)
else: else:
holder.add(self.args, self.kwargs) holder.add_mark(self.mark)
return func return func
kw = self.kwargs.copy()
kw.update(kwargs) mark = Mark(self.name, args, kwargs)
args = self.args + args return self.__class__(self.mark.combined_with(mark))
return self.__class__(self.name, args=args, kwargs=kw)
def extract_argvalue(maybe_marked_args):
# TODO: incorrect mark data, the old code wanst able to collect lists
# individual parametrized argument sets can be wrapped in a series
# of markers in which case we unwrap the values and apply the mark
# at Function init
newmarks = {}
argval = maybe_marked_args
while isinstance(argval, MarkDecorator):
newmark = MarkDecorator(argval.markname,
argval.args[:-1], argval.kwargs)
newmarks[newmark.markname] = newmark
argval = argval.args[-1]
return argval, newmarks
class MarkInfo:
class Mark(namedtuple('Mark', 'name, args, kwargs')):
def combined_with(self, other):
assert self.name == other.name
return Mark(
self.name, self.args + other.args,
dict(self.kwargs, **other.kwargs))
class MarkInfo(object):
""" Marking object created by :class:`MarkDecorator` instances. """ """ Marking object created by :class:`MarkDecorator` instances. """
def __init__(self, name, args, kwargs): def __init__(self, mark):
#: name of attribute assert isinstance(mark, Mark), repr(mark)
self.name = name self.combined = mark
#: positional argument list, empty if none specified self._marks = [mark]
self.args = args
#: keyword argument dictionary, empty if nothing specified name = alias('combined.name')
self.kwargs = kwargs.copy() args = alias('combined.args')
self._arglist = [(args, kwargs.copy())] kwargs = alias('combined.kwargs')
def __repr__(self): def __repr__(self):
return "<MarkInfo %r args=%r kwargs=%r>" % ( return "<MarkInfo {0!r}>".format(self.combined)
self.name, self.args, self.kwargs
)
def add(self, args, kwargs): def add_mark(self, mark):
""" add a MarkInfo with the given args and kwargs. """ """ add a MarkInfo with the given args and kwargs. """
self._arglist.append((args, kwargs)) self._marks.append(mark)
self.args += args self.combined = self.combined.combined_with(mark)
self.kwargs.update(kwargs)
def __iter__(self): def __iter__(self):
""" yield MarkInfo objects each relating to a marking-call. """ """ yield MarkInfo objects each relating to a marking-call. """
for args, kwargs in self._arglist: return imap(MarkInfo, self._marks)
yield MarkInfo(self.name, args, kwargs)
MARK_GEN = MarkGenerator()

View File

@ -1,16 +1,17 @@
""" monkeypatching and mocking functionality. """ """ monkeypatching and mocking functionality. """
from __future__ import absolute_import, division, print_function
import os, sys import os
import sys
import re import re
from py.builtin import _basestring from py.builtin import _basestring
from _pytest.fixtures import fixture
import pytest
RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$") RE_IMPORT_ERROR_NAME = re.compile("^No module named (.*)$")
@pytest.fixture @fixture
def monkeypatch(): def monkeypatch():
"""The returned ``monkeypatch`` fixture provides these """The returned ``monkeypatch`` fixture provides these
helper methods to modify objects, dictionaries or os.environ:: helper methods to modify objects, dictionaries or os.environ::

View File

@ -1,10 +1,11 @@
""" run test suites written for nose. """ """ run test suites written for nose. """
from __future__ import absolute_import, division, print_function
import sys import sys
import py import py
import pytest from _pytest import unittest, runner, python
from _pytest import unittest from _pytest.config import hookimpl
def get_skip_exceptions(): def get_skip_exceptions():
@ -19,19 +20,19 @@ def get_skip_exceptions():
def pytest_runtest_makereport(item, call): def pytest_runtest_makereport(item, call):
if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()): if call.excinfo and call.excinfo.errisinstance(get_skip_exceptions()):
# let's substitute the excinfo with a pytest.skip one # let's substitute the excinfo with a pytest.skip one
call2 = call.__class__(lambda: call2 = call.__class__(
pytest.skip(str(call.excinfo.value)), call.when) lambda: runner.skip(str(call.excinfo.value)), call.when)
call.excinfo = call2.excinfo call.excinfo = call2.excinfo
@pytest.hookimpl(trylast=True) @hookimpl(trylast=True)
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
if is_potential_nosetest(item): if is_potential_nosetest(item):
if isinstance(item.parent, pytest.Generator): if isinstance(item.parent, python.Generator):
gen = item.parent gen = item.parent
if not hasattr(gen, '_nosegensetup'): if not hasattr(gen, '_nosegensetup'):
call_optional(gen.obj, 'setup') call_optional(gen.obj, 'setup')
if isinstance(gen.parent, pytest.Instance): if isinstance(gen.parent, python.Instance):
call_optional(gen.parent.obj, 'setup') call_optional(gen.parent.obj, 'setup')
gen._nosegensetup = True gen._nosegensetup = True
if not call_optional(item.obj, 'setup'): if not call_optional(item.obj, 'setup'):
@ -50,14 +51,14 @@ def teardown_nose(item):
def pytest_make_collect_report(collector): def pytest_make_collect_report(collector):
if isinstance(collector, pytest.Generator): if isinstance(collector, python.Generator):
call_optional(collector.obj, 'setup') call_optional(collector.obj, 'setup')
def is_potential_nosetest(item): def is_potential_nosetest(item):
# extra check needed since we do not do nose style setup/teardown # extra check needed since we do not do nose style setup/teardown
# on direct unittest style classes # on direct unittest style classes
return isinstance(item, pytest.Function) and \ return isinstance(item, python.Function) and \
not isinstance(item, unittest.TestCaseFunction) not isinstance(item, unittest.TestCaseFunction)

View File

@ -1,4 +1,6 @@
""" submit failure or test session information to a pastebin service. """ """ submit failure or test session information to a pastebin service. """
from __future__ import absolute_import, division, print_function
import pytest import pytest
import sys import sys
import tempfile import tempfile

View File

@ -1,4 +1,6 @@
""" (disabled by default) support for testing pytest and pytest plugins. """ """ (disabled by default) support for testing pytest and pytest plugins. """
from __future__ import absolute_import, division, print_function
import codecs import codecs
import gc import gc
import os import os
@ -10,8 +12,9 @@ import time
import traceback import traceback
from fnmatch import fnmatch from fnmatch import fnmatch
from py.builtin import print_ from weakref import WeakKeyDictionary
from _pytest.capture import MultiCapture, SysCapture
from _pytest._code import Source from _pytest._code import Source
import py import py
import pytest import pytest
@ -85,7 +88,7 @@ class LsofFdLeakChecker(object):
return True return True
@pytest.hookimpl(hookwrapper=True, tryfirst=True) @pytest.hookimpl(hookwrapper=True, tryfirst=True)
def pytest_runtest_item(self, item): def pytest_runtest_protocol(self, item):
lines1 = self.get_open_files() lines1 = self.get_open_files()
yield yield
if hasattr(sys, "pypy_version_info"): if hasattr(sys, "pypy_version_info"):
@ -104,7 +107,8 @@ class LsofFdLeakChecker(object):
error.extend([str(f) for f in lines2]) error.extend([str(f) for f in lines2])
error.append(error[0]) error.append(error[0])
error.append("*** function %s:%s: %s " % item.location) error.append("*** function %s:%s: %s " % item.location)
pytest.fail("\n".join(error), pytrace=False) error.append("See issue #2366")
item.warn('', "\n".join(error))
# XXX copied from execnet's conftest.py - needs to be merged # XXX copied from execnet's conftest.py - needs to be merged
@ -226,15 +230,15 @@ class HookRecorder:
name, check = entries.pop(0) name, check = entries.pop(0)
for ind, call in enumerate(self.calls[i:]): for ind, call in enumerate(self.calls[i:]):
if call._name == name: if call._name == name:
print_("NAMEMATCH", name, call) print("NAMEMATCH", name, call)
if eval(check, backlocals, call.__dict__): if eval(check, backlocals, call.__dict__):
print_("CHECKERMATCH", repr(check), "->", call) print("CHECKERMATCH", repr(check), "->", call)
else: else:
print_("NOCHECKERMATCH", repr(check), "-", call) print("NOCHECKERMATCH", repr(check), "-", call)
continue continue
i += ind + 1 i += ind + 1
break break
print_("NONAMEMATCH", name, "with", call) print("NONAMEMATCH", name, "with", call)
else: else:
pytest.fail("could not find %r check %r" % (name, check)) pytest.fail("could not find %r check %r" % (name, check))
@ -402,6 +406,7 @@ class Testdir:
def __init__(self, request, tmpdir_factory): def __init__(self, request, tmpdir_factory):
self.request = request self.request = request
self._mod_collections = WeakKeyDictionary()
# XXX remove duplication with tmpdir plugin # XXX remove duplication with tmpdir plugin
basetmp = tmpdir_factory.ensuretemp("testdir") basetmp = tmpdir_factory.ensuretemp("testdir")
name = request.function.__name__ name = request.function.__name__
@ -470,7 +475,7 @@ class Testdir:
if not hasattr(self, '_olddir'): if not hasattr(self, '_olddir'):
self._olddir = old self._olddir = old
def _makefile(self, ext, args, kwargs): def _makefile(self, ext, args, kwargs, encoding="utf-8"):
items = list(kwargs.items()) items = list(kwargs.items())
if args: if args:
source = py.builtin._totext("\n").join( source = py.builtin._totext("\n").join(
@ -490,7 +495,7 @@ class Testdir:
source_unicode = "\n".join([my_totext(line) for line in source.lines]) source_unicode = "\n".join([my_totext(line) for line in source.lines])
source = py.builtin._totext(source_unicode) source = py.builtin._totext(source_unicode)
content = source.strip().encode("utf-8") # + "\n" content = source.strip().encode(encoding) # + "\n"
#content = content.rstrip() + "\n" #content = content.rstrip() + "\n"
p.write(content, "wb") p.write(content, "wb")
if ret is None: if ret is None:
@ -735,7 +740,8 @@ class Testdir:
if kwargs.get("syspathinsert"): if kwargs.get("syspathinsert"):
self.syspathinsert() self.syspathinsert()
now = time.time() now = time.time()
capture = py.io.StdCapture() capture = MultiCapture(Capture=SysCapture)
capture.start_capturing()
try: try:
try: try:
reprec = self.inline_run(*args, **kwargs) reprec = self.inline_run(*args, **kwargs)
@ -750,7 +756,8 @@ class Testdir:
class reprec: class reprec:
ret = 3 ret = 3
finally: finally:
out, err = capture.reset() out, err = capture.readouterr()
capture.stop_capturing()
sys.stdout.write(out) sys.stdout.write(out)
sys.stderr.write(err) sys.stderr.write(err)
@ -867,6 +874,7 @@ class Testdir:
self.makepyfile(__init__ = "#") self.makepyfile(__init__ = "#")
self.config = config = self.parseconfigure(path, *configargs) self.config = config = self.parseconfigure(path, *configargs)
node = self.getnode(config, path) node = self.getnode(config, path)
return node return node
def collect_by_name(self, modcol, name): def collect_by_name(self, modcol, name):
@ -881,7 +889,9 @@ class Testdir:
:param name: The name of the node to return. :param name: The name of the node to return.
""" """
for colitem in modcol._memocollect(): if modcol not in self._mod_collections:
self._mod_collections[modcol] = list(modcol.collect())
for colitem in self._mod_collections[modcol]:
if colitem.name == name: if colitem.name == name:
return colitem return colitem
@ -916,8 +926,8 @@ class Testdir:
cmdargs = [str(x) for x in cmdargs] cmdargs = [str(x) for x in cmdargs]
p1 = self.tmpdir.join("stdout") p1 = self.tmpdir.join("stdout")
p2 = self.tmpdir.join("stderr") p2 = self.tmpdir.join("stderr")
print_("running:", ' '.join(cmdargs)) print("running:", ' '.join(cmdargs))
print_(" in:", str(py.path.local())) print(" in:", str(py.path.local()))
f1 = codecs.open(str(p1), "w", encoding="utf8") f1 = codecs.open(str(p1), "w", encoding="utf8")
f2 = codecs.open(str(p2), "w", encoding="utf8") f2 = codecs.open(str(p2), "w", encoding="utf8")
try: try:
@ -943,7 +953,7 @@ class Testdir:
def _dump_lines(self, lines, fp): def _dump_lines(self, lines, fp):
try: try:
for line in lines: for line in lines:
py.builtin.print_(line, file=fp) print(line, file=fp)
except UnicodeEncodeError: except UnicodeEncodeError:
print("couldn't print to %s because of encoding" % (fp,)) print("couldn't print to %s because of encoding" % (fp,))
@ -1000,7 +1010,7 @@ class Testdir:
The pexpect child is returned. The pexpect child is returned.
""" """
basetemp = self.tmpdir.mkdir("pexpect") basetemp = self.tmpdir.mkdir("temp-pexpect")
invoke = " ".join(map(str, self._getpytestargs())) invoke = " ".join(map(str, self._getpytestargs()))
cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string) cmd = "%s --basetemp=%s %s" % (invoke, basetemp, string)
return self.spawn(cmd, expect_timeout=expect_timeout) return self.spawn(cmd, expect_timeout=expect_timeout)

View File

@ -1,4 +1,5 @@
""" Python test discovery, setup and run of test functions. """ """ Python test discovery, setup and run of test functions. """
from __future__ import absolute_import, division, print_function
import fnmatch import fnmatch
import inspect import inspect
@ -9,19 +10,20 @@ import math
from itertools import count from itertools import count
import py import py
import pytest
from _pytest.mark import MarkerError from _pytest.mark import MarkerError
from _pytest.config import hookimpl
import _pytest import _pytest
import _pytest._pluggy as pluggy import _pytest._pluggy as pluggy
from _pytest import fixtures from _pytest import fixtures
from _pytest import main
from _pytest.compat import ( from _pytest.compat import (
isclass, isfunction, is_generator, _escape_strings, isclass, isfunction, is_generator, _escape_strings,
REGEX_TYPE, STRING_TYPES, NoneType, NOTSET, REGEX_TYPE, STRING_TYPES, NoneType, NOTSET,
get_real_func, getfslineno, safe_getattr, get_real_func, getfslineno, safe_getattr,
safe_str, getlocation, enum, safe_str, getlocation, enum,
) )
from _pytest.runner import fail
cutdir1 = py.path.local(pluggy.__file__.rstrip("oc")) cutdir1 = py.path.local(pluggy.__file__.rstrip("oc"))
cutdir2 = py.path.local(_pytest.__file__).dirpath() cutdir2 = py.path.local(_pytest.__file__).dirpath()
@ -49,7 +51,7 @@ def filter_traceback(entry):
def pyobj_property(name): def pyobj_property(name):
def get(self): def get(self):
node = self.getparent(getattr(pytest, name)) node = self.getparent(getattr(__import__('pytest'), name))
if node is not None: if node is not None:
return node.obj return node.obj
doc = "python %s object this node was collected from (can be None)." % ( doc = "python %s object this node was collected from (can be None)." % (
@ -126,23 +128,8 @@ def pytest_configure(config):
"all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures " "all of the specified fixtures. see http://pytest.org/latest/fixture.html#usefixtures "
) )
@pytest.hookimpl(trylast=True)
def pytest_namespace():
raises.Exception = pytest.fail.Exception
return {
'raises': raises,
'approx': approx,
'collect': {
'Module': Module,
'Class': Class,
'Instance': Instance,
'Function': Function,
'Generator': Generator,
}
}
@hookimpl(trylast=True)
@pytest.hookimpl(trylast=True)
def pytest_pyfunc_call(pyfuncitem): def pytest_pyfunc_call(pyfuncitem):
testfunction = pyfuncitem.obj testfunction = pyfuncitem.obj
if pyfuncitem._isyieldedfunction(): if pyfuncitem._isyieldedfunction():
@ -155,6 +142,7 @@ def pytest_pyfunc_call(pyfuncitem):
testfunction(**testargs) testfunction(**testargs)
return True return True
def pytest_collect_file(path, parent): def pytest_collect_file(path, parent):
ext = path.ext ext = path.ext
if ext == ".py": if ext == ".py":
@ -170,7 +158,7 @@ def pytest_collect_file(path, parent):
def pytest_pycollect_makemodule(path, parent): def pytest_pycollect_makemodule(path, parent):
return Module(path, parent) return Module(path, parent)
@pytest.hookimpl(hookwrapper=True) @hookimpl(hookwrapper=True)
def pytest_pycollect_makeitem(collector, name, obj): def pytest_pycollect_makeitem(collector, name, obj):
outcome = yield outcome = yield
res = outcome.get_result() res = outcome.get_result()
@ -198,7 +186,7 @@ def pytest_pycollect_makeitem(collector, name, obj):
res = list(collector._genfunctions(name, obj)) res = list(collector._genfunctions(name, obj))
outcome.force_result(res) outcome.force_result(res)
def pytest_make_parametrize_id(config, val): def pytest_make_parametrize_id(config, val, argname=None):
return None return None
@ -265,7 +253,7 @@ class PyobjMixin(PyobjContext):
assert isinstance(lineno, int) assert isinstance(lineno, int)
return fspath, lineno, modpath return fspath, lineno, modpath
class PyCollector(PyobjMixin, pytest.Collector): class PyCollector(PyobjMixin, main.Collector):
def funcnamefilter(self, name): def funcnamefilter(self, name):
return self._matches_prefix_or_glob_option('python_functions', name) return self._matches_prefix_or_glob_option('python_functions', name)
@ -402,10 +390,12 @@ def transfer_markers(funcobj, cls, mod):
if not _marked(funcobj, pytestmark): if not _marked(funcobj, pytestmark):
pytestmark(funcobj) pytestmark(funcobj)
class Module(pytest.File, PyCollector):
class Module(main.File, PyCollector):
""" Collector for test classes and functions. """ """ Collector for test classes and functions. """
def _getobj(self): def _getobj(self):
return self._memoizedcall('_obj', self._importtestmodule) return self._importtestmodule()
def collect(self): def collect(self):
self.session._fixturemanager.parsefactories(self) self.session._fixturemanager.parsefactories(self)
@ -502,6 +492,8 @@ def _get_xunit_func(obj, name):
class Class(PyCollector): class Class(PyCollector):
""" Collector for test methods. """ """ Collector for test methods. """
def collect(self): def collect(self):
if not safe_getattr(self.obj, "__test__", True):
return []
if hasinit(self.obj): if hasinit(self.obj):
self.warn("C1", "cannot collect test class %r because it has a " self.warn("C1", "cannot collect test class %r because it has a "
"__init__ constructor" % self.obj.__name__) "__init__ constructor" % self.obj.__name__)
@ -586,7 +578,7 @@ class FunctionMixin(PyobjMixin):
entry.set_repr_style('short') entry.set_repr_style('short')
def _repr_failure_py(self, excinfo, style="long"): def _repr_failure_py(self, excinfo, style="long"):
if excinfo.errisinstance(pytest.fail.Exception): if excinfo.errisinstance(fail.Exception):
if not excinfo.value.pytrace: if not excinfo.value.pytrace:
return py._builtin._totext(excinfo.value) return py._builtin._totext(excinfo.value)
return super(FunctionMixin, self)._repr_failure_py(excinfo, return super(FunctionMixin, self)._repr_failure_py(excinfo,
@ -784,36 +776,34 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
to set a dynamic scope using test context or configuration. to set a dynamic scope using test context or configuration.
""" """
from _pytest.fixtures import scope2index from _pytest.fixtures import scope2index
from _pytest.mark import extract_argvalue from _pytest.mark import MARK_GEN, ParameterSet
from py.io import saferepr from py.io import saferepr
unwrapped_argvalues = []
newkeywords = []
for maybe_marked_args in argvalues:
argval, newmarks = extract_argvalue(maybe_marked_args)
unwrapped_argvalues.append(argval)
newkeywords.append(newmarks)
argvalues = unwrapped_argvalues
if not isinstance(argnames, (tuple, list)): if not isinstance(argnames, (tuple, list)):
argnames = [x.strip() for x in argnames.split(",") if x.strip()] argnames = [x.strip() for x in argnames.split(",") if x.strip()]
if len(argnames) == 1: force_tuple = len(argnames) == 1
argvalues = [(val,) for val in argvalues] else:
if not argvalues: force_tuple = False
argvalues = [(NOTSET,) * len(argnames)] parameters = [
# we passed a empty list to parameterize, skip that test ParameterSet.extract_from(x, legacy_force_tuple=force_tuple)
# for x in argvalues]
del argvalues
if not parameters:
fs, lineno = getfslineno(self.function) fs, lineno = getfslineno(self.function)
newmark = pytest.mark.skip( reason = "got empty parameter set %r, function %s at %s:%d" % (
reason="got empty parameter set %r, function %s at %s:%d" % ( argnames, self.function.__name__, fs, lineno)
argnames, self.function.__name__, fs, lineno)) mark = MARK_GEN.skip(reason=reason)
newkeywords = [{newmark.markname: newmark}] parameters.append(ParameterSet(
values=(NOTSET,) * len(argnames),
marks=[mark],
id=None,
))
if scope is None: if scope is None:
scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect) scope = _find_parametrized_scope(argnames, self._arg2fixturedefs, indirect)
scopenum = scope2index( scopenum = scope2index(scope, descr='call to {0}'.format(self.parametrize))
scope, descr='call to {0}'.format(self.parametrize))
valtypes = {} valtypes = {}
for arg in argnames: for arg in argnames:
if arg not in self.fixturenames: if arg not in self.fixturenames:
@ -841,26 +831,26 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
idfn = ids idfn = ids
ids = None ids = None
if ids: if ids:
if len(ids) != len(argvalues): if len(ids) != len(parameters):
raise ValueError('%d tests specified with %d ids' %( raise ValueError('%d tests specified with %d ids' % (
len(argvalues), len(ids))) len(parameters), len(ids)))
for id_value in ids: for id_value in ids:
if id_value is not None and not isinstance(id_value, py.builtin._basestring): if id_value is not None and not isinstance(id_value, py.builtin._basestring):
msg = 'ids must be list of strings, found: %s (type: %s)' msg = 'ids must be list of strings, found: %s (type: %s)'
raise ValueError(msg % (saferepr(id_value), type(id_value).__name__)) raise ValueError(msg % (saferepr(id_value), type(id_value).__name__))
ids = idmaker(argnames, argvalues, idfn, ids, self.config) ids = idmaker(argnames, parameters, idfn, ids, self.config)
newcalls = [] newcalls = []
for callspec in self._calls or [CallSpec2(self)]: for callspec in self._calls or [CallSpec2(self)]:
elements = zip(ids, argvalues, newkeywords, count()) elements = zip(ids, parameters, count())
for a_id, valset, keywords, param_index in elements: for a_id, param, param_index in elements:
if len(valset) != len(argnames): if len(param.values) != len(argnames):
raise ValueError( raise ValueError(
'In "parametrize" the number of values ({0}) must be ' 'In "parametrize" the number of values ({0}) must be '
'equal to the number of names ({1})'.format( 'equal to the number of names ({1})'.format(
valset, argnames)) param.values, argnames))
newcallspec = callspec.copy(self) newcallspec = callspec.copy(self)
newcallspec.setmulti(valtypes, argnames, valset, a_id, newcallspec.setmulti(valtypes, argnames, param.values, a_id,
keywords, scopenum, param_index) param.deprecated_arg_dict, scopenum, param_index)
newcalls.append(newcallspec) newcalls.append(newcallspec)
self._calls = newcalls self._calls = newcalls
@ -884,7 +874,7 @@ class Metafunc(fixtures.FuncargnamesCompatAttr):
if funcargs is not None: if funcargs is not None:
for name in funcargs: for name in funcargs:
if name not in self.fixturenames: if name not in self.fixturenames:
pytest.fail("funcarg %r not used in this function." % name) fail("funcarg %r not used in this function." % name)
else: else:
funcargs = {} funcargs = {}
if id is None: if id is None:
@ -929,15 +919,21 @@ def _find_parametrized_scope(argnames, arg2fixturedefs, indirect):
def _idval(val, argname, idx, idfn, config=None): def _idval(val, argname, idx, idfn, config=None):
if idfn: if idfn:
s = None
try: try:
s = idfn(val) s = idfn(val)
if s:
return _escape_strings(s)
except Exception: except Exception:
pass # See issue https://github.com/pytest-dev/pytest/issues/2169
import warnings
msg = "Raised while trying to determine id of parameter %s at position %d." % (argname, idx)
msg += '\nUpdate your code as this will raise an error in pytest-4.0.'
warnings.warn(msg, DeprecationWarning)
if s:
return _escape_strings(s)
if config: if config:
hook_id = config.hook.pytest_make_parametrize_id(config=config, val=val) hook_id = config.hook.pytest_make_parametrize_id(
config=config, val=val, argname=argname)
if hook_id: if hook_id:
return hook_id return hook_id
@ -953,17 +949,21 @@ def _idval(val, argname, idx, idfn, config=None):
return val.__name__ return val.__name__
return str(argname)+str(idx) return str(argname)+str(idx)
def _idvalset(idx, valset, argnames, idfn, ids, config=None):
def _idvalset(idx, parameterset, argnames, idfn, ids, config=None):
if parameterset.id is not None:
return parameterset.id
if ids is None or (idx >= len(ids) or ids[idx] is None): if ids is None or (idx >= len(ids) or ids[idx] is None):
this_id = [_idval(val, argname, idx, idfn, config) this_id = [_idval(val, argname, idx, idfn, config)
for val, argname in zip(valset, argnames)] for val, argname in zip(parameterset.values, argnames)]
return "-".join(this_id) return "-".join(this_id)
else: else:
return _escape_strings(ids[idx]) return _escape_strings(ids[idx])
def idmaker(argnames, argvalues, idfn=None, ids=None, config=None):
ids = [_idvalset(valindex, valset, argnames, idfn, ids, config) def idmaker(argnames, parametersets, idfn=None, ids=None, config=None):
for valindex, valset in enumerate(argvalues)] ids = [_idvalset(valindex, parameterset, argnames, idfn, ids, config)
for valindex, parameterset in enumerate(parametersets)]
if len(set(ids)) != len(ids): if len(set(ids)) != len(ids):
# The ids are not unique # The ids are not unique
duplicates = [testid for testid in ids if ids.count(testid) > 1] duplicates = [testid for testid in ids if ids.count(testid) > 1]
@ -1039,6 +1039,7 @@ def showfixtures(config):
from _pytest.main import wrap_session from _pytest.main import wrap_session
return wrap_session(config, _showfixtures_main) return wrap_session(config, _showfixtures_main)
def _showfixtures_main(config, session): def _showfixtures_main(config, session):
import _pytest.config import _pytest.config
session.perform_collect() session.perform_collect()
@ -1129,7 +1130,7 @@ def raises(expected_exception, *args, **kwargs):
>>> with raises(ValueError) as exc_info: >>> with raises(ValueError) as exc_info:
... if value > 10: ... if value > 10:
... raise ValueError("value must be <= 10") ... raise ValueError("value must be <= 10")
... assert str(exc_info.value) == "value must be <= 10" # this will not execute ... assert exc_info.type == ValueError # this will not execute
Instead, the following approach must be taken (note the difference in Instead, the following approach must be taken (note the difference in
scope):: scope)::
@ -1138,7 +1139,16 @@ def raises(expected_exception, *args, **kwargs):
... if value > 10: ... if value > 10:
... raise ValueError("value must be <= 10") ... raise ValueError("value must be <= 10")
... ...
>>> assert str(exc_info.value) == "value must be <= 10" >>> assert exc_info.type == ValueError
Or you can use the keyword argument ``match`` to assert that the
exception matches a text or regex::
>>> with raises(ValueError, match='must be 0 or None'):
... raise ValueError("value must be 0 or None")
>>> with raises(ValueError, match=r'must be \d+$'):
... raise ValueError("value must be 42")
Or you can specify a callable by passing a to-be-called lambda:: Or you can specify a callable by passing a to-be-called lambda::
@ -1179,12 +1189,6 @@ def raises(expected_exception, *args, **kwargs):
""" """
__tracebackhide__ = True __tracebackhide__ = True
if expected_exception is AssertionError:
# we want to catch a AssertionError
# replace our subclass with the builtin one
# see https://github.com/pytest-dev/pytest/issues/176
from _pytest.assertion.util import BuiltinAssertionError \
as expected_exception
msg = ("exceptions must be old-style classes or" msg = ("exceptions must be old-style classes or"
" derived from BaseException, not %s") " derived from BaseException, not %s")
if isinstance(expected_exception, tuple): if isinstance(expected_exception, tuple):
@ -1195,11 +1199,15 @@ def raises(expected_exception, *args, **kwargs):
raise TypeError(msg % type(expected_exception)) raise TypeError(msg % type(expected_exception))
message = "DID NOT RAISE {0}".format(expected_exception) message = "DID NOT RAISE {0}".format(expected_exception)
match_expr = None
if not args: if not args:
if "message" in kwargs: if "message" in kwargs:
message = kwargs.pop("message") message = kwargs.pop("message")
return RaisesContext(expected_exception, message) if "match" in kwargs:
match_expr = kwargs.pop("match")
message += " matching '{0}'".format(match_expr)
return RaisesContext(expected_exception, message, match_expr)
elif isinstance(args[0], str): elif isinstance(args[0], str):
code, = args code, = args
assert isinstance(code, str) assert isinstance(code, str)
@ -1220,12 +1228,17 @@ def raises(expected_exception, *args, **kwargs):
func(*args[1:], **kwargs) func(*args[1:], **kwargs)
except expected_exception: except expected_exception:
return _pytest._code.ExceptionInfo() return _pytest._code.ExceptionInfo()
pytest.fail(message) fail(message)
raises.Exception = fail.Exception
class RaisesContext(object): class RaisesContext(object):
def __init__(self, expected_exception, message): def __init__(self, expected_exception, message, match_expr):
self.expected_exception = expected_exception self.expected_exception = expected_exception
self.message = message self.message = message
self.match_expr = match_expr
self.excinfo = None self.excinfo = None
def __enter__(self): def __enter__(self):
@ -1235,7 +1248,7 @@ class RaisesContext(object):
def __exit__(self, *tp): def __exit__(self, *tp):
__tracebackhide__ = True __tracebackhide__ = True
if tp[0] is None: if tp[0] is None:
pytest.fail(self.message) fail(self.message)
if sys.version_info < (2, 7): if sys.version_info < (2, 7):
# py26: on __exit__() exc_value often does not contain the # py26: on __exit__() exc_value often does not contain the
# exception value. # exception value.
@ -1247,6 +1260,8 @@ class RaisesContext(object):
suppress_exception = issubclass(self.excinfo.type, self.expected_exception) suppress_exception = issubclass(self.excinfo.type, self.expected_exception)
if sys.version_info[0] == 2 and suppress_exception: if sys.version_info[0] == 2 and suppress_exception:
sys.exc_clear() sys.exc_clear()
if self.match_expr:
self.excinfo.match(self.match_expr)
return suppress_exception return suppress_exception
@ -1504,7 +1519,7 @@ class ApproxNonIterable(object):
# the basic pytest Function item # the basic pytest Function item
# #
class Function(FunctionMixin, pytest.Item, fixtures.FuncargnamesCompatAttr): class Function(FunctionMixin, main.Item, fixtures.FuncargnamesCompatAttr):
""" a Function Item is responsible for setting up and executing a """ a Function Item is responsible for setting up and executing a
Python test function. Python test function.
""" """

View File

@ -1,4 +1,5 @@
""" recording warnings during test function execution. """ """ recording warnings during test function execution. """
from __future__ import absolute_import, division, print_function
import inspect import inspect
@ -6,11 +7,11 @@ import _pytest._code
import py import py
import sys import sys
import warnings import warnings
import pytest from _pytest.fixtures import yield_fixture
@pytest.yield_fixture @yield_fixture
def recwarn(request): def recwarn():
"""Return a WarningsRecorder instance that provides these methods: """Return a WarningsRecorder instance that provides these methods:
* ``pop(category=None)``: return last warning matching the category. * ``pop(category=None)``: return last warning matching the category.
@ -25,11 +26,6 @@ def recwarn(request):
yield wrec yield wrec
def pytest_namespace():
return {'deprecated_call': deprecated_call,
'warns': warns}
def deprecated_call(func=None, *args, **kwargs): def deprecated_call(func=None, *args, **kwargs):
""" assert that calling ``func(*args, **kwargs)`` triggers a """ assert that calling ``func(*args, **kwargs)`` triggers a
``DeprecationWarning`` or ``PendingDeprecationWarning``. ``DeprecationWarning`` or ``PendingDeprecationWarning``.
@ -55,14 +51,12 @@ def deprecated_call(func=None, *args, **kwargs):
def warn_explicit(message, category, *args, **kwargs): def warn_explicit(message, category, *args, **kwargs):
categories.append(category) categories.append(category)
old_warn_explicit(message, category, *args, **kwargs)
def warn(message, category=None, *args, **kwargs): def warn(message, category=None, *args, **kwargs):
if isinstance(message, Warning): if isinstance(message, Warning):
categories.append(message.__class__) categories.append(message.__class__)
else: else:
categories.append(category) categories.append(category)
old_warn(message, category, *args, **kwargs)
old_warn = warnings.warn old_warn = warnings.warn
old_warn_explicit = warnings.warn_explicit old_warn_explicit = warnings.warn_explicit
@ -115,24 +109,14 @@ def warns(expected_warning, *args, **kwargs):
return func(*args[1:], **kwargs) return func(*args[1:], **kwargs)
class RecordedWarning(object): class WarningsRecorder(warnings.catch_warnings):
def __init__(self, message, category, filename, lineno, file, line):
self.message = message
self.category = category
self.filename = filename
self.lineno = lineno
self.file = file
self.line = line
class WarningsRecorder(object):
"""A context manager to record raised warnings. """A context manager to record raised warnings.
Adapted from `warnings.catch_warnings`. Adapted from `warnings.catch_warnings`.
""" """
def __init__(self, module=None): def __init__(self):
self._module = sys.modules['warnings'] if module is None else module super(WarningsRecorder, self).__init__(record=True)
self._entered = False self._entered = False
self._list = [] self._list = []
@ -169,38 +153,20 @@ class WarningsRecorder(object):
if self._entered: if self._entered:
__tracebackhide__ = True __tracebackhide__ = True
raise RuntimeError("Cannot enter %r twice" % self) raise RuntimeError("Cannot enter %r twice" % self)
self._entered = True self._list = super(WarningsRecorder, self).__enter__()
self._filters = self._module.filters warnings.simplefilter('always')
self._module.filters = self._filters[:]
self._showwarning = self._module.showwarning
def showwarning(message, category, filename, lineno,
file=None, line=None):
self._list.append(RecordedWarning(
message, category, filename, lineno, file, line))
# still perform old showwarning functionality
self._showwarning(
message, category, filename, lineno, file=file, line=line)
self._module.showwarning = showwarning
# allow the same warning to be raised more than once
self._module.simplefilter('always')
return self return self
def __exit__(self, *exc_info): def __exit__(self, *exc_info):
if not self._entered: if not self._entered:
__tracebackhide__ = True __tracebackhide__ = True
raise RuntimeError("Cannot exit %r without entering first" % self) raise RuntimeError("Cannot exit %r without entering first" % self)
self._module.filters = self._filters super(WarningsRecorder, self).__exit__(*exc_info)
self._module.showwarning = self._showwarning
class WarningsChecker(WarningsRecorder): class WarningsChecker(WarningsRecorder):
def __init__(self, expected_warning=None, module=None): def __init__(self, expected_warning=None):
super(WarningsChecker, self).__init__(module=module) super(WarningsChecker, self).__init__()
msg = ("exceptions must be old-style classes or " msg = ("exceptions must be old-style classes or "
"derived from Warning, not %s") "derived from Warning, not %s")
@ -221,9 +187,11 @@ class WarningsChecker(WarningsRecorder):
# only check if we're not currently handling an exception # only check if we're not currently handling an exception
if all(a is None for a in exc_info): if all(a is None for a in exc_info):
if self.expected_warning is not None: if self.expected_warning is not None:
if not any(r.category in self.expected_warning for r in self): if not any(issubclass(r.category, self.expected_warning)
for r in self):
__tracebackhide__ = True __tracebackhide__ = True
pytest.fail("DID NOT WARN. No warnings of type {0} was emitted. " from _pytest.runner import fail
"The list of emitted warnings is: {1}.".format( fail("DID NOT WARN. No warnings of type {0} was emitted. "
self.expected_warning, "The list of emitted warnings is: {1}.".format(
[each.message for each in self])) self.expected_warning,
[each.message for each in self]))

View File

@ -1,6 +1,7 @@
""" log machine-parseable test session result information in a plain """ log machine-parseable test session result information in a plain
text file. text file.
""" """
from __future__ import absolute_import, division, print_function
import py import py
import os import os
@ -61,9 +62,9 @@ class ResultLog(object):
self.logfile = logfile # preferably line buffered self.logfile = logfile # preferably line buffered
def write_log_entry(self, testpath, lettercode, longrepr): def write_log_entry(self, testpath, lettercode, longrepr):
py.builtin.print_("%s %s" % (lettercode, testpath), file=self.logfile) print("%s %s" % (lettercode, testpath), file=self.logfile)
for line in longrepr.splitlines(): for line in longrepr.splitlines():
py.builtin.print_(" %s" % line, file=self.logfile) print(" %s" % line, file=self.logfile)
def log_outcome(self, report, lettercode, longrepr): def log_outcome(self, report, lettercode, longrepr):
testpath = getattr(report, 'nodeid', None) testpath = getattr(report, 'nodeid', None)

View File

@ -1,20 +1,14 @@
""" basic collect and runtest protocol implementations """ """ basic collect and runtest protocol implementations """
from __future__ import absolute_import, division, print_function
import bdb import bdb
import sys import sys
from time import time from time import time
import py import py
import pytest
from _pytest._code.code import TerminalRepr, ExceptionInfo from _pytest._code.code import TerminalRepr, ExceptionInfo
def pytest_namespace():
return {
'fail' : fail,
'skip' : skip,
'importorskip' : importorskip,
'exit' : exit,
}
# #
# pytest plugin hooks # pytest plugin hooks
@ -262,7 +256,7 @@ def pytest_runtest_makereport(item, call):
if not isinstance(excinfo, ExceptionInfo): if not isinstance(excinfo, ExceptionInfo):
outcome = "failed" outcome = "failed"
longrepr = excinfo longrepr = excinfo
elif excinfo.errisinstance(pytest.skip.Exception): elif excinfo.errisinstance(skip.Exception):
outcome = "skipped" outcome = "skipped"
r = excinfo._getreprcrash() r = excinfo._getreprcrash()
longrepr = (str(r.path), r.lineno, r.message) longrepr = (str(r.path), r.lineno, r.message)
@ -330,7 +324,9 @@ class TeardownErrorReport(BaseReport):
self.__dict__.update(extra) self.__dict__.update(extra)
def pytest_make_collect_report(collector): def pytest_make_collect_report(collector):
call = CallInfo(collector._memocollect, "memocollect") call = CallInfo(
lambda: list(collector.collect()),
'collect')
longrepr = None longrepr = None
if not call.excinfo: if not call.excinfo:
outcome = "passed" outcome = "passed"
@ -550,14 +546,21 @@ def importorskip(modname, minversion=None):
__version__ attribute. If no minversion is specified the a skip __version__ attribute. If no minversion is specified the a skip
is only triggered if the module can not be imported. is only triggered if the module can not be imported.
""" """
import warnings
__tracebackhide__ = True __tracebackhide__ = True
compile(modname, '', 'eval') # to catch syntaxerrors compile(modname, '', 'eval') # to catch syntaxerrors
should_skip = False should_skip = False
try:
__import__(modname) with warnings.catch_warnings():
except ImportError: # make sure to ignore ImportWarnings that might happen because
# Do not raise chained exception here(#1485) # of existing directories with the same name we're trying to
should_skip = True # import but without a __init__.py file
warnings.simplefilter('ignore')
try:
__import__(modname)
except ImportError:
# Do not raise chained exception here(#1485)
should_skip = True
if should_skip: if should_skip:
raise Skipped("could not import %r" %(modname,), allow_module_level=True) raise Skipped("could not import %r" %(modname,), allow_module_level=True)
mod = sys.modules[modname] mod = sys.modules[modname]
@ -575,4 +578,3 @@ def importorskip(modname, minversion=None):
raise Skipped("module %r has __version__ %r, required is: %r" %( raise Skipped("module %r has __version__ %r, required is: %r" %(
modname, verattr, minversion), allow_module_level=True) modname, verattr, minversion), allow_module_level=True)
return mod return mod

View File

@ -1,3 +1,5 @@
from __future__ import absolute_import, division, print_function
import pytest import pytest
import sys import sys

View File

@ -1,3 +1,5 @@
from __future__ import absolute_import, division, print_function
import pytest import pytest

View File

@ -1,12 +1,14 @@
""" support for skip/xfail functions and markers. """ """ support for skip/xfail functions and markers. """
from __future__ import absolute_import, division, print_function
import os import os
import sys import sys
import traceback import traceback
import py import py
import pytest from _pytest.config import hookimpl
from _pytest.mark import MarkInfo, MarkDecorator from _pytest.mark import MarkInfo, MarkDecorator
from _pytest.runner import fail, skip
def pytest_addoption(parser): def pytest_addoption(parser):
group = parser.getgroup("general") group = parser.getgroup("general")
@ -23,6 +25,8 @@ def pytest_addoption(parser):
def pytest_configure(config): def pytest_configure(config):
if config.option.runxfail: if config.option.runxfail:
# yay a hack
import pytest
old = pytest.xfail old = pytest.xfail
config._cleanup.append(lambda: setattr(pytest, "xfail", old)) config._cleanup.append(lambda: setattr(pytest, "xfail", old))
@ -55,11 +59,7 @@ def pytest_configure(config):
) )
def pytest_namespace(): class XFailed(fail.Exception):
return dict(xfail=xfail)
class XFailed(pytest.fail.Exception):
""" raised from an explicit call to pytest.xfail() """ """ raised from an explicit call to pytest.xfail() """
@ -100,15 +100,15 @@ class MarkEvaluator:
except Exception: except Exception:
self.exc = sys.exc_info() self.exc = sys.exc_info()
if isinstance(self.exc[1], SyntaxError): if isinstance(self.exc[1], SyntaxError):
msg = [" " * (self.exc[1].offset + 4) + "^",] msg = [" " * (self.exc[1].offset + 4) + "^", ]
msg.append("SyntaxError: invalid syntax") msg.append("SyntaxError: invalid syntax")
else: else:
msg = traceback.format_exception_only(*self.exc[:2]) msg = traceback.format_exception_only(*self.exc[:2])
pytest.fail("Error evaluating %r expression\n" fail("Error evaluating %r expression\n"
" %s\n" " %s\n"
"%s" "%s"
%(self.name, self.expr, "\n".join(msg)), % (self.name, self.expr, "\n".join(msg)),
pytrace=False) pytrace=False)
def _getglobals(self): def _getglobals(self):
d = {'os': os, 'sys': sys, 'config': self.item.config} d = {'os': os, 'sys': sys, 'config': self.item.config}
@ -125,11 +125,9 @@ class MarkEvaluator:
# "holder" might be a MarkInfo or a MarkDecorator; only # "holder" might be a MarkInfo or a MarkDecorator; only
# MarkInfo keeps track of all parameters it received in an # MarkInfo keeps track of all parameters it received in an
# _arglist attribute # _arglist attribute
if hasattr(self.holder, '_arglist'): marks = getattr(self.holder, '_marks', None) \
arglist = self.holder._arglist or [self.holder.mark]
else: for _, args, kwargs in marks:
arglist = [(self.holder.args, self.holder.kwargs)]
for args, kwargs in arglist:
if 'condition' in kwargs: if 'condition' in kwargs:
args = (kwargs['condition'],) args = (kwargs['condition'],)
for expr in args: for expr in args:
@ -142,7 +140,7 @@ class MarkEvaluator:
# XXX better be checked at collection time # XXX better be checked at collection time
msg = "you need to specify reason=STRING " \ msg = "you need to specify reason=STRING " \
"when using booleans as conditions." "when using booleans as conditions."
pytest.fail(msg) fail(msg)
result = bool(expr) result = bool(expr)
if result: if result:
self.result = True self.result = True
@ -166,7 +164,7 @@ class MarkEvaluator:
return expl return expl
@pytest.hookimpl(tryfirst=True) @hookimpl(tryfirst=True)
def pytest_runtest_setup(item): def pytest_runtest_setup(item):
# Check if skip or skipif are specified as pytest marks # Check if skip or skipif are specified as pytest marks
@ -175,23 +173,23 @@ def pytest_runtest_setup(item):
eval_skipif = MarkEvaluator(item, 'skipif') eval_skipif = MarkEvaluator(item, 'skipif')
if eval_skipif.istrue(): if eval_skipif.istrue():
item._evalskip = eval_skipif item._evalskip = eval_skipif
pytest.skip(eval_skipif.getexplanation()) skip(eval_skipif.getexplanation())
skip_info = item.keywords.get('skip') skip_info = item.keywords.get('skip')
if isinstance(skip_info, (MarkInfo, MarkDecorator)): if isinstance(skip_info, (MarkInfo, MarkDecorator)):
item._evalskip = True item._evalskip = True
if 'reason' in skip_info.kwargs: if 'reason' in skip_info.kwargs:
pytest.skip(skip_info.kwargs['reason']) skip(skip_info.kwargs['reason'])
elif skip_info.args: elif skip_info.args:
pytest.skip(skip_info.args[0]) skip(skip_info.args[0])
else: else:
pytest.skip("unconditional skip") skip("unconditional skip")
item._evalxfail = MarkEvaluator(item, 'xfail') item._evalxfail = MarkEvaluator(item, 'xfail')
check_xfail_no_run(item) check_xfail_no_run(item)
@pytest.mark.hookwrapper @hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem): def pytest_pyfunc_call(pyfuncitem):
check_xfail_no_run(pyfuncitem) check_xfail_no_run(pyfuncitem)
outcome = yield outcome = yield
@ -206,7 +204,7 @@ def check_xfail_no_run(item):
evalxfail = item._evalxfail evalxfail = item._evalxfail
if evalxfail.istrue(): if evalxfail.istrue():
if not evalxfail.get('run', True): if not evalxfail.get('run', True):
pytest.xfail("[NOTRUN] " + evalxfail.getexplanation()) xfail("[NOTRUN] " + evalxfail.getexplanation())
def check_strict_xfail(pyfuncitem): def check_strict_xfail(pyfuncitem):
@ -218,10 +216,10 @@ def check_strict_xfail(pyfuncitem):
if is_strict_xfail: if is_strict_xfail:
del pyfuncitem._evalxfail del pyfuncitem._evalxfail
explanation = evalxfail.getexplanation() explanation = evalxfail.getexplanation()
pytest.fail('[XPASS(strict)] ' + explanation, pytrace=False) fail('[XPASS(strict)] ' + explanation, pytrace=False)
@pytest.hookimpl(hookwrapper=True) @hookimpl(hookwrapper=True)
def pytest_runtest_makereport(item, call): def pytest_runtest_makereport(item, call):
outcome = yield outcome = yield
rep = outcome.get_result() rep = outcome.get_result()
@ -241,7 +239,7 @@ def pytest_runtest_makereport(item, call):
rep.wasxfail = rep.longrepr rep.wasxfail = rep.longrepr
elif item.config.option.runxfail: elif item.config.option.runxfail:
pass # don't interefere pass # don't interefere
elif call.excinfo and call.excinfo.errisinstance(pytest.xfail.Exception): elif call.excinfo and call.excinfo.errisinstance(xfail.Exception):
rep.wasxfail = "reason: " + call.excinfo.value.msg rep.wasxfail = "reason: " + call.excinfo.value.msg
rep.outcome = "skipped" rep.outcome = "skipped"
elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \ elif evalxfail and not rep.skipped and evalxfail.wasvalid() and \
@ -309,12 +307,14 @@ def pytest_terminal_summary(terminalreporter):
for line in lines: for line in lines:
tr._tw.line(line) tr._tw.line(line)
def show_simple(terminalreporter, lines, stat, format): def show_simple(terminalreporter, lines, stat, format):
failed = terminalreporter.stats.get(stat) failed = terminalreporter.stats.get(stat)
if failed: if failed:
for rep in failed: for rep in failed:
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
lines.append(format %(pos,)) lines.append(format % (pos,))
def show_xfailed(terminalreporter, lines): def show_xfailed(terminalreporter, lines):
xfailed = terminalreporter.stats.get("xfailed") xfailed = terminalreporter.stats.get("xfailed")
@ -326,13 +326,15 @@ def show_xfailed(terminalreporter, lines):
if reason: if reason:
lines.append(" " + str(reason)) lines.append(" " + str(reason))
def show_xpassed(terminalreporter, lines): def show_xpassed(terminalreporter, lines):
xpassed = terminalreporter.stats.get("xpassed") xpassed = terminalreporter.stats.get("xpassed")
if xpassed: if xpassed:
for rep in xpassed: for rep in xpassed:
pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid) pos = terminalreporter.config.cwd_relative_nodeid(rep.nodeid)
reason = rep.wasxfail reason = rep.wasxfail
lines.append("XPASS %s %s" %(pos, reason)) lines.append("XPASS %s %s" % (pos, reason))
def cached_eval(config, expr, d): def cached_eval(config, expr, d):
if not hasattr(config, '_evalcache'): if not hasattr(config, '_evalcache'):
@ -357,6 +359,7 @@ def folded_skips(skipped):
l.append((len(events),) + key) l.append((len(events),) + key)
return l return l
def show_skipped(terminalreporter, lines): def show_skipped(terminalreporter, lines):
tr = terminalreporter tr = terminalreporter
skipped = tr.stats.get('skipped', []) skipped = tr.stats.get('skipped', [])
@ -372,5 +375,6 @@ def show_skipped(terminalreporter, lines):
for num, fspath, lineno, reason in fskips: for num, fspath, lineno, reason in fskips:
if reason.startswith("Skipped: "): if reason.startswith("Skipped: "):
reason = reason[9:] reason = reason[9:]
lines.append("SKIP [%d] %s:%d: %s" % lines.append(
"SKIP [%d] %s:%d: %s" %
(num, fspath, lineno, reason)) (num, fspath, lineno, reason))

View File

@ -2,6 +2,9 @@
This is a good source for looking at the various reporting hooks. This is a good source for looking at the various reporting hooks.
""" """
from __future__ import absolute_import, division, print_function
import itertools
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \ from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
import pytest import pytest
@ -24,11 +27,11 @@ def pytest_addoption(parser):
help="show extra test summary info as specified by chars (f)ailed, " help="show extra test summary info as specified by chars (f)ailed, "
"(E)error, (s)skipped, (x)failed, (X)passed, " "(E)error, (s)skipped, (x)failed, (X)passed, "
"(p)passed, (P)passed with output, (a)all except pP. " "(p)passed, (P)passed with output, (a)all except pP. "
"The pytest warnings are displayed at all times except when " "Warnings are displayed at all times except when "
"--disable-pytest-warnings is set") "--disable-warnings is set")
group._addoption('--disable-pytest-warnings', default=False, group._addoption('--disable-warnings', '--disable-pytest-warnings', default=False,
dest='disablepytestwarnings', action='store_true', dest='disable_warnings', action='store_true',
help='disable warnings summary, overrides -r w flag') help='disable warnings summary')
group._addoption('-l', '--showlocals', group._addoption('-l', '--showlocals',
action="store_true", dest="showlocals", default=False, action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default).") help="show locals in tracebacks (disabled by default).")
@ -57,9 +60,9 @@ def pytest_configure(config):
def getreportopt(config): def getreportopt(config):
reportopts = "" reportopts = ""
reportchars = config.option.reportchars reportchars = config.option.reportchars
if not config.option.disablepytestwarnings and 'w' not in reportchars: if not config.option.disable_warnings and 'w' not in reportchars:
reportchars += 'w' reportchars += 'w'
elif config.option.disablepytestwarnings and 'w' in reportchars: elif config.option.disable_warnings and 'w' in reportchars:
reportchars = reportchars.replace('w', '') reportchars = reportchars.replace('w', '')
if reportchars: if reportchars:
for char in reportchars: for char in reportchars:
@ -80,13 +83,40 @@ def pytest_report_teststatus(report):
letter = "f" letter = "f"
return report.outcome, letter, report.outcome.upper() return report.outcome, letter, report.outcome.upper()
class WarningReport: class WarningReport:
"""
Simple structure to hold warnings information captured by ``pytest_logwarning``.
"""
def __init__(self, code, message, nodeid=None, fslocation=None): def __init__(self, code, message, nodeid=None, fslocation=None):
"""
:param code: unused
:param str message: user friendly message about the warning
:param str|None nodeid: node id that generated the warning (see ``get_location``).
:param tuple|py.path.local fslocation:
file system location of the source of the warning (see ``get_location``).
"""
self.code = code self.code = code
self.message = message self.message = message
self.nodeid = nodeid self.nodeid = nodeid
self.fslocation = fslocation self.fslocation = fslocation
def get_location(self, config):
"""
Returns the more user-friendly information about the location
of a warning, or None.
"""
if self.nodeid:
return self.nodeid
if self.fslocation:
if isinstance(self.fslocation, tuple) and len(self.fslocation) >= 2:
filename, linenum = self.fslocation[:2]
relpath = py.path.local(filename).relto(config.invocation_dir)
return '%s:%s' % (relpath, linenum)
else:
return str(self.fslocation)
return None
class TerminalReporter: class TerminalReporter:
def __init__(self, config, file=None): def __init__(self, config, file=None):
@ -166,8 +196,6 @@ class TerminalReporter:
def pytest_logwarning(self, code, fslocation, message, nodeid): def pytest_logwarning(self, code, fslocation, message, nodeid):
warnings = self.stats.setdefault("warnings", []) warnings = self.stats.setdefault("warnings", [])
if isinstance(fslocation, tuple):
fslocation = "%s:%d" % fslocation
warning = WarningReport(code=code, fslocation=fslocation, warning = WarningReport(code=code, fslocation=fslocation,
message=message, nodeid=nodeid) message=message, nodeid=nodeid)
warnings.append(warning) warnings.append(warning)
@ -438,13 +466,21 @@ class TerminalReporter:
def summary_warnings(self): def summary_warnings(self):
if self.hasopt("w"): if self.hasopt("w"):
warnings = self.stats.get("warnings") all_warnings = self.stats.get("warnings")
if not warnings: if not all_warnings:
return return
self.write_sep("=", "pytest-warning summary")
for w in warnings: grouped = itertools.groupby(all_warnings, key=lambda wr: wr.get_location(self.config))
self._tw.line("W%s %s %s" % (w.code,
w.fslocation, w.message)) self.write_sep("=", "warnings summary", yellow=True, bold=False)
for location, warnings in grouped:
self._tw.line(str(location) or '<undetermined location>')
for w in warnings:
lines = w.message.splitlines()
indented = '\n'.join(' ' + x for x in lines)
self._tw.line(indented)
self._tw.line()
self._tw.line('-- Docs: http://doc.pytest.org/en/latest/warnings.html')
def summary_passes(self): def summary_passes(self):
if self.config.option.tbstyle != "no": if self.config.option.tbstyle != "no":
@ -546,8 +582,7 @@ def flatten(l):
def build_summary_stats_line(stats): def build_summary_stats_line(stats):
keys = ("failed passed skipped deselected " keys = ("failed passed skipped deselected "
"xfailed xpassed warnings error").split() "xfailed xpassed warnings error").split()
key_translation = {'warnings': 'pytest-warnings'}
unknown_key_seen = False unknown_key_seen = False
for key in stats.keys(): for key in stats.keys():
if key not in keys: if key not in keys:
@ -558,8 +593,7 @@ def build_summary_stats_line(stats):
for key in keys: for key in keys:
val = stats.get(key, None) val = stats.get(key, None)
if val: if val:
key_name = key_translation.get(key, key) parts.append("%d %s" % (len(val), key))
parts.append("%d %s" % (len(val), key_name))
if parts: if parts:
line = ", ".join(parts) line = ", ".join(parts)

View File

@ -1,4 +1,6 @@
""" support for providing temporary directories to test functions. """ """ support for providing temporary directories to test functions. """
from __future__ import absolute_import, division, print_function
import re import re
import pytest import pytest

View File

@ -1,14 +1,15 @@
""" discovery and running of std-library "unittest" style tests. """ """ discovery and running of std-library "unittest" style tests. """
from __future__ import absolute_import from __future__ import absolute_import, division, print_function
import sys import sys
import traceback import traceback
import pytest
# for transferring markers # for transferring markers
import _pytest._code import _pytest._code
from _pytest.python import transfer_markers from _pytest.config import hookimpl
from _pytest.skipping import MarkEvaluator from _pytest.runner import fail, skip
from _pytest.python import transfer_markers, Class, Module, Function
from _pytest.skipping import MarkEvaluator, xfail
def pytest_pycollect_makeitem(collector, name, obj): def pytest_pycollect_makeitem(collector, name, obj):
@ -22,11 +23,11 @@ def pytest_pycollect_makeitem(collector, name, obj):
return UnitTestCase(name, parent=collector) return UnitTestCase(name, parent=collector)
class UnitTestCase(pytest.Class): class UnitTestCase(Class):
# marker for fixturemanger.getfixtureinfo() # marker for fixturemanger.getfixtureinfo()
# to declare that our children do not support funcargs # to declare that our children do not support funcargs
nofuncargs = True nofuncargs = True
def setup(self): def setup(self):
cls = self.obj cls = self.obj
if getattr(cls, '__unittest_skip__', False): if getattr(cls, '__unittest_skip__', False):
@ -46,7 +47,7 @@ class UnitTestCase(pytest.Class):
return return
self.session._fixturemanager.parsefactories(self, unittest=True) self.session._fixturemanager.parsefactories(self, unittest=True)
loader = TestLoader() loader = TestLoader()
module = self.getparent(pytest.Module).obj module = self.getparent(Module).obj
foundsomething = False foundsomething = False
for name in loader.getTestCaseNames(self.obj): for name in loader.getTestCaseNames(self.obj):
x = getattr(self.obj, name) x = getattr(self.obj, name)
@ -65,7 +66,7 @@ class UnitTestCase(pytest.Class):
yield TestCaseFunction('runTest', parent=self) yield TestCaseFunction('runTest', parent=self)
class TestCaseFunction(pytest.Function): class TestCaseFunction(Function):
_excinfo = None _excinfo = None
def setup(self): def setup(self):
@ -110,36 +111,37 @@ class TestCaseFunction(pytest.Function):
try: try:
l = traceback.format_exception(*rawexcinfo) l = traceback.format_exception(*rawexcinfo)
l.insert(0, "NOTE: Incompatible Exception Representation, " l.insert(0, "NOTE: Incompatible Exception Representation, "
"displaying natively:\n\n") "displaying natively:\n\n")
pytest.fail("".join(l), pytrace=False) fail("".join(l), pytrace=False)
except (pytest.fail.Exception, KeyboardInterrupt): except (fail.Exception, KeyboardInterrupt):
raise raise
except: except:
pytest.fail("ERROR: Unknown Incompatible Exception " fail("ERROR: Unknown Incompatible Exception "
"representation:\n%r" %(rawexcinfo,), pytrace=False) "representation:\n%r" % (rawexcinfo,), pytrace=False)
except KeyboardInterrupt: except KeyboardInterrupt:
raise raise
except pytest.fail.Exception: except fail.Exception:
excinfo = _pytest._code.ExceptionInfo() excinfo = _pytest._code.ExceptionInfo()
self.__dict__.setdefault('_excinfo', []).append(excinfo) self.__dict__.setdefault('_excinfo', []).append(excinfo)
def addError(self, testcase, rawexcinfo): def addError(self, testcase, rawexcinfo):
self._addexcinfo(rawexcinfo) self._addexcinfo(rawexcinfo)
def addFailure(self, testcase, rawexcinfo): def addFailure(self, testcase, rawexcinfo):
self._addexcinfo(rawexcinfo) self._addexcinfo(rawexcinfo)
def addSkip(self, testcase, reason): def addSkip(self, testcase, reason):
try: try:
pytest.skip(reason) skip(reason)
except pytest.skip.Exception: except skip.Exception:
self._evalskip = MarkEvaluator(self, 'SkipTest') self._evalskip = MarkEvaluator(self, 'SkipTest')
self._evalskip.result = True self._evalskip.result = True
self._addexcinfo(sys.exc_info()) self._addexcinfo(sys.exc_info())
def addExpectedFailure(self, testcase, rawexcinfo, reason=""): def addExpectedFailure(self, testcase, rawexcinfo, reason=""):
try: try:
pytest.xfail(str(reason)) xfail(str(reason))
except pytest.xfail.Exception: except xfail.Exception:
self._addexcinfo(sys.exc_info()) self._addexcinfo(sys.exc_info())
def addUnexpectedSuccess(self, testcase, reason=""): def addUnexpectedSuccess(self, testcase, reason=""):
@ -179,13 +181,14 @@ class TestCaseFunction(pytest.Function):
self._testcase.debug() self._testcase.debug()
def _prunetraceback(self, excinfo): def _prunetraceback(self, excinfo):
pytest.Function._prunetraceback(self, excinfo) Function._prunetraceback(self, excinfo)
traceback = excinfo.traceback.filter( traceback = excinfo.traceback.filter(
lambda x:not x.frame.f_globals.get('__unittest')) lambda x: not x.frame.f_globals.get('__unittest'))
if traceback: if traceback:
excinfo.traceback = traceback excinfo.traceback = traceback
@pytest.hookimpl(tryfirst=True)
@hookimpl(tryfirst=True)
def pytest_runtest_makereport(item, call): def pytest_runtest_makereport(item, call):
if isinstance(item, TestCaseFunction): if isinstance(item, TestCaseFunction):
if item._excinfo: if item._excinfo:
@ -197,7 +200,8 @@ def pytest_runtest_makereport(item, call):
# twisted trial support # twisted trial support
@pytest.hookimpl(hookwrapper=True)
@hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item): def pytest_runtest_protocol(item):
if isinstance(item, TestCaseFunction) and \ if isinstance(item, TestCaseFunction) and \
'twisted.trial.unittest' in sys.modules: 'twisted.trial.unittest' in sys.modules:

73
_pytest/warnings.py Normal file
View File

@ -0,0 +1,73 @@
from __future__ import absolute_import, division, print_function
import warnings
from contextlib import contextmanager
import pytest
def _setoption(wmod, arg):
"""
Copy of the warning._setoption function but does not escape arguments.
"""
parts = arg.split(':')
if len(parts) > 5:
raise wmod._OptionError("too many fields (max 5): %r" % (arg,))
while len(parts) < 5:
parts.append('')
action, message, category, module, lineno = [s.strip()
for s in parts]
action = wmod._getaction(action)
category = wmod._getcategory(category)
if lineno:
try:
lineno = int(lineno)
if lineno < 0:
raise ValueError
except (ValueError, OverflowError):
raise wmod._OptionError("invalid lineno %r" % (lineno,))
else:
lineno = 0
wmod.filterwarnings(action, message, category, module, lineno)
def pytest_addoption(parser):
group = parser.getgroup("pytest-warnings")
group.addoption(
'-W', '--pythonwarnings', action='append',
help="set which warnings to report, see -W option of python itself.")
parser.addini("filterwarnings", type="linelist",
help="Each line specifies warning filter pattern which would be passed"
"to warnings.filterwarnings. Process after -W and --pythonwarnings.")
@contextmanager
def catch_warnings_for_item(item):
"""
catches the warnings generated during setup/call/teardown execution
of the given item and after it is done posts them as warnings to this
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)
for arg in inifilters:
_setoption(warnings, arg)
yield
for warning in log:
msg = warnings.formatwarning(
warning.message, warning.category,
warning.filename, warning.lineno, warning.line)
item.warn("unused", msg)
@pytest.hookimpl(hookwrapper=True)
def pytest_runtest_protocol(item):
with catch_warnings_for_item(item):
yield

View File

@ -17,7 +17,12 @@ REGENDOC_ARGS := \
--normalize "/_{8,} (.*) _{8,}/_______ \1 ________/" \ --normalize "/_{8,} (.*) _{8,}/_______ \1 ________/" \
--normalize "/in \d+.\d+ seconds/in 0.12 seconds/" \ --normalize "/in \d+.\d+ seconds/in 0.12 seconds/" \
--normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \ --normalize "@/tmp/pytest-of-.*/pytest-\d+@PYTEST_TMPDIR@" \
--normalize "@pytest-(\d+)\\.[^ ,]+@pytest-\1.x.y@" \
--normalize "@(This is pytest version )(\d+)\\.[^ ,]+@\1\2.x.y@" \
--normalize "@py-(\d+)\\.[^ ,]+@py-\1.x.y@" \
--normalize "@pluggy-(\d+)\\.[.\d,]+@pluggy-\1.x.y@" \
--normalize "@hypothesis-(\d+)\\.[.\d,]+@hypothesis-\1.x.y@" \
--normalize "@Python (\d+)\\.[^ ,]+@Python \1.x.y@"
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
@ -36,7 +41,7 @@ clean:
-rm -rf $(BUILDDIR)/* -rm -rf $(BUILDDIR)/*
regen: regen:
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS} PYTHONDONTWRITEBYTECODE=1 PYTEST_ADDOPT=-pno:hypothesis COLUMNS=76 regendoc --update *.rst */*.rst ${REGENDOC_ARGS}
html: html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

View File

@ -6,6 +6,7 @@ Release announcements
:maxdepth: 2 :maxdepth: 2
release-3.1.0
release-3.0.7 release-3.0.7
release-3.0.6 release-3.0.6
release-3.0.5 release-3.0.5

View File

@ -0,0 +1,61 @@
pytest-3.1.0
=======================================
The pytest team is proud to announce the 3.1.0 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
to take a look at the CHANGELOG:
http://doc.pytest.org/en/latest/changelog.html
For complete documentation, please visit:
http://docs.pytest.org
As usual, you can upgrade from pypi via:
pip install -U pytest
Thanks to all who contributed to this release, among them:
* Anthony Sottile
* Ben Lloyd
* Bruno Oliveira
* David Giese
* David Szotten
* Dmitri Pribysh
* Florian Bruhin
* Florian Schulze
* Floris Bruynooghe
* John Towler
* Jonas Obrist
* Katerina Koukiou
* Kodi Arfer
* Krzysztof Szularz
* Lev Maximov
* Loïc Estève
* Luke Murphy
* Manuel Krebber
* Matthew Duck
* Matthias Bussonnier
* Michael Howitz
* Michal Wajszczuk
* Paweł Adamczak
* Rafael Bertoldi
* Ravi Chandra
* Ronny Pfannschmidt
* Skylar Downes
* Thomas Kriechbaumer
* Vitaly Lashmanov
* Vlad Dragos
* Wheerd
* Xander Johnson
* mandeep
* reut
Happy testing,
The Pytest Development Team

View File

@ -26,7 +26,7 @@ you will see the return value of the function call::
$ pytest test_assert1.py $ pytest test_assert1.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items collected 1 items
@ -170,7 +170,7 @@ if you run this module::
$ pytest test_assert2.py $ pytest test_assert2.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items collected 1 items
@ -223,7 +223,7 @@ provides an alternative explanation for ``Foo`` objects::
now, given this test module:: now, given this test module::
# content of test_foocompare.py # content of test_foocompare.py
class Foo: class Foo(object):
def __init__(self, val): def __init__(self, val):
self.val = val self.val = val

View File

@ -80,7 +80,7 @@ If you then run it with ``--lf``::
$ pytest --lf $ pytest --lf
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
run-last-failure: rerun last 2 failures run-last-failure: rerun last 2 failures
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 50 items collected 50 items
@ -122,7 +122,7 @@ of ``FF`` and dots)::
$ pytest --ff $ pytest --ff
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
run-last-failure: rerun last 2 failures first run-last-failure: rerun last 2 failures first
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 50 items collected 50 items
@ -227,14 +227,14 @@ You can always peek at the content of the cache using the
$ py.test --cache-show $ py.test --cache-show
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
cachedir: $REGENDOC_TMPDIR/.cache cachedir: $REGENDOC_TMPDIR/.cache
------------------------------- cache values ------------------------------- ------------------------------- cache values -------------------------------
cache/lastfailed contains:
{'test_caching.py::test_function': True}
example/value contains: example/value contains:
42 42
cache/lastfailed contains:
{'test_caching.py::test_function': True}
======= no tests ran in 0.12 seconds ======== ======= no tests ran in 0.12 seconds ========

View File

@ -64,7 +64,7 @@ of the failing function and hide the other one::
$ pytest $ pytest
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items collected 2 items

View File

@ -19,7 +19,7 @@ Full pytest documentation
monkeypatch monkeypatch
tmpdir tmpdir
capture capture
recwarn warnings
doctest doctest
mark mark
skipping skipping

View File

@ -160,7 +160,7 @@ Builtin configuration file options
[seq] matches any character in seq [seq] matches any character in seq
[!seq] matches any char not in seq [!seq] matches any char not in seq
Default patterns are ``'.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg'``. Default patterns are ``'.*', 'build', 'dist', 'CVS', '_darcs', '{arch}', '*.egg', 'venv'``.
Setting a ``norecursedirs`` replaces the default. Here is an example of Setting a ``norecursedirs`` replaces the default. Here is an example of
how to avoid certain directories: how to avoid certain directories:
@ -242,3 +242,23 @@ Builtin configuration file options
By default, pytest will stop searching for ``conftest.py`` files upwards By default, pytest will stop searching for ``conftest.py`` files upwards
from ``pytest.ini``/``tox.ini``/``setup.cfg`` of the project if any, from ``pytest.ini``/``tox.ini``/``setup.cfg`` of the project if any,
or up to the file-system root. or up to the file-system root.
.. confval:: filterwarnings
.. versionadded:: 3.1
Sets a list of filters and actions that should be taken for matched
warnings. By default all warnings emitted during the test session
will be displayed in a summary at the end of the test session.
.. code-block:: ini
# content of pytest.ini
[pytest]
filterwarnings =
error
ignore::DeprecationWarning
This tells pytest to ignore deprecation warnings and turn all other warnings
into errors. For more information please refer to :ref:`warnings`.

View File

@ -11,6 +11,19 @@ can change the pattern by issuing::
on the command line. Since version ``2.9``, ``--doctest-glob`` on the command line. Since version ``2.9``, ``--doctest-glob``
can be given multiple times in the command-line. can be given multiple times in the command-line.
.. versionadded:: 3.1
You can specify the encoding that will be used for those doctest files
using the ``doctest_encoding`` ini option:
.. code-block:: ini
# content of pytest.ini
[pytest]
doctest_encoding = latin1
The default encoding is UTF-8.
You can also trigger running of doctests You can also trigger running of doctests
from docstrings in all python modules (including regular from docstrings in all python modules (including regular
python test modules):: python test modules)::
@ -49,7 +62,7 @@ then you can just invoke ``pytest`` without command line options::
$ pytest $ pytest
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 1 items collected 1 items

View File

@ -128,7 +128,7 @@ def test_attribute_multiple():
def globf(x): def globf(x):
return x+1 return x+1
class TestRaises: class TestRaises(object):
def test_raises(self): def test_raises(self):
s = 'qwe' s = 'qwe'
raises(TypeError, "int(s)") raises(TypeError, "int(s)")
@ -167,7 +167,7 @@ def test_dynamic_compile_shows_nicely():
class TestMoreErrors: class TestMoreErrors(object):
def test_complex_error(self): def test_complex_error(self):
def f(): def f():
return 44 return 44
@ -213,23 +213,23 @@ class TestMoreErrors:
x = 0 x = 0
class TestCustomAssertMsg: class TestCustomAssertMsg(object):
def test_single_line(self): def test_single_line(self):
class A: class A(object):
a = 1 a = 1
b = 2 b = 2
assert A.a == b, "A.a appears not to be b" assert A.a == b, "A.a appears not to be b"
def test_multiline(self): def test_multiline(self):
class A: class A(object):
a = 1 a = 1
b = 2 b = 2
assert A.a == b, "A.a appears not to be b\n" \ assert A.a == b, "A.a appears not to be b\n" \
"or does not appear to be b\none of those" "or does not appear to be b\none of those"
def test_custom_repr(self): def test_custom_repr(self):
class JSON: class JSON(object):
a = 1 a = 1
def __repr__(self): def __repr__(self):
return "This is JSON\n{\n 'foo': 'bar'\n}" return "This is JSON\n{\n 'foo': 'bar'\n}"

View File

@ -1,7 +1,7 @@
def setup_module(module): def setup_module(module):
module.TestStateFullThing.classcount = 0 module.TestStateFullThing.classcount = 0
class TestStateFullThing: class TestStateFullThing(object):
def setup_class(cls): def setup_class(cls):
cls.classcount += 1 cls.classcount += 1

View File

@ -15,7 +15,7 @@ example: specifying and selecting acceptance tests
def pytest_funcarg__accept(request): def pytest_funcarg__accept(request):
return AcceptFixture(request) return AcceptFixture(request)
class AcceptFixture: class AcceptFixture(object):
def __init__(self, request): def __init__(self, request):
if not request.config.option.acceptance: if not request.config.option.acceptance:
pytest.skip("specify -A to run acceptance tests") pytest.skip("specify -A to run acceptance tests")
@ -61,7 +61,7 @@ extend the `accept example`_ by putting this in our test module:
arg.tmpdir.mkdir("special") arg.tmpdir.mkdir("special")
return arg return arg
class TestSpecialAcceptance: class TestSpecialAcceptance(object):
def test_sometest(self, accept): def test_sometest(self, accept):
assert accept.tmpdir.join("special").check() assert accept.tmpdir.join("special").check()

View File

@ -7,7 +7,7 @@ def setup(request):
yield setup yield setup
setup.finalize() setup.finalize()
class CostlySetup: class CostlySetup(object):
def __init__(self): def __init__(self):
import time import time
print ("performing costly setup") print ("performing costly setup")

View File

@ -21,7 +21,7 @@ You can "mark" a test function with custom metadata like this::
pass pass
def test_another(): def test_another():
pass pass
class TestClass: class TestClass(object):
def test_method(self): def test_method(self):
pass pass
@ -31,7 +31,7 @@ You can then restrict a test run to only run tests marked with ``webtest``::
$ pytest -v -m webtest $ pytest -v -m webtest
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 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 cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items collecting ... collected 4 items
@ -45,7 +45,7 @@ Or the inverse, running all tests except the webtest ones::
$ pytest -v -m "not webtest" $ pytest -v -m "not webtest"
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 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 cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items collecting ... collected 4 items
@ -66,7 +66,7 @@ tests based on their module, class, method, or function name::
$ pytest -v test_server.py::TestClass::test_method $ pytest -v test_server.py::TestClass::test_method
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 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 cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 5 items collecting ... collected 5 items
@ -79,7 +79,7 @@ You can also select on the class::
$ pytest -v test_server.py::TestClass $ pytest -v test_server.py::TestClass
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 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 cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items collecting ... collected 4 items
@ -92,7 +92,7 @@ Or select multiple nodes::
$ pytest -v test_server.py::TestClass test_server.py::test_send_http $ pytest -v test_server.py::TestClass test_server.py::test_send_http
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 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 cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 8 items collecting ... collected 8 items
@ -130,7 +130,7 @@ select tests based on their names::
$ pytest -v -k http # running with the above defined example module $ pytest -v -k http # running with the above defined example module
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 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 cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items collecting ... collected 4 items
@ -144,7 +144,7 @@ And you can also run all tests except the ones that match the keyword::
$ pytest -k "not send_http" -v $ pytest -k "not send_http" -v
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 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 cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items collecting ... collected 4 items
@ -160,7 +160,7 @@ Or to select "http" and "quick" tests::
$ pytest -k "http or quick" -v $ pytest -k "http or quick" -v
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 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 cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 4 items collecting ... collected 4 items
@ -242,7 +242,7 @@ its test methods::
# content of test_mark_classlevel.py # content of test_mark_classlevel.py
import pytest import pytest
@pytest.mark.webtest @pytest.mark.webtest
class TestClass: class TestClass(object):
def test_startup(self): def test_startup(self):
pass pass
def test_startup_and_more(self): def test_startup_and_more(self):
@ -256,14 +256,14 @@ To remain backward-compatible with Python 2.4 you can also set a
import pytest import pytest
class TestClass: class TestClass(object):
pytestmark = pytest.mark.webtest pytestmark = pytest.mark.webtest
or if you need to use multiple markers you can use a list:: or if you need to use multiple markers you can use a list::
import pytest import pytest
class TestClass: class TestClass(object):
pytestmark = [pytest.mark.webtest, pytest.mark.slowtest] pytestmark = [pytest.mark.webtest, pytest.mark.slowtest]
You can also set a module level marker:: You can also set a module level marker::
@ -352,7 +352,7 @@ the test needs::
$ pytest -E stage2 $ pytest -E stage2
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items collected 1 items
@ -364,7 +364,7 @@ and here is one that specifies exactly the environment needed::
$ pytest -E stage1 $ pytest -E stage1
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items collected 1 items
@ -407,7 +407,7 @@ code you can read over all such settings. Example::
pytestmark = pytest.mark.glob("module", x=1) pytestmark = pytest.mark.glob("module", x=1)
@pytest.mark.glob("class", x=2) @pytest.mark.glob("class", x=2)
class TestClass: class TestClass(object):
@pytest.mark.glob("function", x=3) @pytest.mark.glob("function", x=3)
def test_something(self): def test_something(self):
pass pass
@ -485,7 +485,7 @@ then you will see two tests skipped and two executed tests as expected::
$ pytest -rs # this option reports skip reasons $ pytest -rs # this option reports skip reasons
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items collected 4 items
@ -499,7 +499,7 @@ Note that if you specify a platform via the marker-command line option like this
$ pytest -m linux $ pytest -m linux
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items collected 4 items
@ -551,7 +551,7 @@ We can now use the ``-m option`` to select one set::
$ pytest -m interface --tb=short $ pytest -m interface --tb=short
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items collected 4 items
@ -573,7 +573,7 @@ or to select both "event" and "interface" tests::
$ pytest -m "interface or event" --tb=short $ pytest -m "interface or event" --tb=short
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items collected 4 items

View File

@ -16,7 +16,7 @@ def python1(request, tmpdir):
def python2(request, python1): def python2(request, python1):
return Python(request.param, python1.picklefile) return Python(request.param, python1.picklefile)
class Python: class Python(object):
def __init__(self, version, picklefile): def __init__(self, version, picklefile):
self.pythonpath = py.path.local.sysfind(version) self.pythonpath = py.path.local.sysfind(version)
if not self.pythonpath: if not self.pythonpath:

View File

@ -27,7 +27,7 @@ now execute the test specification::
nonpython $ pytest test_simple.yml nonpython $ pytest test_simple.yml
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR/nonpython, inifile: rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
collected 2 items collected 2 items
@ -59,7 +59,7 @@ consulted when reporting in ``verbose`` mode::
nonpython $ pytest -v nonpython $ pytest -v
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 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 cachedir: .cache
rootdir: $REGENDOC_TMPDIR/nonpython, inifile: rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
collecting ... collected 2 items collecting ... collected 2 items
@ -81,7 +81,7 @@ interesting to just look at the collection tree::
nonpython $ pytest --collect-only nonpython $ pytest --collect-only
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR/nonpython, inifile: rootdir: $REGENDOC_TMPDIR/nonpython, inifile:
collected 2 items collected 2 items
<YamlFile 'test_simple.yml'> <YamlFile 'test_simple.yml'>

View File

@ -130,7 +130,7 @@ objects, they are still using the default pytest representation::
$ pytest test_time.py --collect-only $ pytest test_time.py --collect-only
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 6 items collected 6 items
<Module 'test_time.py'> <Module 'test_time.py'>
@ -168,7 +168,7 @@ only have to work a bit to construct the correct arguments for pytest's
scenario1 = ('basic', {'attribute': 'value'}) scenario1 = ('basic', {'attribute': 'value'})
scenario2 = ('advanced', {'attribute': 'value2'}) scenario2 = ('advanced', {'attribute': 'value2'})
class TestSampleWithScenarios: class TestSampleWithScenarios(object):
scenarios = [scenario1, scenario2] scenarios = [scenario1, scenario2]
def test_demo1(self, attribute): def test_demo1(self, attribute):
@ -181,7 +181,7 @@ this is a fully self-contained example which you can run with::
$ pytest test_scenarios.py $ pytest test_scenarios.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items collected 4 items
@ -194,7 +194,7 @@ If you just collect tests you'll also nicely see 'advanced' and 'basic' as varia
$ pytest --collect-only test_scenarios.py $ pytest --collect-only test_scenarios.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items collected 4 items
<Module 'test_scenarios.py'> <Module 'test_scenarios.py'>
@ -241,9 +241,9 @@ creates a database object for the actual test invocations::
if 'db' in metafunc.fixturenames: if 'db' in metafunc.fixturenames:
metafunc.parametrize("db", ['d1', 'd2'], indirect=True) metafunc.parametrize("db", ['d1', 'd2'], indirect=True)
class DB1: class DB1(object):
"one database object" "one database object"
class DB2: class DB2(object):
"alternative database object" "alternative database object"
@pytest.fixture @pytest.fixture
@ -259,7 +259,7 @@ Let's first see how it looks like at collection time::
$ pytest test_backends.py --collect-only $ pytest test_backends.py --collect-only
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items collected 2 items
<Module 'test_backends.py'> <Module 'test_backends.py'>
@ -320,7 +320,7 @@ The result of this test will be successful::
$ pytest test_indirect_list.py --collect-only $ pytest test_indirect_list.py --collect-only
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items collected 1 items
<Module 'test_indirect_list.py'> <Module 'test_indirect_list.py'>
@ -350,7 +350,7 @@ parametrizer`_ but in a lot less code::
metafunc.parametrize(argnames, [[funcargs[name] for name in argnames] metafunc.parametrize(argnames, [[funcargs[name] for name in argnames]
for funcargs in funcarglist]) for funcargs in funcarglist])
class TestClass: class TestClass(object):
# a map specifying multiple argument sets for a test method # a map specifying multiple argument sets for a test method
params = { params = {
'test_equals': [dict(a=1, b=2), dict(a=3, b=3), ], 'test_equals': [dict(a=1, b=2), dict(a=3, b=3), ],
@ -447,7 +447,7 @@ If you run this with reporting for skips enabled::
$ pytest -rs test_module.py $ pytest -rs test_module.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items collected 2 items

View File

@ -4,7 +4,7 @@
def test_function(): def test_function():
pass pass
class TestClass: class TestClass(object):
def test_method(self): def test_method(self):
pass pass
def test_anothermethod(self): def test_anothermethod(self):

View File

@ -107,7 +107,7 @@ This would make ``pytest`` look for tests in files that match the ``check_*
that match ``*_check``. For example, if we have:: that match ``*_check``. For example, if we have::
# content of check_myapp.py # content of check_myapp.py
class CheckMyApp: class CheckMyApp(object):
def simple_check(self): def simple_check(self):
pass pass
def complex_check(self): def complex_check(self):
@ -117,7 +117,7 @@ then the test collection looks like this::
$ pytest --collect-only $ pytest --collect-only
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 2 items collected 2 items
<Module 'check_myapp.py'> <Module 'check_myapp.py'>
@ -163,7 +163,7 @@ You can always peek at the collection tree without running tests like this::
. $ pytest --collect-only pythoncollection.py . $ pytest --collect-only pythoncollection.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 3 items collected 3 items
<Module 'CWD/pythoncollection.py'> <Module 'CWD/pythoncollection.py'>
@ -230,7 +230,7 @@ will be left out::
$ pytest --collect-only $ pytest --collect-only
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini rootdir: $REGENDOC_TMPDIR, inifile: pytest.ini
collected 0 items collected 0 items

View File

@ -11,7 +11,7 @@ get on the terminal - we are working on that)::
assertion $ pytest failure_demo.py assertion $ pytest failure_demo.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR/assertion, inifile: rootdir: $REGENDOC_TMPDIR/assertion, inifile:
collected 42 items collected 42 items
@ -144,13 +144,9 @@ get on the terminal - we are working on that)::
E 1 E 1
E 1 E 1
E 1 E 1
E 1 E 1...
E - a2 E
E + b2 E ...Full output truncated (7 lines hidden), use '-vv' to show
E 2
E 2
E 2
E 2
failure_demo.py:59: AssertionError failure_demo.py:59: AssertionError
_______ TestSpecialisedExplanations.test_eq_list ________ _______ TestSpecialisedExplanations.test_eq_list ________
@ -184,14 +180,15 @@ get on the terminal - we are working on that)::
def test_eq_dict(self): def test_eq_dict(self):
> assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} > assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
E AssertionError: assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} E AssertionError: assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
E Omitting 1 identical items, use -v to show E Omitting 1 identical items, use -vv to show
E Differing items: E Differing items:
E {'b': 1} != {'b': 2} E {'b': 1} != {'b': 2}
E Left contains more items: E Left contains more items:
E {'c': 0} E {'c': 0}
E Right contains more items: E Right contains more items:
E {'d': 0} E {'d': 0}...
E Use -v to get the full diff E
E ...Full output truncated (2 lines hidden), use '-vv' to show
failure_demo.py:70: AssertionError failure_demo.py:70: AssertionError
_______ TestSpecialisedExplanations.test_eq_set ________ _______ TestSpecialisedExplanations.test_eq_set ________
@ -200,15 +197,16 @@ get on the terminal - we are working on that)::
def test_eq_set(self): def test_eq_set(self):
> assert set([0, 10, 11, 12]) == set([0, 20, 21]) > assert set([0, 10, 11, 12]) == set([0, 20, 21])
E assert {0, 10, 11, 12} == {0, 20, 21} E AssertionError: assert {0, 10, 11, 12} == {0, 20, 21}
E Extra items in the left set: E Extra items in the left set:
E 10 E 10
E 11 E 11
E 12 E 12
E Extra items in the right set: E Extra items in the right set:
E 20 E 20
E 21 E 21...
E Use -v to get the full diff E
E ...Full output truncated (2 lines hidden), use '-vv' to show
failure_demo.py:73: AssertionError failure_demo.py:73: AssertionError
_______ TestSpecialisedExplanations.test_eq_longer_list ________ _______ TestSpecialisedExplanations.test_eq_longer_list ________
@ -245,8 +243,9 @@ get on the terminal - we are working on that)::
E which E which
E includes foo E includes foo
E ? +++ E ? +++
E and a E and a...
E tail E
E ...Full output truncated (2 lines hidden), use '-vv' to show
failure_demo.py:83: AssertionError failure_demo.py:83: AssertionError
_______ TestSpecialisedExplanations.test_not_in_text_single ________ _______ TestSpecialisedExplanations.test_not_in_text_single ________
@ -359,7 +358,7 @@ get on the terminal - we are working on that)::
> int(s) > int(s)
E ValueError: invalid literal for int() with base 10: 'qwe' E ValueError: invalid literal for int() with base 10: 'qwe'
<0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python.py:1207>:1: ValueError <0-codegen $PYTHON_PREFIX/lib/python3.5/site-packages/_pytest/python.py:1219>:1: ValueError
_______ TestRaises.test_raises_doesnt ________ _______ TestRaises.test_raises_doesnt ________
self = <failure_demo.TestRaises object at 0xdeadbeef> self = <failure_demo.TestRaises object at 0xdeadbeef>
@ -550,7 +549,7 @@ get on the terminal - we are working on that)::
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef> self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
def test_single_line(self): def test_single_line(self):
class A: class A(object):
a = 1 a = 1
b = 2 b = 2
> assert A.a == b, "A.a appears not to be b" > assert A.a == b, "A.a appears not to be b"
@ -564,7 +563,7 @@ get on the terminal - we are working on that)::
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef> self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
def test_multiline(self): def test_multiline(self):
class A: class A(object):
a = 1 a = 1
b = 2 b = 2
> assert A.a == b, "A.a appears not to be b\n" \ > assert A.a == b, "A.a appears not to be b\n" \
@ -581,7 +580,7 @@ get on the terminal - we are working on that)::
self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef> self = <failure_demo.TestCustomAssertMsg object at 0xdeadbeef>
def test_custom_repr(self): def test_custom_repr(self):
class JSON: class JSON(object):
a = 1 a = 1
def __repr__(self): def __repr__(self):
return "This is JSON\n{\n 'foo': 'bar'\n}" return "This is JSON\n{\n 'foo': 'bar'\n}"

View File

@ -113,7 +113,7 @@ directory with the above conftest.py::
$ pytest $ pytest
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items collected 0 items
@ -164,7 +164,7 @@ and when running it will see a skipped "slow" test::
$ pytest -rs # "-rs" means report details on the little 's' $ pytest -rs # "-rs" means report details on the little 's'
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items collected 2 items
@ -178,7 +178,7 @@ Or run it including the ``slow`` marked test::
$ pytest --runslow $ pytest --runslow
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items collected 2 items
@ -303,7 +303,7 @@ which will add the string to the test header accordingly::
$ pytest $ pytest
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
project deps: mylib-1.1 project deps: mylib-1.1
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items collected 0 items
@ -328,7 +328,7 @@ which will add info only when run with "--v"::
$ pytest -v $ pytest -v
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 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 cachedir: .cache
info1: did you know that ... info1: did you know that ...
did you? did you?
@ -341,7 +341,7 @@ and nothing when run plainly::
$ pytest $ pytest
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 0 items collected 0 items
@ -375,7 +375,7 @@ Now we can profile which test functions execute the slowest::
$ pytest --durations=3 $ pytest --durations=3
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 3 items collected 3 items
@ -426,7 +426,7 @@ tests in a class. Here is a test module example:
import pytest import pytest
@pytest.mark.incremental @pytest.mark.incremental
class TestUserHandling: class TestUserHandling(object):
def test_login(self): def test_login(self):
pass pass
def test_modification(self): def test_modification(self):
@ -441,7 +441,7 @@ If we run this::
$ pytest -rx $ pytest -rx
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 4 items collected 4 items
@ -484,7 +484,7 @@ Here is an example for making a ``db`` fixture available in a directory:
# content of a/conftest.py # content of a/conftest.py
import pytest import pytest
class DB: class DB(object):
pass pass
@pytest.fixture(scope="session") @pytest.fixture(scope="session")
@ -520,7 +520,7 @@ We can run this::
$ pytest $ pytest
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 7 items collected 7 items
@ -628,7 +628,7 @@ and run them::
$ pytest test_module.py $ pytest test_module.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items collected 2 items
@ -722,7 +722,7 @@ and run it::
$ pytest -s test_module.py $ pytest -s test_module.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 3 items collected 3 items

View File

@ -28,7 +28,7 @@ will be called ahead of running any tests::
# content of test_module.py # content of test_module.py
class TestHello: class TestHello(object):
@classmethod @classmethod
def callme(cls): def callme(cls):
print ("callme called!") print ("callme called!")
@ -39,7 +39,7 @@ will be called ahead of running any tests::
def test_method2(self): def test_method2(self):
print ("test_method1 called") print ("test_method1 called")
class TestOther: class TestOther(object):
@classmethod @classmethod
def callme(cls): def callme(cls):
print ("callme other called") print ("callme other called")

View File

@ -70,7 +70,7 @@ marked ``smtp`` fixture function. Running the test looks like this::
$ pytest test_smtpsimple.py $ pytest test_smtpsimple.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items collected 1 items
@ -188,7 +188,7 @@ inspect what is going on and can now run the tests::
$ pytest test_module.py $ pytest test_module.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items collected 2 items
@ -523,7 +523,7 @@ Running the above tests results in the following test IDs being used::
$ pytest --collect-only $ pytest --collect-only
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 10 items collected 10 items
<Module 'test_anothersmtp.py'> <Module 'test_anothersmtp.py'>
@ -558,7 +558,7 @@ and instantiate an object ``app`` where we stick the already defined
import pytest import pytest
class App: class App(object):
def __init__(self, smtp): def __init__(self, smtp):
self.smtp = smtp self.smtp = smtp
@ -574,7 +574,7 @@ Here we declare an ``app`` fixture which receives the previously defined
$ pytest -v test_appsetup.py $ pytest -v test_appsetup.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 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 cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 2 items collecting ... collected 2 items
@ -643,7 +643,7 @@ Let's run the tests in verbose mode and with looking at the print-output::
$ pytest -v -s test_module.py $ pytest -v -s test_module.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 -- $PYTHON_PREFIX/bin/python3.5 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 cachedir: .cache
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collecting ... collected 8 items collecting ... collected 8 items
@ -729,7 +729,7 @@ and declare its use in a test module via a ``usefixtures`` marker::
import pytest import pytest
@pytest.mark.usefixtures("cleandir") @pytest.mark.usefixtures("cleandir")
class TestDirectoryInit: class TestDirectoryInit(object):
def test_cwd_starts_empty(self): def test_cwd_starts_empty(self):
assert os.listdir(os.getcwd()) == [] assert os.listdir(os.getcwd()) == []
with open("myfile", "w") as f: with open("myfile", "w") as f:
@ -792,7 +792,7 @@ self-contained implementation of this idea::
import pytest import pytest
class DB: class DB(object):
def __init__(self): def __init__(self):
self.intransaction = [] self.intransaction = []
def begin(self, name): def begin(self, name):
@ -804,7 +804,7 @@ self-contained implementation of this idea::
def db(): def db():
return DB() return DB()
class TestClass: class TestClass(object):
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def transact(self, request, db): def transact(self, request, db):
db.begin(request.function.__name__) db.begin(request.function.__name__)
@ -862,7 +862,7 @@ into a conftest.py file **without** using ``autouse``::
and then e.g. have a TestClass using it by declaring the need:: and then e.g. have a TestClass using it by declaring the need::
@pytest.mark.usefixtures("transact") @pytest.mark.usefixtures("transact")
class TestClass: class TestClass(object):
def test_method1(self): def test_method1(self):
... ...

View File

@ -24,7 +24,7 @@ resources. Here is a basic example how we could implement
a per-session Database object:: a per-session Database object::
# content of conftest.py # content of conftest.py
class Database: class Database(object):
def __init__(self): def __init__(self):
print ("database instance created") print ("database instance created")
def destroy(self): def destroy(self):

View File

@ -1,7 +1,7 @@
import textwrap import textwrap
import inspect import inspect
class Writer: class Writer(object):
def __init__(self, clsname): def __init__(self, clsname):
self.clsname = clsname self.clsname = clsname

View File

@ -26,7 +26,7 @@ Installation::
To check your installation has installed the correct version:: To check your installation has installed the correct version::
$ pytest --version $ pytest --version
This is pytest version 3.0.7, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py This is pytest version 3.x.y, imported from $PYTHON_PREFIX/lib/python3.5/site-packages/pytest.py
.. _`simpletest`: .. _`simpletest`:
@ -46,20 +46,20 @@ That's it. You can execute the test function now::
$ pytest $ pytest
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items collected 1 items
test_sample.py F test_sample.py F
======= FAILURES ======== ======= FAILURES ========
_______ test_answer ________ _______ test_answer ________
def test_answer(): def test_answer():
> assert func(3) == 5 > assert func(3) == 5
E assert 4 == 5 E assert 4 == 5
E + where 4 = func(3) E + where 4 = func(3)
test_sample.py:5: AssertionError test_sample.py:5: AssertionError
======= 1 failed in 0.12 seconds ======== ======= 1 failed in 0.12 seconds ========
@ -111,7 +111,7 @@ to group tests logically, in classes and modules. Let's write a class
containing two tests:: containing two tests::
# content of test_class.py # content of test_class.py
class TestClass: class TestClass(object):
def test_one(self): def test_one(self):
x = "this" x = "this"
assert 'h' in x assert 'h' in x
@ -128,15 +128,15 @@ run the module by passing its filename::
.F .F
======= FAILURES ======== ======= FAILURES ========
_______ TestClass.test_two ________ _______ TestClass.test_two ________
self = <test_class.TestClass object at 0xdeadbeef> self = <test_class.TestClass object at 0xdeadbeef>
def test_two(self): def test_two(self):
x = "hello" x = "hello"
> assert hasattr(x, 'check') > assert hasattr(x, 'check')
E AssertionError: assert False E AssertionError: assert False
E + where False = hasattr('hello', 'check') E + where False = hasattr('hello', 'check')
test_class.py:8: AssertionError test_class.py:8: AssertionError
1 failed, 1 passed in 0.12 seconds 1 failed, 1 passed in 0.12 seconds
@ -165,14 +165,14 @@ before performing the test function call. Let's just run it::
F F
======= FAILURES ======== ======= FAILURES ========
_______ test_needsfiles ________ _______ test_needsfiles ________
tmpdir = local('PYTEST_TMPDIR/test_needsfiles0') tmpdir = local('PYTEST_TMPDIR/test_needsfiles0')
def test_needsfiles(tmpdir): def test_needsfiles(tmpdir):
print (tmpdir) print (tmpdir)
> assert 0 > assert 0
E assert 0 E assert 0
test_tmpdir.py:3: AssertionError test_tmpdir.py:3: AssertionError
--------------------------- Captured stdout call --------------------------- --------------------------- Captured stdout call ---------------------------
PYTEST_TMPDIR/test_needsfiles0 PYTEST_TMPDIR/test_needsfiles0

View File

@ -25,7 +25,7 @@ To execute it::
$ pytest $ pytest
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items collected 1 items

View File

@ -54,7 +54,7 @@ them in turn::
$ pytest $ pytest
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 3 items collected 3 items
@ -93,16 +93,37 @@ for example with the builtin ``mark.xfail``::
@pytest.mark.parametrize("test_input,expected", [ @pytest.mark.parametrize("test_input,expected", [
("3+5", 8), ("3+5", 8),
("2+4", 6), ("2+4", 6),
pytest.mark.xfail(("6*9", 42)), pytest.param("6*9", 42,
marks=pytest.mark.xfail),
]) ])
def test_eval(test_input, expected): def test_eval(test_input, expected):
assert eval(test_input) == expected assert eval(test_input) == expected
.. note::
prior to version 3.1 the supported mechanism for marking values
used the syntax::
import pytest
@pytest.mark.parametrize("test_input,expected", [
("3+5", 8),
("2+4", 6),
pytest.mark.xfail(("6*9", 42),),
])
def test_eval(test_input, expected):
assert eval(test_input) == expected
This was an initial hack to support the feature but soon was demonstrated to be incomplete,
broken for passing functions or applying multiple marks with the same name but different parameters.
The old syntax will be removed in pytest-4.0.
Let's run this:: Let's run this::
$ pytest $ pytest
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 3 items collected 3 items

View File

@ -1,139 +1,3 @@
.. _`asserting warnings`: :orphan:
.. _assertwarnings: This page has been moved, please see :ref:`assertwarnings`.
Asserting Warnings
=====================================================
.. _`asserting warnings with the warns function`:
.. _warns:
Asserting warnings with the warns function
-----------------------------------------------
.. versionadded:: 2.8
You can check that code raises a particular warning using ``pytest.warns``,
which works in a similar manner to :ref:`raises <assertraises>`::
import warnings
import pytest
def test_warning():
with pytest.warns(UserWarning):
warnings.warn("my warning", UserWarning)
The test will fail if the warning in question is not raised.
You can also call ``pytest.warns`` on a function or code string::
pytest.warns(expected_warning, func, *args, **kwargs)
pytest.warns(expected_warning, "func(*args, **kwargs)")
The function also returns a list of all raised warnings (as
``warnings.WarningMessage`` objects), which you can query for
additional information::
with pytest.warns(RuntimeWarning) as record:
warnings.warn("another warning", RuntimeWarning)
# check that only one warning was raised
assert len(record) == 1
# check that the message matches
assert record[0].message.args[0] == "another warning"
Alternatively, you can examine raised warnings in detail using the
:ref:`recwarn <recwarn>` fixture (see below).
.. note::
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
differently; see :ref:`ensuring_function_triggers`.
.. _`recording warnings`:
.. _recwarn:
Recording warnings
------------------------
You can record raised warnings either using ``pytest.warns`` or with
the ``recwarn`` fixture.
To record with ``pytest.warns`` without asserting anything about the warnings,
pass ``None`` as the expected warning type::
with pytest.warns(None) as record:
warnings.warn("user", UserWarning)
warnings.warn("runtime", RuntimeWarning)
assert len(record) == 2
assert str(record[0].message) == "user"
assert str(record[1].message) == "runtime"
The ``recwarn`` fixture will record warnings for the whole function::
import warnings
def test_hello(recwarn):
warnings.warn("hello", UserWarning)
assert len(recwarn) == 1
w = recwarn.pop(UserWarning)
assert issubclass(w.category, UserWarning)
assert str(w.message) == "hello"
assert w.filename
assert w.lineno
Both ``recwarn`` and ``pytest.warns`` return the same interface for recorded
warnings: a WarningsRecorder instance. To view the recorded warnings, you can
iterate over this instance, call ``len`` on it to get the number of recorded
warnings, or index into it to get a particular recorded warning. It also
provides these methods:
.. autoclass:: _pytest.recwarn.WarningsRecorder()
:members:
Each recorded warning has the attributes ``message``, ``category``,
``filename``, ``lineno``, ``file``, and ``line``. The ``category`` is the
class of the warning. The ``message`` is the warning itself; calling
``str(message)`` will return the actual message of the warning.
.. note::
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
differently; see :ref:`ensuring_function_triggers`.
.. _`ensuring a function triggers a deprecation warning`:
.. _ensuring_function_triggers:
Ensuring a function triggers a deprecation warning
-------------------------------------------------------
You can also call a global helper for checking
that a certain function call triggers a ``DeprecationWarning`` or
``PendingDeprecationWarning``::
import pytest
def test_global():
pytest.deprecated_call(myfunction, 17)
By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be
caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide
them. If you wish to record them in your own code, use the
command ``warnings.simplefilter('always')``::
import warnings
import pytest
def test_deprecation(recwarn):
warnings.simplefilter('always')
warnings.warn("deprecated", DeprecationWarning)
assert len(recwarn) == 1
assert recwarn.pop(DeprecationWarning)
You can also use it as a contextmanager::
def test_global():
with pytest.deprecated_call():
myobject.deprecated_method()

View File

@ -98,7 +98,7 @@ You can use the ``skipif`` decorator (and any other marker) on classes::
@pytest.mark.skipif(sys.platform == 'win32', @pytest.mark.skipif(sys.platform == 'win32',
reason="does not run on windows") reason="does not run on windows")
class TestPosixCalls: class TestPosixCalls(object):
def test_function(self): def test_function(self):
"will not be setup or run under 'win32' platform" "will not be setup or run under 'win32' platform"
@ -224,7 +224,7 @@ Running it with the report-on-xfail option gives this output::
example $ pytest -rx xfail_demo.py example $ pytest -rx xfail_demo.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR/example, inifile: rootdir: $REGENDOC_TMPDIR/example, inifile:
collected 7 items collected 7 items

View File

@ -110,7 +110,7 @@ If you want to disable a complete test class you
can set the class-level attribute ``disabled``. can set the class-level attribute ``disabled``.
For example, in order to avoid running some tests on Win32:: For example, in order to avoid running some tests on Win32::
class TestPosixOnly: class TestPosixOnly(object):
disabled = sys.platform == 'win32' disabled = sys.platform == 'win32'
def test_xxx(self): def test_xxx(self):

View File

@ -29,7 +29,7 @@ Running this would result in a passed test except for the last
$ pytest test_tmpdir.py $ pytest test_tmpdir.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 1 items collected 1 items

View File

@ -71,7 +71,7 @@ it from a unittest-style test::
@pytest.fixture(scope="class") @pytest.fixture(scope="class")
def db_class(request): def db_class(request):
class DummyDB: class DummyDB(object):
pass pass
# set a class attribute on the invoking test context # set a class attribute on the invoking test context
request.cls.db = DummyDB() request.cls.db = DummyDB()
@ -108,7 +108,7 @@ the ``self.db`` values in the traceback::
$ pytest test_unittest_db.py $ pytest test_unittest_db.py
======= test session starts ======== ======= test session starts ========
platform linux -- Python 3.5.2, pytest-3.0.7, py-1.4.32, pluggy-0.4.0 platform linux -- Python 3.x.y, pytest-3.x.y, py-1.x.y, pluggy-0.x.y
rootdir: $REGENDOC_TMPDIR, inifile: rootdir: $REGENDOC_TMPDIR, inifile:
collected 2 items collected 2 items
@ -171,7 +171,8 @@ creation of a per-test temporary directory::
tmpdir.join("samplefile.ini").write("# testdata") tmpdir.join("samplefile.ini").write("# testdata")
def test_method(self): def test_method(self):
s = open("samplefile.ini").read() with open("samplefile.ini") as f:
s = f.read()
assert "testdata" in s assert "testdata" in s
Due to the ``autouse`` flag the ``initdir`` fixture function will be Due to the ``autouse`` flag the ``initdir`` fixture function will be

View File

@ -177,6 +177,15 @@ integration servers, use this invocation::
to create an XML file at ``path``. to create an XML file at ``path``.
.. versionadded:: 3.1
To set the name of the root test suite xml item, you can configure the ``junit_suite_name`` option in your config file:
.. code-block:: ini
[pytest]
junit_suite_name = my_suite
record_xml_property record_xml_property
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -238,7 +247,7 @@ to all testcases you can use ``LogXML.add_global_properties``
def start_and_prepare_env(): def start_and_prepare_env():
pass pass
class TestMe: class TestMe(object):
def test_foo(self): def test_foo(self):
assert True assert True
@ -326,7 +335,7 @@ You can specify additional plugins to ``pytest.main``::
# content of myinvoke.py # content of myinvoke.py
import pytest import pytest
class MyPlugin: class MyPlugin(object):
def pytest_sessionfinish(self): def pytest_sessionfinish(self):
print("*** test run reporting finishing") print("*** test run reporting finishing")

224
doc/en/warnings.rst Normal file
View File

@ -0,0 +1,224 @@
.. _`warnings`:
Warnings Capture
================
.. versionadded:: 3.1
Starting from version ``3.1``, pytest now automatically catches all 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)
return 1
def test_one():
assert deprecated_function() == 1
Running pytest now produces this output::
$ pytest test_show_warnings.py
======= 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
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)
-- Docs: http://doc.pytest.org/en/latest/warnings.html
======= 1 passed, 1 warnings in 0.12 seconds ========
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
F
======= FAILURES ========
_______ test_one ________
def test_one():
> assert deprecated_function() == 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()
test_show_warnings.py:4: DeprecationWarning
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
all other warnings into errors.
.. code-block:: ini
[pytest]
filterwarnings =
error
ignore::DeprecationWarning
When a warning matches more than one option in the list, the action for the last matching option
is performed.
Both ``-W`` command-line option and ``filterwarnings`` ini option are based on Python's own
`-W option`_ and `warnings.simplefilter`_, so please refer to those sections in the Python
documentation for other examples and advanced usage.
*Credits go to Florian Schulze for the reference implementation in the* `pytest-warnings`_
*plugin.*
.. _`-W option`: https://docs.python.org/3/using/cmdline.html?highlight=#cmdoption-W
.. _warnings.simplefilter: https://docs.python.org/3/library/warnings.html#warnings.simplefilter
.. _`pytest-warnings`: https://github.com/fschulze/pytest-warnings
.. _`asserting warnings`:
.. _assertwarnings:
.. _`asserting warnings with the warns function`:
.. _warns:
Asserting warnings with the warns function
-----------------------------------------------
.. versionadded:: 2.8
You can check that code raises a particular warning using ``pytest.warns``,
which works in a similar manner to :ref:`raises <assertraises>`::
import warnings
import pytest
def test_warning():
with pytest.warns(UserWarning):
warnings.warn("my warning", UserWarning)
The test will fail if the warning in question is not raised.
You can also call ``pytest.warns`` on a function or code string::
pytest.warns(expected_warning, func, *args, **kwargs)
pytest.warns(expected_warning, "func(*args, **kwargs)")
The function also returns a list of all raised warnings (as
``warnings.WarningMessage`` objects), which you can query for
additional information::
with pytest.warns(RuntimeWarning) as record:
warnings.warn("another warning", RuntimeWarning)
# check that only one warning was raised
assert len(record) == 1
# check that the message matches
assert record[0].message.args[0] == "another warning"
Alternatively, you can examine raised warnings in detail using the
:ref:`recwarn <recwarn>` fixture (see below).
.. note::
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
differently; see :ref:`ensuring_function_triggers`.
.. _`recording warnings`:
.. _recwarn:
Recording warnings
------------------------
You can record raised warnings either using ``pytest.warns`` or with
the ``recwarn`` fixture.
To record with ``pytest.warns`` without asserting anything about the warnings,
pass ``None`` as the expected warning type::
with pytest.warns(None) as record:
warnings.warn("user", UserWarning)
warnings.warn("runtime", RuntimeWarning)
assert len(record) == 2
assert str(record[0].message) == "user"
assert str(record[1].message) == "runtime"
The ``recwarn`` fixture will record warnings for the whole function::
import warnings
def test_hello(recwarn):
warnings.warn("hello", UserWarning)
assert len(recwarn) == 1
w = recwarn.pop(UserWarning)
assert issubclass(w.category, UserWarning)
assert str(w.message) == "hello"
assert w.filename
assert w.lineno
Both ``recwarn`` and ``pytest.warns`` return the same interface for recorded
warnings: a WarningsRecorder instance. To view the recorded warnings, you can
iterate over this instance, call ``len`` on it to get the number of recorded
warnings, or index into it to get a particular recorded warning. It also
provides these methods:
.. autoclass:: _pytest.recwarn.WarningsRecorder()
:members:
Each recorded warning has the attributes ``message``, ``category``,
``filename``, ``lineno``, ``file``, and ``line``. The ``category`` is the
class of the warning. The ``message`` is the warning itself; calling
``str(message)`` will return the actual message of the warning.
.. note::
:class:`RecordedWarning` was changed from a plain class to a namedtuple in pytest 3.1
.. note::
``DeprecationWarning`` and ``PendingDeprecationWarning`` are treated
differently; see :ref:`ensuring_function_triggers`.
.. _`ensuring a function triggers a deprecation warning`:
.. _ensuring_function_triggers:
Ensuring a function triggers a deprecation warning
-------------------------------------------------------
You can also call a global helper for checking
that a certain function call triggers a ``DeprecationWarning`` or
``PendingDeprecationWarning``::
import pytest
def test_global():
pytest.deprecated_call(myfunction, 17)
By default, ``DeprecationWarning`` and ``PendingDeprecationWarning`` will not be
caught when using ``pytest.warns`` or ``recwarn`` because default Python warnings filters hide
them. If you wish to record them in your own code, use the
command ``warnings.simplefilter('always')``::
import warnings
import pytest
def test_deprecation(recwarn):
warnings.simplefilter('always')
warnings.warn("deprecated", DeprecationWarning)
assert len(recwarn) == 1
assert recwarn.pop(DeprecationWarning)
You can also use it as a contextmanager::
def test_global():
with pytest.deprecated_call():
myobject.deprecated_method()

View File

@ -517,7 +517,6 @@ Initialization, command line and configuration hooks
.. autofunction:: pytest_load_initial_conftests .. autofunction:: pytest_load_initial_conftests
.. autofunction:: pytest_cmdline_preparse .. autofunction:: pytest_cmdline_preparse
.. autofunction:: pytest_cmdline_parse .. autofunction:: pytest_cmdline_parse
.. autofunction:: pytest_namespace
.. autofunction:: pytest_addoption .. autofunction:: pytest_addoption
.. autofunction:: pytest_cmdline_main .. autofunction:: pytest_cmdline_main
.. autofunction:: pytest_configure .. autofunction:: pytest_configure

View File

@ -2,19 +2,7 @@
""" """
pytest: unit and functional testing with Python. pytest: unit and functional testing with Python.
""" """
__all__ = [
'main',
'UsageError',
'cmdline',
'hookspec',
'hookimpl',
'__version__',
]
if __name__ == '__main__': # if run as a script or by 'python -m pytest'
# we trigger the below "else" condition by the following import
import pytest
raise SystemExit(pytest.main())
# else we are imported # else we are imported
@ -22,7 +10,69 @@ from _pytest.config import (
main, UsageError, _preloadplugins, cmdline, main, UsageError, _preloadplugins, cmdline,
hookspec, hookimpl hookspec, hookimpl
) )
from _pytest.fixtures import fixture, yield_fixture
from _pytest.assertion import register_assert_rewrite
from _pytest.freeze_support import freeze_includes
from _pytest import __version__ from _pytest import __version__
from _pytest.debugging import pytestPDB as __pytestPDB
from _pytest.recwarn import warns, deprecated_call
from _pytest.runner import fail, skip, importorskip, exit
from _pytest.mark import MARK_GEN as mark, param
from _pytest.skipping import xfail
from _pytest.main import Item, Collector, File, Session
from _pytest.fixtures import fillfixtures as _fillfuncargs
from _pytest.python import (
raises, approx,
Module, Class, Instance, Function, Generator,
)
_preloadplugins() # to populate pytest.* namespace so help(pytest) works set_trace = __pytestPDB.set_trace
__all__ = [
'main',
'UsageError',
'cmdline',
'hookspec',
'hookimpl',
'__version__',
'register_assert_rewrite',
'freeze_includes',
'set_trace',
'warns',
'deprecated_call',
'fixture',
'yield_fixture',
'fail',
'skip',
'xfail',
'importorskip',
'exit',
'mark',
'param',
'approx',
'_fillfuncargs',
'Item',
'File',
'Collector',
'Session',
'Module',
'Class',
'Instance',
'Function',
'Generator',
'raises',
]
if __name__ == '__main__':
# if run as a script or by 'python -m pytest'
# we trigger the below "else" condition by the following import
import pytest
raise SystemExit(pytest.main())
else:
from _pytest.compat import _setup_collect_fakemodule
_preloadplugins() # to populate pytest.* namespace so help(pytest) works
_setup_collect_fakemodule()

View File

@ -18,4 +18,3 @@ if os.path.isdir('.git'):
else: else:
print('No .git directory found, skipping checking the manifest file') print('No .git directory found, skipping checking the manifest file')
sys.exit(0) sys.exit(0)

View File

@ -9,6 +9,10 @@ upload-dir = doc/en/build/html
[bdist_wheel] [bdist_wheel]
universal = 1 universal = 1
[check-manifest]
ignore =
_pytest/_version.py
[metadata] [metadata]
license_file = LICENSE license_file = LICENSE

View File

@ -1,32 +1,27 @@
import os, sys import os
import sys
import setuptools import setuptools
import pkg_resources import pkg_resources
from setuptools import setup, Command from setuptools import setup, Command
classifiers = ['Development Status :: 6 - Mature', classifiers = [
'Intended Audience :: Developers', 'Development Status :: 6 - Mature',
'License :: OSI Approved :: MIT License', 'Intended Audience :: Developers',
'Operating System :: POSIX', 'License :: OSI Approved :: MIT License',
'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX',
'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows',
'Topic :: Software Development :: Testing', 'Operating System :: MacOS :: MacOS X',
'Topic :: Software Development :: Libraries', 'Topic :: Software Development :: Testing',
'Topic :: Utilities'] + [ 'Topic :: Software Development :: Libraries',
('Programming Language :: Python :: %s' % x) for x in 'Topic :: Utilities',
'2 2.6 2.7 3 3.3 3.4 3.5 3.6'.split()] ] + [
('Programming Language :: Python :: %s' % x)
for x in '2 2.6 2.7 3 3.3 3.4 3.5 3.6'.split()
]
with open('README.rst') as fd: with open('README.rst') as fd:
long_description = fd.read() long_description = fd.read()
def get_version():
p = os.path.join(os.path.dirname(
os.path.abspath(__file__)), "_pytest", "__init__.py")
with open(p) as f:
for line in f.readlines():
if "__version__" in line:
return line.strip().split("=")[-1].strip(" '")
raise ValueError("could not read version")
def has_environment_marker_support(): def has_environment_marker_support():
""" """
@ -63,7 +58,9 @@ def main():
name='pytest', name='pytest',
description='pytest: simple powerful testing with Python', description='pytest: simple powerful testing with Python',
long_description=long_description, long_description=long_description,
version=get_version(), use_scm_version={
'write_to': '_pytest/_version.py',
},
url='http://pytest.org', url='http://pytest.org',
license='MIT license', license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'], platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
@ -74,6 +71,7 @@ def main():
keywords="test unittest", keywords="test unittest",
cmdclass={'test': PyTest}, cmdclass={'test': PyTest},
# the following should be enabled for release # the following should be enabled for release
setup_requires=['setuptools-scm'],
install_requires=install_requires, install_requires=install_requires,
extras_require=extras_require, extras_require=extras_require,
packages=['_pytest', '_pytest.assertion', '_pytest._code', '_pytest.vendored_packages'], packages=['_pytest', '_pytest.assertion', '_pytest._code', '_pytest.vendored_packages'],

9
tasks/__init__.py Normal file
View File

@ -0,0 +1,9 @@
"""
Invoke tasks to help with pytest development and release process.
"""
import invoke
from . import generate
ns = invoke.Collection(generate)

112
tasks/generate.py Normal file
View File

@ -0,0 +1,112 @@
import os
from pathlib import Path
from subprocess import check_output, check_call
import invoke
@invoke.task(help={
'version': 'version being released',
})
def announce(ctx, version):
"""Generates a new release announcement entry in the docs."""
# Get our list of authors
stdout = check_output(["git", "describe", "--abbrev=0", '--tags'])
stdout = stdout.decode('utf-8')
last_version = stdout.strip()
stdout = check_output(["git", "log", "{}..HEAD".format(last_version), "--format=%aN"])
stdout = stdout.decode('utf-8')
contributors = set(stdout.splitlines())
template_name = 'release.minor.rst' if version.endswith('.0') else 'release.patch.rst'
template_text = Path(__file__).parent.joinpath(template_name).read_text(encoding='UTF-8')
contributors_text = '\n'.join('* {}'.format(name) for name in sorted(contributors)) + '\n'
text = template_text.format(version=version, contributors=contributors_text)
target = Path(__file__).parent.joinpath('../doc/en/announce/release-{}.rst'.format(version))
target.write_text(text, encoding='UTF-8')
print("[generate.announce] Generated {}".format(target.name))
# Update index with the new release entry
index_path = Path(__file__).parent.joinpath('../doc/en/announce/index.rst')
lines = index_path.read_text(encoding='UTF-8').splitlines()
indent = ' '
for index, line in enumerate(lines):
if line.startswith('{}release-'.format(indent)):
new_line = indent + target.stem
if line != new_line:
lines.insert(index, new_line)
index_path.write_text('\n'.join(lines) + '\n', encoding='UTF-8')
print("[generate.announce] Updated {}".format(index_path.name))
else:
print("[generate.announce] Skip {} (already contains release)".format(index_path.name))
break
check_call(['git', 'add', str(target)])
@invoke.task()
def regen(ctx):
"""Call regendoc tool to update examples and pytest output in the docs."""
print("[generate.regen] Updating docs")
check_call(['tox', '-e', 'regen'])
@invoke.task()
def make_tag(ctx, version):
"""Create a new (local) tag for the release, only if the repository is clean."""
from git import Repo
repo = Repo('.')
if repo.is_dirty():
print('Current repository is dirty. Please commit any changes and try again.')
raise invoke.Exit(code=2)
tag_names = [x.name for x in repo.tags]
if version in tag_names:
print("[generate.make_tag] Delete existing tag {}".format(version))
repo.delete_tag(version)
print("[generate.make_tag] Create tag {}".format(version))
repo.create_tag(version)
@invoke.task()
def devpi_upload(ctx, version, user, password=None):
"""Creates and uploads a package to devpi for testing."""
if password:
print("[generate.devpi_upload] devpi login {}".format(user))
check_call(['devpi', 'login', user, '--password', password])
check_call(['devpi', 'use', 'https://devpi.net/{}/dev'.format(user)])
env = os.environ.copy()
env['SETUPTOOLS_SCM_PRETEND_VERSION'] = version
check_call(['devpi', 'upload', '--formats', 'sdist,bdist_wheel'], env=env)
print("[generate.devpi_upload] package uploaded")
@invoke.task(help={
'version': 'version being released',
'user': 'name of the user on devpi to stage the generated package',
'password': 'user password on devpi to stage the generated package '
'(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."""
announce(ctx, version)
regen(ctx)
msg = 'Preparing release version {}'.format(version)
check_call(['git', 'commit', '-a', '-m', msg])
make_tag(ctx, version)
devpi_upload(ctx, version=version, user=user, password=password)
print()
print('[generate.pre_release] Please push your branch and open a PR.')

27
tasks/release.minor.rst Normal file
View File

@ -0,0 +1,27 @@
pytest-{version}
=======================================
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
to take a look at the CHANGELOG:
http://doc.pytest.org/en/latest/changelog.html
For complete documentation, please visit:
http://docs.pytest.org
As usual, you can upgrade from pypi via:
pip install -U pytest
Thanks to all who contributed to this release, among them:
{contributors}
Happy testing,
The Pytest Development Team

17
tasks/release.patch.rst Normal file
View File

@ -0,0 +1,17 @@
pytest-{version}
=======================================
pytest {version} 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:
{contributors}
Happy testing,
The pytest Development Team

3
tasks/requirements.txt Normal file
View File

@ -0,0 +1,3 @@
invoke
tox
gitpython

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
import os import os
import sys import sys
@ -8,7 +9,7 @@ import pytest
from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR from _pytest.main import EXIT_NOTESTSCOLLECTED, EXIT_USAGEERROR
class TestGeneralUsage: class TestGeneralUsage(object):
def test_config_error(self, testdir): def test_config_error(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
def pytest_configure(config): def pytest_configure(config):
@ -338,10 +339,16 @@ class TestGeneralUsage:
"*ERROR*test_b.py::b*", "*ERROR*test_b.py::b*",
]) ])
@pytest.mark.usefixtures('recwarn')
def test_namespace_import_doesnt_confuse_import_hook(self, testdir): def test_namespace_import_doesnt_confuse_import_hook(self, testdir):
# Ref #383. Python 3.3's namespace package messed with our import hooks """
# Importing a module that didn't exist, even if the ImportError was Ref #383. Python 3.3's namespace package messed with our import hooks
# gracefully handled, would make our test crash. Importing a module that didn't exist, even if the ImportError was
gracefully handled, would make our test crash.
Use recwarn here to silence this warning in Python 2.6 and 2.7:
ImportWarning: Not importing directory '...\not_a_package': missing __init__.py
"""
testdir.mkdir('not_a_package') testdir.mkdir('not_a_package')
p = testdir.makepyfile(""" p = testdir.makepyfile("""
try: try:
@ -410,7 +417,7 @@ class TestGeneralUsage:
]) ])
class TestInvocationVariants: class TestInvocationVariants(object):
def test_earlyinit(self, testdir): def test_earlyinit(self, testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
import pytest import pytest
@ -502,7 +509,7 @@ class TestInvocationVariants:
out, err = capsys.readouterr() out, err = capsys.readouterr()
def test_invoke_plugin_api(self, testdir, capsys): def test_invoke_plugin_api(self, testdir, capsys):
class MyPlugin: class MyPlugin(object):
def pytest_addoption(self, parser): def pytest_addoption(self, parser):
parser.addoption("--myopt") parser.addoption("--myopt")
@ -523,6 +530,7 @@ class TestInvocationVariants:
]) ])
def test_cmdline_python_package(self, testdir, monkeypatch): def test_cmdline_python_package(self, testdir, monkeypatch):
import warnings
monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False) monkeypatch.delenv('PYTHONDONTWRITEBYTECODE', False)
path = testdir.mkpydir("tpkg") path = testdir.mkpydir("tpkg")
path.join("test_hello.py").write("def test_hello(): pass") path.join("test_hello.py").write("def test_hello(): pass")
@ -545,7 +553,11 @@ class TestInvocationVariants:
return what return what
empty_package = testdir.mkpydir("empty_package") empty_package = testdir.mkpydir("empty_package")
monkeypatch.setenv('PYTHONPATH', join_pythonpath(empty_package)) monkeypatch.setenv('PYTHONPATH', join_pythonpath(empty_package))
result = testdir.runpytest("--pyargs", ".") # the path which is not a package raises a warning on pypy;
# no idea why only pypy and not normal python warn about it here
with warnings.catch_warnings():
warnings.simplefilter('ignore', ImportWarning)
result = testdir.runpytest("--pyargs", ".")
assert result.ret == 0 assert result.ret == 0
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
"*2 passed*" "*2 passed*"
@ -670,7 +682,7 @@ class TestInvocationVariants:
assert request.config.pluginmanager.hasplugin('python') assert request.config.pluginmanager.hasplugin('python')
class TestDurations: class TestDurations(object):
source = """ source = """
import time import time
frag = 0.002 frag = 0.002
@ -741,7 +753,7 @@ class TestDurations:
assert result.ret == 0 assert result.ret == 0
class TestDurationWithFixture: class TestDurationWithFixture(object):
source = """ source = """
import time import time
frag = 0.001 frag = 0.001
@ -781,3 +793,45 @@ def test_zipimport_hook(testdir, tmpdir):
assert result.ret == 0 assert result.ret == 0
result.stderr.fnmatch_lines(['*not found*foo*']) result.stderr.fnmatch_lines(['*not found*foo*'])
assert 'INTERNALERROR>' not in result.stdout.str() assert 'INTERNALERROR>' not in result.stdout.str()
def test_import_plugin_unicode_name(testdir):
testdir.makepyfile(
myplugin='',
)
testdir.makepyfile("""
def test(): pass
""")
testdir.makeconftest("""
pytest_plugins = [u'myplugin']
""")
r = testdir.runpytest()
assert r.ret == 0
def test_deferred_hook_checking(testdir):
"""
Check hooks as late as possible (#1821).
"""
testdir.syspathinsert()
testdir.makepyfile(**{
'plugin.py': """
class Hooks:
def pytest_my_hook(self, config):
pass
def pytest_configure(config):
config.pluginmanager.add_hookspecs(Hooks)
""",
'conftest.py': """
pytest_plugins = ['plugin']
def pytest_my_hook(config):
return 40
""",
'test_foo.py': """
def test(request):
assert request.config.hook.pytest_my_hook(config=request.config) == [40]
"""
})
result = testdir.runpytest()
result.stdout.fnmatch_lines(['* 1 passed *'])

View File

@ -1,3 +1,4 @@
from __future__ import absolute_import, division, print_function
import sys import sys
import _pytest._code import _pytest._code
@ -20,7 +21,7 @@ def test_code_gives_back_name_for_not_existing_file():
assert code.fullsource is None assert code.fullsource is None
def test_code_with_class(): def test_code_with_class():
class A: class A(object):
pass pass
pytest.raises(TypeError, "_pytest._code.Code(A)") pytest.raises(TypeError, "_pytest._code.Code(A)")
@ -136,7 +137,7 @@ def test_frame_getargs():
('z', {'c': 'd'})] ('z', {'c': 'd'})]
class TestExceptionInfo: class TestExceptionInfo(object):
def test_bad_getsource(self): def test_bad_getsource(self):
try: try:
@ -147,7 +148,7 @@ class TestExceptionInfo:
assert exci.getrepr() assert exci.getrepr()
class TestTracebackEntry: class TestTracebackEntry(object):
def test_getsource(self): def test_getsource(self):
try: try:

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
import operator import operator
import _pytest import _pytest
@ -25,7 +26,7 @@ else:
import pytest import pytest
pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3])) pytest_version_info = tuple(map(int, pytest.__version__.split(".")[:3]))
class TWMock: class TWMock(object):
WRITE = object() WRITE = object()
def __init__(self): def __init__(self):
@ -89,7 +90,7 @@ def h():
g() g()
# #
class TestTraceback_f_g_h: class TestTraceback_f_g_h(object):
def setup_method(self, method): def setup_method(self, method):
try: try:
h() h()
@ -369,7 +370,7 @@ def test_codepath_Queue_example():
def test_match_succeeds(): def test_match_succeeds():
with pytest.raises(ZeroDivisionError) as excinfo: with pytest.raises(ZeroDivisionError) as excinfo:
0 / 0 0 // 0
excinfo.match(r'.*zero.*') excinfo.match(r'.*zero.*')
def test_match_raises_error(testdir): def test_match_raises_error(testdir):
@ -386,7 +387,7 @@ def test_match_raises_error(testdir):
"*AssertionError*Pattern*[123]*not found*", "*AssertionError*Pattern*[123]*not found*",
]) ])
class TestFormattedExcinfo: class TestFormattedExcinfo(object):
@pytest.fixture @pytest.fixture
def importasmod(self, request): def importasmod(self, request):
@ -472,7 +473,7 @@ raise ValueError()
pr = FormattedExcinfo() pr = FormattedExcinfo()
class FakeCode(object): class FakeCode(object):
class raw: class raw(object):
co_filename = '?' co_filename = '?'
path = '?' path = '?'

View File

@ -1,6 +1,7 @@
# flake8: noqa # flake8: noqa
# disable flake check on this file because some constructs are strange # disable flake check on this file because some constructs are strange
# or redundant on purpose and can't be disable on a line-by-line basis # or redundant on purpose and can't be disable on a line-by-line basis
from __future__ import absolute_import, division, print_function
import sys import sys
import _pytest._code import _pytest._code
@ -49,7 +50,7 @@ def test_source_from_function():
assert str(source).startswith('def test_source_str_function():') assert str(source).startswith('def test_source_str_function():')
def test_source_from_method(): def test_source_from_method():
class TestClass: class TestClass(object):
def test_method(self): def test_method(self):
pass pass
source = _pytest._code.Source(TestClass().test_method) source = _pytest._code.Source(TestClass().test_method)
@ -119,7 +120,7 @@ def test_isparseable():
assert not Source(" \nif 1:\npass").isparseable() assert not Source(" \nif 1:\npass").isparseable()
assert not Source(chr(0)).isparseable() assert not Source(chr(0)).isparseable()
class TestAccesses: class TestAccesses(object):
source = Source("""\ source = Source("""\
def f(x): def f(x):
pass pass
@ -143,7 +144,7 @@ class TestAccesses:
l = [x for x in self.source] l = [x for x in self.source]
assert len(l) == 4 assert len(l) == 4
class TestSourceParsingAndCompiling: class TestSourceParsingAndCompiling(object):
source = Source("""\ source = Source("""\
def f(x): def f(x):
assert (x == assert (x ==
@ -307,7 +308,7 @@ class TestSourceParsingAndCompiling:
pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode='eval') pytest.raises(SyntaxError, _pytest._code.compile, "lambda a,a: 0", mode='eval')
def test_getstartingblock_singleline(): def test_getstartingblock_singleline():
class A: class A(object):
def __init__(self, *args): def __init__(self, *args):
frame = sys._getframe(1) frame = sys._getframe(1)
self.source = _pytest._code.Frame(frame).statement self.source = _pytest._code.Frame(frame).statement
@ -318,7 +319,7 @@ def test_getstartingblock_singleline():
assert len(l) == 1 assert len(l) == 1
def test_getstartingblock_multiline(): def test_getstartingblock_multiline():
class A: class A(object):
def __init__(self, *args): def __init__(self, *args):
frame = sys._getframe(1) frame = sys._getframe(1)
self.source = _pytest._code.Frame(frame).statement self.source = _pytest._code.Frame(frame).statement
@ -461,16 +462,16 @@ def test_getfslineno():
assert lineno == A_lineno assert lineno == A_lineno
assert getfslineno(3) == ("", -1) assert getfslineno(3) == ("", -1)
class B: class B(object):
pass pass
B.__name__ = "B2" B.__name__ = "B2"
assert getfslineno(B)[1] == -1 assert getfslineno(B)[1] == -1
def test_code_of_object_instance_with_call(): def test_code_of_object_instance_with_call():
class A: class A(object):
pass pass
pytest.raises(TypeError, lambda: _pytest._code.Source(A())) pytest.raises(TypeError, lambda: _pytest._code.Source(A()))
class WithCall: class WithCall(object):
def __call__(self): def __call__(self):
pass pass
@ -559,7 +560,7 @@ x = 3
""") """)
assert str(source) == "raise ValueError(\n 23\n)" assert str(source) == "raise ValueError(\n 23\n)"
class TestTry: class TestTry(object):
pytestmark = astonly pytestmark = astonly
source = """\ source = """\
try: try:
@ -586,7 +587,7 @@ else:
source = getstatement(5, self.source) source = getstatement(5, self.source)
assert str(source) == " raise KeyError()" assert str(source) == " raise KeyError()"
class TestTryFinally: class TestTryFinally(object):
source = """\ source = """\
try: try:
raise ValueError raise ValueError
@ -604,7 +605,7 @@ finally:
class TestIf: class TestIf(object):
pytestmark = astonly pytestmark = astonly
source = """\ source = """\
if 1: if 1:

View File

@ -1,3 +1,4 @@
from __future__ import absolute_import, division, print_function
import pytest import pytest
@ -26,7 +27,7 @@ def test_funcarg_prefix_deprecation(testdir):
""") """)
result = testdir.runpytest('-ra') result = testdir.runpytest('-ra')
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
('WC1 None pytest_funcarg__value: ' ('*pytest_funcarg__value: '
'declaring fixtures using "pytest_funcarg__" prefix is deprecated ' 'declaring fixtures using "pytest_funcarg__" prefix is deprecated '
'and scheduled to be removed in pytest 4.0. ' 'and scheduled to be removed in pytest 4.0. '
'Please remove the prefix and use the @pytest.fixture decorator instead.'), 'Please remove the prefix and use the @pytest.fixture decorator instead.'),
@ -48,7 +49,7 @@ def test_str_args_deprecated(tmpdir, testdir):
from _pytest.main import EXIT_NOTESTSCOLLECTED from _pytest.main import EXIT_NOTESTSCOLLECTED
warnings = [] warnings = []
class Collect: class Collect(object):
def pytest_logwarning(self, message): def pytest_logwarning(self, message):
warnings.append(message) warnings.append(message)

View File

@ -20,7 +20,7 @@ class MyDocTestRunner(doctest.DocTestRunner):
example.source.strip(), got.strip(), example.want.strip())) example.source.strip(), got.strip(), example.want.strip()))
class TestApprox: class TestApprox(object):
def test_repr_string(self): def test_repr_string(self):
# for some reason in Python 2.6 it is not displaying the tolerance representation correctly # for some reason in Python 2.6 it is not displaying the tolerance representation correctly

View File

@ -12,7 +12,7 @@ from _pytest.main import (
) )
class TestModule: class TestModule(object):
def test_failing_import(self, testdir): def test_failing_import(self, testdir):
modcol = testdir.getmodulecol("import alksdjalskdjalkjals") modcol = testdir.getmodulecol("import alksdjalskdjalkjals")
pytest.raises(Collector.CollectError, modcol.collect) pytest.raises(Collector.CollectError, modcol.collect)
@ -104,7 +104,6 @@ class TestModule:
else: else:
assert name not in stdout assert name not in stdout
def test_show_traceback_import_error_unicode(self, testdir): def test_show_traceback_import_error_unicode(self, testdir):
"""Check test modules collected which raise ImportError with unicode messages """Check test modules collected which raise ImportError with unicode messages
are handled properly (#2336). are handled properly (#2336).
@ -122,17 +121,17 @@ class TestModule:
assert result.ret == 2 assert result.ret == 2
class TestClass: class TestClass(object):
def test_class_with_init_warning(self, testdir): def test_class_with_init_warning(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
class TestClass1: class TestClass1(object):
def __init__(self): def __init__(self):
pass pass
""") """)
result = testdir.runpytest("-rw") result = testdir.runpytest("-rw")
result.stdout.fnmatch_lines_random(""" result.stdout.fnmatch_lines([
WC1*test_class_with_init_warning.py*__init__* "*cannot collect test class 'TestClass1' because it has a __init__ constructor",
""") ])
def test_class_subclassobject(self, testdir): def test_class_subclassobject(self, testdir):
testdir.getmodulecol(""" testdir.getmodulecol("""
@ -146,7 +145,7 @@ class TestClass:
def test_setup_teardown_class_as_classmethod(self, testdir): def test_setup_teardown_class_as_classmethod(self, testdir):
testdir.makepyfile(test_mod1=""" testdir.makepyfile(test_mod1="""
class TestClassMethod: class TestClassMethod(object):
@classmethod @classmethod
def setup_class(cls): def setup_class(cls):
pass pass
@ -194,7 +193,7 @@ class TestClass:
assert result.ret == EXIT_NOTESTSCOLLECTED assert result.ret == EXIT_NOTESTSCOLLECTED
class TestGenerator: class TestGenerator(object):
def test_generative_functions(self, testdir): def test_generative_functions(self, testdir):
modcol = testdir.getmodulecol(""" modcol = testdir.getmodulecol("""
def func1(arg, arg2): def func1(arg, arg2):
@ -219,7 +218,7 @@ class TestGenerator:
modcol = testdir.getmodulecol(""" modcol = testdir.getmodulecol("""
def func1(arg, arg2): def func1(arg, arg2):
assert arg == arg2 assert arg == arg2
class TestGenMethods: class TestGenMethods(object):
def test_gen(self): def test_gen(self):
yield func1, 17, 3*5 yield func1, 17, 3*5
yield func1, 42, 6*7 yield func1, 42, 6*7
@ -273,7 +272,7 @@ class TestGenerator:
modcol = testdir.getmodulecol(""" modcol = testdir.getmodulecol("""
def func1(arg, arg2): def func1(arg, arg2):
assert arg == arg2 assert arg == arg2
class TestGenMethods: class TestGenMethods(object):
def test_gen(self): def test_gen(self):
yield "m1", func1, 17, 3*5 yield "m1", func1, 17, 3*5
yield "m2", func1, 42, 6*7 yield "m2", func1, 42, 6*7
@ -291,6 +290,7 @@ class TestGenerator:
def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir): def test_order_of_execution_generator_same_codeline(self, testdir, tmpdir):
o = testdir.makepyfile(""" o = testdir.makepyfile("""
from __future__ import print_function
def test_generative_order_of_execution(): def test_generative_order_of_execution():
import py, pytest import py, pytest
test_list = [] test_list = []
@ -300,8 +300,8 @@ class TestGenerator:
test_list.append(item) test_list.append(item)
def assert_order_of_execution(): def assert_order_of_execution():
py.builtin.print_('expected order', expected_list) print('expected order', expected_list)
py.builtin.print_('but got ', test_list) print('but got ', test_list)
assert test_list == expected_list assert test_list == expected_list
for i in expected_list: for i in expected_list:
@ -315,6 +315,7 @@ class TestGenerator:
def test_order_of_execution_generator_different_codeline(self, testdir): def test_order_of_execution_generator_different_codeline(self, testdir):
o = testdir.makepyfile(""" o = testdir.makepyfile("""
from __future__ import print_function
def test_generative_tests_different_codeline(): def test_generative_tests_different_codeline():
import py, pytest import py, pytest
test_list = [] test_list = []
@ -330,8 +331,8 @@ class TestGenerator:
test_list.append(0) test_list.append(0)
def assert_order_of_execution(): def assert_order_of_execution():
py.builtin.print_('expected order', expected_list) print('expected order', expected_list)
py.builtin.print_('but got ', test_list) print('but got ', test_list)
assert test_list == expected_list assert test_list == expected_list
yield list_append_0 yield list_append_0
@ -353,7 +354,7 @@ class TestGenerator:
# has been used during collection. # has been used during collection.
o = testdir.makepyfile(""" o = testdir.makepyfile("""
setuplist = [] setuplist = []
class TestClass: class TestClass(object):
def setup_method(self, func): def setup_method(self, func):
#print "setup_method", self, func #print "setup_method", self, func
setuplist.append(self) setuplist.append(self)
@ -387,7 +388,7 @@ class TestGenerator:
assert not skipped and not failed assert not skipped and not failed
class TestFunction: class TestFunction(object):
def test_getmodulecollector(self, testdir): def test_getmodulecollector(self, testdir):
item = testdir.getitem("def test_func(): pass") item = testdir.getitem("def test_func(): pass")
modcol = item.getparent(pytest.Module) modcol = item.getparent(pytest.Module)
@ -396,7 +397,7 @@ class TestFunction:
def test_function_as_object_instance_ignored(self, testdir): def test_function_as_object_instance_ignored(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
class A: class A(object):
def __call__(self, tmpdir): def __call__(self, tmpdir):
0/0 0/0
@ -447,7 +448,7 @@ class TestFunction:
def test_issue213_parametrize_value_no_equal(self, testdir): def test_issue213_parametrize_value_no_equal(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
class A: class A(object):
def __eq__(self, other): def __eq__(self, other):
raise ValueError("not possible") raise ValueError("not possible")
@pytest.mark.parametrize('arg', [A()]) @pytest.mark.parametrize('arg', [A()])
@ -578,11 +579,11 @@ class TestFunction:
item = testdir.getitem("def test_func(): raise ValueError") item = testdir.getitem("def test_func(): raise ValueError")
config = item.config config = item.config
class MyPlugin1: class MyPlugin1(object):
def pytest_pyfunc_call(self, pyfuncitem): def pytest_pyfunc_call(self, pyfuncitem):
raise ValueError raise ValueError
class MyPlugin2: class MyPlugin2(object):
def pytest_pyfunc_call(self, pyfuncitem): def pytest_pyfunc_call(self, pyfuncitem):
return True return True
@ -710,7 +711,7 @@ class TestFunction:
assert [x.originalname for x in items] == ['test_func', 'test_func'] assert [x.originalname for x in items] == ['test_func', 'test_func']
class TestSorting: class TestSorting(object):
def test_check_equality(self, testdir): def test_check_equality(self, testdir):
modcol = testdir.getmodulecol(""" modcol = testdir.getmodulecol("""
def test_pass(): pass def test_pass(): pass
@ -760,7 +761,7 @@ class TestSorting:
assert [item.name for item in colitems] == ['test_b', 'test_a'] assert [item.name for item in colitems] == ['test_b', 'test_a']
class TestConftestCustomization: class TestConftestCustomization(object):
def test_pytest_pycollect_module(self, testdir): def test_pytest_pycollect_module(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
import pytest import pytest
@ -902,7 +903,7 @@ def test_modulecol_roundtrip(testdir):
assert modcol.name == newcol.name assert modcol.name == newcol.name
class TestTracebackCutting: class TestTracebackCutting(object):
def test_skip_simple(self): def test_skip_simple(self):
excinfo = pytest.raises(pytest.skip.Exception, 'pytest.skip("xxx")') excinfo = pytest.raises(pytest.skip.Exception, 'pytest.skip("xxx")')
assert excinfo.traceback[-1].frame.code.name == "skip" assert excinfo.traceback[-1].frame.code.name == "skip"
@ -1028,7 +1029,7 @@ class TestTracebackCutting:
assert filter_traceback(tb[-1]) assert filter_traceback(tb[-1])
class TestReportInfo: class TestReportInfo(object):
def test_itemreport_reportinfo(self, testdir, linecomp): def test_itemreport_reportinfo(self, testdir, linecomp):
testdir.makeconftest(""" testdir.makeconftest("""
import pytest import pytest
@ -1053,7 +1054,7 @@ class TestReportInfo:
def test_class_reportinfo(self, testdir): def test_class_reportinfo(self, testdir):
modcol = testdir.getmodulecol(""" modcol = testdir.getmodulecol("""
# lineno 0 # lineno 0
class TestClass: class TestClass(object):
def test_hello(self): pass def test_hello(self): pass
""") """)
classcol = testdir.collect_by_name(modcol, "TestClass") classcol = testdir.collect_by_name(modcol, "TestClass")
@ -1088,7 +1089,7 @@ class TestReportInfo:
def check(x): def check(x):
pass pass
yield check, 3 yield check, 3
class TestClass: class TestClass(object):
def test_method(self): def test_method(self):
pass pass
""" """
@ -1097,7 +1098,7 @@ class TestReportInfo:
# https://github.com/pytest-dev/pytest/issues/1204 # https://github.com/pytest-dev/pytest/issues/1204
modcol = testdir.getmodulecol(""" modcol = testdir.getmodulecol("""
# lineno 0 # lineno 0
class TestClass: class TestClass(object):
def __getattr__(self, name): def __getattr__(self, name):
return "this is not an int" return "this is not an int"
@ -1119,7 +1120,7 @@ def test_customized_python_discovery(testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
def check_simple(): def check_simple():
pass pass
class CheckMyApp: class CheckMyApp(object):
def check_meth(self): def check_meth(self):
pass pass
""") """)
@ -1194,7 +1195,7 @@ def test_customize_through_attributes(testdir):
return MyClass(name, parent=collector) return MyClass(name, parent=collector)
""") """)
testdir.makepyfile(""" testdir.makepyfile("""
class MyTestClass: class MyTestClass(object):
def test_hello(self): def test_hello(self):
pass pass
""") """)
@ -1208,11 +1209,11 @@ def test_customize_through_attributes(testdir):
def test_unorderable_types(testdir): def test_unorderable_types(testdir):
testdir.makepyfile(""" testdir.makepyfile("""
class TestJoinEmpty: class TestJoinEmpty(object):
pass pass
def make_test(): def make_test():
class Test: class Test(object):
pass pass
Test.__name__ = "TestFoo" Test.__name__ = "TestFoo"
return Test return Test
@ -1286,8 +1287,8 @@ def test_dont_collect_non_function_callable(testdir):
result = testdir.runpytest('-rw') result = testdir.runpytest('-rw')
result.stdout.fnmatch_lines([ result.stdout.fnmatch_lines([
'*collected 1 item*', '*collected 1 item*',
'WC2 *', "*cannot collect 'test_a' because it is not a function*",
'*1 passed, 1 pytest-warnings in *', '*1 passed, 1 warnings in *',
]) ])

View File

@ -20,7 +20,7 @@ def test_getfuncargnames():
def h(arg1, arg2, arg3="hello"): pass def h(arg1, arg2, arg3="hello"): pass
assert fixtures.getfuncargnames(h) == ('arg1', 'arg2') assert fixtures.getfuncargnames(h) == ('arg1', 'arg2')
class A: class A(object):
def f(self, arg1, arg2="hello"): def f(self, arg1, arg2="hello"):
pass pass
@ -28,7 +28,7 @@ def test_getfuncargnames():
if sys.version_info < (3,0): if sys.version_info < (3,0):
assert fixtures.getfuncargnames(A.f) == ('arg1',) assert fixtures.getfuncargnames(A.f) == ('arg1',)
class TestFillFixtures: class TestFillFixtures(object):
def test_fillfuncargs_exposed(self): def test_fillfuncargs_exposed(self):
# used by oejskit, kept for compatibility # used by oejskit, kept for compatibility
assert pytest._fillfuncargs == fixtures.fillfixtures assert pytest._fillfuncargs == fixtures.fillfixtures
@ -79,7 +79,7 @@ class TestFillFixtures:
def something(request): def something(request):
return request.function.__name__ return request.function.__name__
class TestClass: class TestClass(object):
def test_method(self, something): def test_method(self, something):
assert something == "test_method" assert something == "test_method"
def test_func(something): def test_func(something):
@ -91,7 +91,7 @@ class TestFillFixtures:
def test_funcarg_lookup_classlevel(self, testdir): def test_funcarg_lookup_classlevel(self, testdir):
p = testdir.makepyfile(""" p = testdir.makepyfile("""
import pytest import pytest
class TestClass: class TestClass(object):
@pytest.fixture @pytest.fixture
def something(self, request): def something(self, request):
@ -134,7 +134,7 @@ class TestFillFixtures:
def spam(): def spam():
return 'spam' return 'spam'
class TestSpam: class TestSpam(object):
@pytest.fixture @pytest.fixture
def spam(self, spam): def spam(self, spam):
@ -463,7 +463,7 @@ class TestFillFixtures:
assert result.ret == 0 assert result.ret == 0
class TestRequestBasic: class TestRequestBasic(object):
def test_request_attributes(self, testdir): def test_request_attributes(self, testdir):
item = testdir.getitem(""" item = testdir.getitem("""
import pytest import pytest
@ -484,7 +484,7 @@ class TestRequestBasic:
def test_request_attributes_method(self, testdir): def test_request_attributes_method(self, testdir):
item, = testdir.getitems(""" item, = testdir.getitems("""
import pytest import pytest
class TestB: class TestB(object):
@pytest.fixture @pytest.fixture
def something(self, request): def something(self, request):
@ -502,7 +502,7 @@ class TestRequestBasic:
@pytest.fixture @pytest.fixture
def something(request): def something(request):
pass pass
class TestClass: class TestClass(object):
def test_method(self, something): def test_method(self, something):
pass pass
""") """)
@ -545,22 +545,33 @@ class TestRequestBasic:
return l.pop() return l.pop()
def test_func(something): pass def test_func(something): pass
""") """)
import contextlib
if getfixmethod == 'getfuncargvalue':
warning_expectation = pytest.warns(DeprecationWarning)
else:
# see #1830 for a cleaner way to accomplish this
@contextlib.contextmanager
def expecting_no_warning(): yield
warning_expectation = expecting_no_warning()
req = item._request req = item._request
fixture_fetcher = getattr(req, getfixmethod) with warning_expectation:
pytest.raises(FixtureLookupError, fixture_fetcher, "notexists") fixture_fetcher = getattr(req, getfixmethod)
val = fixture_fetcher("something") with pytest.raises(FixtureLookupError):
assert val == 1 fixture_fetcher("notexists")
val = fixture_fetcher("something") val = fixture_fetcher("something")
assert val == 1 assert val == 1
val2 = fixture_fetcher("other") val = fixture_fetcher("something")
assert val2 == 2 assert val == 1
val2 = fixture_fetcher("other") # see about caching val2 = fixture_fetcher("other")
assert val2 == 2 assert val2 == 2
pytest._fillfuncargs(item) val2 = fixture_fetcher("other") # see about caching
assert item.funcargs["something"] == 1 assert val2 == 2
assert len(get_public_names(item.funcargs)) == 2 pytest._fillfuncargs(item)
assert "request" in item.funcargs assert item.funcargs["something"] == 1
#assert item.funcargs == {'something': 1, "other": 2} assert len(get_public_names(item.funcargs)) == 2
assert "request" in item.funcargs
def test_request_addfinalizer(self, testdir): def test_request_addfinalizer(self, testdir):
item = testdir.getitem(""" item = testdir.getitem("""
@ -704,7 +715,7 @@ class TestRequestBasic:
def test_func(): def test_func():
pass pass
class TestClass: class TestClass(object):
@pytest.fixture(scope="class", autouse=True) @pytest.fixture(scope="class", autouse=True)
def setup_class(self): def setup_class(self):
l.append("class") l.append("class")
@ -771,7 +782,7 @@ class TestRequestBasic:
reprec = testdir.inline_run() reprec = testdir.inline_run()
reprec.assertoutcome(passed=2) reprec.assertoutcome(passed=2)
class TestRequestMarking: class TestRequestMarking(object):
def test_applymarker(self, testdir): def test_applymarker(self, testdir):
item1,item2 = testdir.getitems(""" item1,item2 = testdir.getitems("""
import pytest import pytest
@ -779,7 +790,7 @@ class TestRequestMarking:
@pytest.fixture @pytest.fixture
def something(request): def something(request):
pass pass
class TestClass: class TestClass(object):
def test_func1(self, something): def test_func1(self, something):
pass pass
def test_func2(self, something): def test_func2(self, something):
@ -831,7 +842,7 @@ class TestRequestMarking:
reprec = testdir.inline_run() reprec = testdir.inline_run()
reprec.assertoutcome(passed=2) reprec.assertoutcome(passed=2)
class TestRequestCachedSetup: class TestRequestCachedSetup(object):
def test_request_cachedsetup_defaultmodule(self, testdir): def test_request_cachedsetup_defaultmodule(self, testdir):
reprec = testdir.inline_runsource(""" reprec = testdir.inline_runsource("""
mysetup = ["hello",].pop mysetup = ["hello",].pop
@ -844,7 +855,7 @@ class TestRequestCachedSetup:
def test_func1(something): def test_func1(something):
assert something == "hello" assert something == "hello"
class TestClass: class TestClass(object):
def test_func1a(self, something): def test_func1a(self, something):
assert something == "hello" assert something == "hello"
""") """)
@ -862,7 +873,7 @@ class TestRequestCachedSetup:
assert something == "hello3" assert something == "hello3"
def test_func2(something): def test_func2(something):
assert something == "hello2" assert something == "hello2"
class TestClass: class TestClass(object):
def test_func1a(self, something): def test_func1a(self, something):
assert something == "hello" assert something == "hello"
def test_func2b(self, something): def test_func2b(self, something):
@ -996,7 +1007,7 @@ class TestRequestCachedSetup:
"*ZeroDivisionError*", "*ZeroDivisionError*",
]) ])
class TestFixtureUsages: class TestFixtureUsages(object):
def test_noargfixturedec(self, testdir): def test_noargfixturedec(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@ -1138,7 +1149,7 @@ class TestFixtureUsages:
def test_factory_setup_as_classes_fails(self, testdir): def test_factory_setup_as_classes_fails(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
class arg1: class arg1(object):
def __init__(self, request): def __init__(self, request):
self.x = 1 self.x = 1
arg1 = pytest.fixture()(arg1) arg1 = pytest.fixture()(arg1)
@ -1172,7 +1183,7 @@ class TestFixtureUsages:
request.cls.hello = "world" request.cls.hello = "world"
l.append(1) l.append(1)
class TestClass: class TestClass(object):
def test_one(self): def test_one(self):
assert self.hello == "world" assert self.hello == "world"
assert len(l) == 1 assert len(l) == 1
@ -1198,7 +1209,7 @@ class TestFixtureUsages:
""") """)
testdir.makepyfile(""" testdir.makepyfile("""
class TestClass: class TestClass(object):
def test_one(self): def test_one(self):
assert self.hello == "world" assert self.hello == "world"
def test_two(self): def test_two(self):
@ -1217,7 +1228,7 @@ class TestFixtureUsages:
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
class TestClass: class TestClass(object):
@pytest.fixture @pytest.fixture
def setup1(self, request): def setup1(self, request):
assert self == request.instance assert self == request.instance
@ -1256,7 +1267,7 @@ class TestFixtureUsages:
assert l == [1,2, 10,20] assert l == [1,2, 10,20]
class TestFixtureManagerParseFactories: class TestFixtureManagerParseFactories(object):
@pytest.fixture @pytest.fixture
def testdir(self, request): def testdir(self, request):
@ -1280,7 +1291,7 @@ class TestFixtureManagerParseFactories:
def test_parsefactories_evil_objects_issue214(self, testdir): def test_parsefactories_evil_objects_issue214(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
class A: class A(object):
def __call__(self): def __call__(self):
pass pass
def __getattr__(self, name): def __getattr__(self, name):
@ -1311,7 +1322,7 @@ class TestFixtureManagerParseFactories:
@pytest.fixture @pytest.fixture
def hello(request): def hello(request):
return "module" return "module"
class TestClass: class TestClass(object):
@pytest.fixture @pytest.fixture
def hello(self, request): def hello(self, request):
return "class" return "class"
@ -1360,7 +1371,7 @@ class TestFixtureManagerParseFactories:
reprec.assertoutcome(passed=2) reprec.assertoutcome(passed=2)
class TestAutouseDiscovery: class TestAutouseDiscovery(object):
@pytest.fixture @pytest.fixture
def testdir(self, testdir): def testdir(self, testdir):
@ -1402,14 +1413,14 @@ class TestAutouseDiscovery:
def test_two_classes_separated_autouse(self, testdir): def test_two_classes_separated_autouse(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
class TestA: class TestA(object):
l = [] l = []
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def setup1(self): def setup1(self):
self.l.append(1) self.l.append(1)
def test_setup1(self): def test_setup1(self):
assert self.l == [1] assert self.l == [1]
class TestB: class TestB(object):
l = [] l = []
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def setup2(self): def setup2(self):
@ -1423,7 +1434,7 @@ class TestAutouseDiscovery:
def test_setup_at_classlevel(self, testdir): def test_setup_at_classlevel(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
class TestClass: class TestClass(object):
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def permethod(self, request): def permethod(self, request):
request.instance.funcname = request.function.__name__ request.instance.funcname = request.function.__name__
@ -1505,13 +1516,13 @@ class TestAutouseDiscovery:
def test_x(): def test_x():
assert l == ["module"] assert l == ["module"]
class TestA: class TestA(object):
@pytest.fixture(autouse=True) @pytest.fixture(autouse=True)
def append2(self): def append2(self):
l.append("A") l.append("A")
def test_hello(self): def test_hello(self):
assert l == ["module", "module", "A"], l assert l == ["module", "module", "A"], l
class TestA2: class TestA2(object):
def test_world(self): def test_world(self):
assert l == ["module", "module", "A", "module"], l assert l == ["module", "module", "A", "module"], l
""") """)
@ -1519,7 +1530,7 @@ class TestAutouseDiscovery:
reprec.assertoutcome(passed=3) reprec.assertoutcome(passed=3)
class TestAutouseManagement: class TestAutouseManagement(object):
def test_autouse_conftest_mid_directory(self, testdir): def test_autouse_conftest_mid_directory(self, testdir):
pkgdir = testdir.mkpydir("xyz123") pkgdir = testdir.mkpydir("xyz123")
pkgdir.join("conftest.py").write(_pytest._code.Source(""" pkgdir.join("conftest.py").write(_pytest._code.Source("""
@ -1654,10 +1665,10 @@ class TestAutouseManagement:
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
class TestClass: class TestClass(object):
def test_1(self): def test_1(self):
pass pass
class TestClass2: class TestClass2(object):
def test_2(self): def test_2(self):
pass pass
""") """)
@ -1682,7 +1693,7 @@ class TestAutouseManagement:
def mappend(): def mappend():
l.append(1) l.append(1)
class TestHallo: class TestHallo(object):
def test_method(self): def test_method(self):
assert l == [1,3,2] assert l == [1,3,2]
""") """)
@ -1696,7 +1707,7 @@ class TestAutouseManagement:
def pytest_generate_tests(metafunc): def pytest_generate_tests(metafunc):
if metafunc.cls is not None: if metafunc.cls is not None:
metafunc.parametrize("item", [1,2], scope="class") metafunc.parametrize("item", [1,2], scope="class")
class TestClass: class TestClass(object):
@pytest.fixture(scope="class", autouse=True) @pytest.fixture(scope="class", autouse=True)
def addteardown(self, item, request): def addteardown(self, item, request):
l.append("setup-%d" % item) l.append("setup-%d" % item)
@ -1756,7 +1767,7 @@ class TestAutouseManagement:
reprec.assertoutcome(passed=2) reprec.assertoutcome(passed=2)
class TestFixtureMarker: class TestFixtureMarker(object):
def test_parametrize(self, testdir): def test_parametrize(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@ -1822,7 +1833,7 @@ class TestFixtureMarker:
def test_2(arg): def test_2(arg):
assert arg == 1 assert arg == 1
assert len(l) == 1 assert len(l) == 1
class TestClass: class TestClass(object):
def test3(self, arg): def test3(self, arg):
assert arg == 1 assert arg == 1
assert len(l) == 1 assert len(l) == 1
@ -1916,7 +1927,7 @@ class TestFixtureMarker:
def test_2(arg): def test_2(arg):
assert arg == 1 assert arg == 1
assert len(l) == 1 assert len(l) == 1
class TestClass: class TestClass(object):
def test3(self, arg): def test3(self, arg):
assert arg == 1 assert arg == 1
assert len(l) == 1 assert len(l) == 1
@ -2135,12 +2146,12 @@ class TestFixtureMarker:
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
class TestClass2: class TestClass2(object):
def test_1(self): def test_1(self):
pass pass
def test_2(self): def test_2(self):
pass pass
class TestClass: class TestClass(object):
def test_3(self): def test_3(self):
pass pass
""") """)
@ -2213,7 +2224,7 @@ class TestFixtureMarker:
l = [] l = []
class TestClass: class TestClass(object):
@classmethod @classmethod
@pytest.fixture(scope="class", autouse=True) @pytest.fixture(scope="class", autouse=True)
def setup1(self, request, param1): def setup1(self, request, param1):
@ -2273,7 +2284,7 @@ class TestFixtureMarker:
testpath = testdir.makepyfile(""" testpath = testdir.makepyfile("""
import pytest import pytest
class Box: class Box(object):
value = 0 value = 0
@pytest.fixture(scope='class') @pytest.fixture(scope='class')
@ -2284,11 +2295,11 @@ class TestFixtureMarker:
def test_a(a): def test_a(a):
assert a == 1 assert a == 1
class Test1: class Test1(object):
def test_b(self, a): def test_b(self, a):
assert a == 2 assert a == 2
class Test2: class Test2(object):
def test_c(self, a): def test_c(self, a):
assert a == 3""") assert a == 3""")
reprec = testdir.inline_run(testpath) reprec = testdir.inline_run(testpath)
@ -2402,11 +2413,11 @@ class TestFixtureMarker:
request.addfinalizer(lambda: l.append("fin %s" % request.param)) request.addfinalizer(lambda: l.append("fin %s" % request.param))
return request.param return request.param
class TestGreetings: class TestGreetings(object):
def test_hello(self, human): def test_hello(self, human):
l.append("test_hello") l.append("test_hello")
class TestMetrics: class TestMetrics(object):
def test_name(self, human): def test_name(self, human):
l.append("test_name") l.append("test_name")
@ -2499,7 +2510,7 @@ class TestFixtureMarker:
'*test_foo*beta*']) '*test_foo*beta*'])
class TestRequestScopeAccess: class TestRequestScopeAccess(object):
pytestmark = pytest.mark.parametrize(("scope", "ok", "error"),[ pytestmark = pytest.mark.parametrize(("scope", "ok", "error"),[
["session", "", "fspath class function module"], ["session", "", "fspath class function module"],
["module", "module fspath", "cls function"], ["module", "module fspath", "cls function"],
@ -2543,7 +2554,7 @@ class TestRequestScopeAccess:
reprec = testdir.inline_run() reprec = testdir.inline_run()
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
class TestErrors: class TestErrors(object):
def test_subfactory_missing_funcarg(self, testdir): def test_subfactory_missing_funcarg(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
import pytest import pytest
@ -2607,7 +2618,7 @@ class TestErrors:
"*1 error*", "*1 error*",
]) ])
class TestShowFixtures: class TestShowFixtures(object):
def test_funcarg_compat(self, testdir): def test_funcarg_compat(self, testdir):
config = testdir.parseconfigure("--funcargs") config = testdir.parseconfigure("--funcargs")
assert config.option.showfixtures assert config.option.showfixtures
@ -2770,7 +2781,7 @@ class TestShowFixtures:
@pytest.mark.parametrize('flavor', ['fixture', 'yield_fixture']) @pytest.mark.parametrize('flavor', ['fixture', 'yield_fixture'])
class TestContextManagerFixtureFuncs: class TestContextManagerFixtureFuncs(object):
def test_simple(self, testdir, flavor): def test_simple(self, testdir, flavor):
testdir.makepyfile(""" testdir.makepyfile("""
@ -2877,7 +2888,7 @@ class TestContextManagerFixtureFuncs:
result = testdir.runpytest("-s") result = testdir.runpytest("-s")
result.stdout.fnmatch_lines("*mew*") result.stdout.fnmatch_lines("*mew*")
class TestParameterizedSubRequest: class TestParameterizedSubRequest(object):
def test_call_from_fixture(self, testdir): def test_call_from_fixture(self, testdir):
testfile = testdir.makepyfile(""" testfile = testdir.makepyfile("""
import pytest import pytest

View File

@ -3,7 +3,7 @@ from _pytest import python
from _pytest import runner from _pytest import runner
class TestOEJSKITSpecials: class TestOEJSKITSpecials(object):
def test_funcarg_non_pycollectobj(self, testdir): # rough jstests usage def test_funcarg_non_pycollectobj(self, testdir): # rough jstests usage
testdir.makeconftest(""" testdir.makeconftest("""
import pytest import pytest
@ -19,7 +19,7 @@ class TestOEJSKITSpecials:
@pytest.fixture @pytest.fixture
def arg1(request): def arg1(request):
return 42 return 42
class MyClass: class MyClass(object):
pass pass
""") """)
# this hook finds funcarg factories # this hook finds funcarg factories
@ -48,7 +48,7 @@ class TestOEJSKITSpecials:
@pytest.fixture @pytest.fixture
def arg1(request): def arg1(request):
return 42 return 42
class MyClass: class MyClass(object):
pass pass
""") """)
# this hook finds funcarg factories # this hook finds funcarg factories
@ -76,7 +76,7 @@ def test_wrapped_getfslineno():
fs2, lineno2 = python.getfslineno(wrap) fs2, lineno2 = python.getfslineno(wrap)
assert lineno > lineno2, "getfslineno does not unwrap correctly" assert lineno > lineno2, "getfslineno does not unwrap correctly"
class TestMockDecoration: class TestMockDecoration(object):
def test_wrapped_getfuncargnames(self): def test_wrapped_getfuncargnames(self):
from _pytest.compat import getfuncargnames from _pytest.compat import getfuncargnames
@ -207,7 +207,7 @@ class TestMockDecoration:
@patch('os.getcwd') @patch('os.getcwd')
@patch('os.path') @patch('os.path')
@mark.slow @mark.slow
class TestSimple: class TestSimple(object):
def test_simple_thing(self, mock_path, mock_getcwd): def test_simple_thing(self, mock_path, mock_getcwd):
pass pass
""") """)
@ -215,7 +215,7 @@ class TestMockDecoration:
reprec.assertoutcome(passed=1) reprec.assertoutcome(passed=1)
class TestReRunTests: class TestReRunTests(object):
def test_rerun(self, testdir): def test_rerun(self, testdir):
testdir.makeconftest(""" testdir.makeconftest("""
from _pytest.runner import runtestprotocol from _pytest.runner import runtestprotocol
@ -251,7 +251,7 @@ def test_pytestconfig_is_session_scoped():
assert pytestconfig._pytestfixturefunction.scope == "session" assert pytestconfig._pytestfixturefunction.scope == "session"
class TestNoselikeTestAttribute: class TestNoselikeTestAttribute(object):
def test_module_with_global_test(self, testdir): def test_module_with_global_test(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""
__test__ = False __test__ = False
@ -270,7 +270,7 @@ class TestNoselikeTestAttribute:
pass pass
test_func.__test__ = False test_func.__test__ = False
class TestSome: class TestSome(object):
__test__ = False __test__ = False
def test_method(self): def test_method(self):
pass pass
@ -328,7 +328,7 @@ class TestNoselikeTestAttribute:
@pytest.mark.issue351 @pytest.mark.issue351
class TestParameterize: class TestParameterize(object):
def test_idfn_marker(self, testdir): def test_idfn_marker(self, testdir):
testdir.makepyfile(""" testdir.makepyfile("""

Some files were not shown because too many files have changed in this diff Show More