Merge branch 'pytest-dev'

# Conflicts:
#	AUTHORS
This commit is contained in:
elizabeth 2015-08-24 22:55:11 +03:00
commit e67d66a5d4
149 changed files with 1334 additions and 427 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
CHANGELOG merge=union

5
.gitignore vendored
View File

@ -17,6 +17,11 @@ include/
*.orig
*~
.eggs/
# this file is managed by setuptools_scm
_pytest/__init__.py
doc/*/_build
build/
dist/

View File

@ -28,7 +28,7 @@ env:
- TESTENV=py35
- TESTENV=pypy
script: tox --recreate -i ALL=https://devpi.net/hpk/dev/ -e $TESTENV
script: tox --recreate -e $TESTENV
notifications:
irc:

View File

@ -3,6 +3,7 @@ merlinux GmbH, Germany, office at merlinux eu
Contributors include::
Abhijeet Kasurde
Anatoly Bubenkoff
Andreas Zeidler
Andy Freeland
@ -14,6 +15,7 @@ Bob Ippolito
Brian Dorsey
Brian Okken
Brianna Laugher
Bruno Oliveira
Carl Friedrich Bolz
Charles Cloud
Chris Lamb
@ -24,11 +26,12 @@ Daniel Grana
Daniel Nuri
Dave Hunt
David Mohr
Edison Gustavo Muenz
Eduardo Schettino
Elizaveta Shashkova
Eric Hunsberger
Eric Siegerman
Florian Bruhin
Edison Gustavo Muenz
Floris Bruynooghe
Graham Horler
Grig Gheorghiu
@ -46,6 +49,7 @@ Maciek Fijalkowski
Maho
Marc Schlaich
Mark Abramowitz
Markus Unterwaditzer
Martijn Faassen
Nicolas Delaby
Pieter Mulder
@ -58,3 +62,4 @@ Samuele Pedroni
Tom Viner
Trevor Bekolay
Wouter van Ackooy
David Díaz-Barquero

View File

@ -1,6 +1,40 @@
2.8.0.dev (compared to 2.7.X)
-----------------------------
- Fix #562: @nose.tools.istest now fully respected.
- Fix issue736: Fix a bug where fixture params would be discarded when combined
with parametrization markers.
Thanks to Markus Unterwaditzer for the PR.
- fix issue710: introduce ALLOW_UNICODE doctest option: when enabled, the
``u`` prefix is stripped from unicode strings in expected doctest output. This
allows doctests which use unicode to run in Python 2 and 3 unchanged.
Thanks Jason R. Coombs for the report and Bruno Oliveira for the PR.
- parametrize now also generates meaningful test IDs for enum, regex and class
objects (as opposed to class instances).
Thanks to Florian Bruhin for the PR.
- Add 'warns' to assert that warnings are thrown (like 'raises').
Thanks to Eric Hunsberger for the PR.
- Fix issue683: Do not apply an already applied mark. Thanks ojake for the PR.
- Deal with capturing failures better so fewer exceptions get lost to
/dev/null. Thanks David Szotten for the PR.
- fix issue730: deprecate and warn about the --genscript option.
Thanks Ronny Pfannschmidt for the report and Christian Pommranz for the PR.
- fix issue751: multiple parametrize with ids bug if it parametrizes class with
two or more test methods. Thanks Sergey Chipiga for reporting and Jan
Bednarik for PR.
- fix issue82: avoid loading conftest files from setup.cfg/pytest.ini/tox.ini
files and upwards by default (--confcutdir can still be set to override this).
Thanks Bruno Oliveira for the PR.
- fix issue768: docstrings found in python modules were not setting up session
fixtures. Thanks Jason R. Coombs for reporting and Bruno Oliveira for the PR.
@ -24,6 +58,15 @@
- Summary bar now is colored yellow for warning
situations such as: all tests either were skipped or xpass/xfailed,
or no tests were run at all (this is a partial fix for issue500).
- fix issue812: pytest now exits with status code 5 in situations where no
tests were run at all, such as the directory given in the command line does
not contain any tests or as result of a command line option filters
all out all tests (-k for example).
Thanks Eric Siegerman (issue812) and Bruno Oliveira for the PR.
- Summary bar now is colored yellow for warning
situations such as: all tests either were skipped or xpass/xfailed,
or no tests were run at all (related to issue500).
Thanks Eric Siegerman.
- New `testpaths` ini option: list of directories to search for tests
@ -103,6 +146,11 @@
- fix issue714: add ability to apply indirect=True parameter on particular argnames.
- fix issue890: changed extension of all documentation files from ``txt`` to
``rst``. Thanks to Abhijeet for the PR.
- issue951: add new record_xml_property fixture, that supports logging
additional information on xml output. Thanks David Diaz for the PR.
2.7.3 (compared to 2.7.2)
-----------------------------

View File

@ -21,7 +21,7 @@ in repositories living under:
- `the pytest-dev bitbucket team <https://bitbucket.org/pytest-dev>`_
All pytest-dev team members have write access to all contained
All pytest-dev Contributors team members have write access to all contained
repositories. pytest core and plugins are generally developed
using `pull requests`_ to respective repositories.
@ -46,9 +46,9 @@ the following:
If no contributor strongly objects and two agree, the repo will be
transferred to the ``pytest-dev`` organisation and you'll become a
member of the ``pytest-dev`` team, with commit rights to all projects.
We recommend that each plugin has at least three people who have the
right to release to pypi.
member of the ``pytest-dev Contributors`` team, with commit rights
to all projects. We recommend that each plugin has at least three
people who have the right to release to pypi.
.. _reportbugs:
@ -66,6 +66,10 @@ If you are reporting a bug, please include:
installed libraries and pytest version.
* Detailed steps to reproduce the bug.
If you can write a demonstration test that currently fails but should pass (xfail),
that is a very useful commit to make as well, even if you can't find how
to fix the bug yet.
.. _submitfeedback:
Submit feedback for developers
@ -93,6 +97,8 @@ https://github.com/pytest-dev/pytest/labels/bug
:ref:`Talk <contact>` to developers to find out how you can fix specific bugs.
Don't forget to check the issue trackers of your favourite plugins, too!
.. _writeplugins:
Implement features
@ -111,10 +117,14 @@ Write documentation
pytest could always use more documentation. What exactly is needed?
* More complementary documentation. Have you perhaps found something unclear?
* Documentation translations. We currently have English and Japanese versions.
* Documentation translations. We currently have only English.
* Docstrings. There's never too much of them.
* Blog posts, articles and such -- they're all very appreciated.
You can also edit documentation files directly in the Github web interface
without needing to make a fork and local copy. This can be convenient for
small fixes.
.. _`pull requests`:
.. _pull-requests:
@ -181,9 +191,13 @@ but here is a simple overview:
$ git commit -a -m "<commit message>"
$ git push -u
Make sure you add a CHANGELOG message, and add yourself to AUTHORS. If you
are unsure about either of these steps, submit your pull request and we'll
help you fix it up.
#. Finally, submit a pull request through the GitHub website:
.. image:: img/pullrequest.png
.. image:: doc/en/img/pullrequest.png
:width: 700px
:align: center

View File

@ -1,58 +1,87 @@
How to release pytest (draft)
How to release pytest
--------------------------------------------
1. bump version numbers in _pytest/__init__.py (setup.py reads it)
Note: this assumes you have already registered on pypi.
2. check and finalize CHANGELOG
1. Bump version numbers in _pytest/__init__.py (setup.py reads it)
3. write doc/en/announce/release-VERSION.txt and include
2. Check and finalize CHANGELOG
3. Write doc/en/announce/release-VERSION.txt and include
it in doc/en/announce/index.txt
4. use devpi for uploading a release tarball to a staging area:
- ``devpi use https://devpi.net/USER/dev``
- ``devpi upload --formats sdist,bdist_wheel``
4. Use devpi for uploading a release tarball to a staging area:
5. run from multiple machines:
- ``devpi use https://devpi.net/USER/dev``
- ``devpi test pytest==VERSION``
``devpi use https://devpi.net/USER/dev``
``devpi upload --formats sdist,bdist_wheel``
5. Run from multiple machines:
``devpi use https://devpi.net/USER/dev``
``devpi test pytest==VERSION``
6. Check that tests pass for relevant combinations with
6. check that tests pass for relevant combinations with
``devpi list pytest``
or look at failures with "devpi list -f pytest".
There will be some failed environments like e.g. the py33-trial
or py27-pexpect tox environments on Win32 platforms
which is ok (tox does not support skipping on
per-platform basis yet).
7. Regenerate the docs examples using tox::
# Create and activate a virtualenv with regendoc installed
# (currently needs revision 4a9ec1035734)
tox -e regen
7. Regenerate the docs examples using tox, and check for regressions::
8. Build the docs, you need a virtualenv with, py and sphinx
tox -e regen
git diff
8. Build the docs, you need a virtualenv with py and sphinx
installed::
cd docs/en
cd doc/en
make html
9. Tag the release::
hg tag VERSION
Commit any changes before tagging the release.
9. Tag the release::
git tag VERSION
git push
10. Upload the docs using doc/en/Makefile::
cd doc/en
make install # or "installall" if you have LaTeX installed for PDF
10. Upload the docs using docs/en/Makefile::
cd docs/en
make install # or "installall" if you have LaTeX installed
This requires ssh-login permission on pytest.org because it uses
rsync.
Note that the "install" target of doc/en/Makefile defines where the
rsync goes to, typically to the "latest" section of pytest.org.
11. publish to pypi "devpi push pytest-VERSION pypi:NAME" where NAME
is the name of pypi.python.org as configured in your
~/.pypirc file -- it's the same you would use with
"setup.py upload -r NAME"
If you are making a minor release (e.g. 5.4), you also need to manually
create a symlink for "latest"::
12. send release announcement to mailing lists:
ssh pytest-dev@pytest.org
ln -s 5.4 latest
pytest-dev
testing-in-python
python-announce-list@python.org
Browse to pytest.org to verify.
11. Publish to pypi::
devpi push pytest-VERSION pypi:NAME
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>`_.
12. Send release announcement to mailing lists:
- pytest-dev
- testing-in-python
- python-announce-list@python.org
13. **after the release** Bump the version number in ``_pytest/__init__.py``,
to the next Minor release version (i.e. if you released ``pytest-2.8.0``,
set it to ``pytest-2.9.0.dev1``).

View File

@ -21,11 +21,11 @@ clean:
# generate documentation
docs: develop
find doc/en -name '*.txt' -not -path 'doc/en/_build/*' | xargs .env/bin/regendoc ${REGENDOC_ARGS}
find doc/en -name '*.rst' -not -path 'doc/en/_build/*' | xargs .env/bin/regendoc ${REGENDOC_ARGS}
cd doc/en; make html
# upload documentation
upload-docs: develop
find doc/en -name '*.txt' -not -path 'doc/en/_build/*' | xargs .env/bin/regendoc ${REGENDOC_ARGS} --update
find doc/en -name '*.rst' -not -path 'doc/en/_build/*' | xargs .env/bin/regendoc ${REGENDOC_ARGS} --update
#cd doc/en; make install

View File

@ -1,2 +0,0 @@
#
__version__ = '2.8.0.dev4'

View File

@ -86,7 +86,9 @@ class CaptureManager:
self.deactivate_funcargs()
cap = getattr(self, "_capturing", None)
if cap is not None:
try:
outerr = cap.readouterr()
finally:
cap.suspend_capturing(in_=in_)
return outerr

View File

@ -211,6 +211,10 @@ class PytestPluginManager(PluginManager):
# support deprecated naming because plugins (xdist e.g.) use it
return self.get_plugin(name)
def hasplugin(self, name):
"""Return True if the plugin with the given name is registered."""
return bool(self.get_plugin(name))
def pytest_configure(self, config):
# XXX now that the pluginmanager exposes hookimpl(tryfirst...)
# we should remove tryfirst/trylast as markers
@ -897,6 +901,9 @@ class Config(object):
self.warn("I2", "could not load setuptools entry import: %s" % (e,))
self.pluginmanager.consider_env()
self.known_args_namespace = ns = self._parser.parse_known_args(args)
if self.known_args_namespace.confcutdir is None and self.inifile:
confcutdir = py.path.local(self.inifile).dirname
self.known_args_namespace.confcutdir = confcutdir
try:
self.hook.pytest_load_initial_conftests(early_config=self,
args=args, parser=self._parser)

View File

@ -63,7 +63,7 @@ class DoctestItem(pytest.Item):
lineno = test.lineno + example.lineno + 1
message = excinfo.type.__name__
reprlocation = ReprFileLocation(filename, lineno, message)
checker = doctest.OutputChecker()
checker = _get_unicode_checker()
REPORT_UDIFF = doctest.REPORT_UDIFF
filelines = py.path.local(filename).readlines(cr=0)
lines = []
@ -100,7 +100,8 @@ def _get_flag_lookup():
NORMALIZE_WHITESPACE=doctest.NORMALIZE_WHITESPACE,
ELLIPSIS=doctest.ELLIPSIS,
IGNORE_EXCEPTION_DETAIL=doctest.IGNORE_EXCEPTION_DETAIL,
COMPARISON_FLAGS=doctest.COMPARISON_FLAGS)
COMPARISON_FLAGS=doctest.COMPARISON_FLAGS,
ALLOW_UNICODE=_get_allow_unicode_flag())
def get_optionflags(parent):
optionflags_str = parent.config.getini("doctest_optionflags")
@ -110,15 +111,30 @@ def get_optionflags(parent):
flag_acc |= flag_lookup_table[flag]
return flag_acc
class DoctestTextfile(DoctestItem, pytest.File):
def runtest(self):
import doctest
fixture_request = _setup_fixtures(self)
failed, tot = doctest.testfile(
str(self.fspath), module_relative=False,
optionflags=get_optionflags(self),
extraglobs=dict(getfixture=fixture_request.getfuncargvalue),
raise_on_error=True, verbose=0)
# inspired by doctest.testfile; ideally we would use it directly,
# but it doesn't support passing a custom checker
text = self.fspath.read()
filename = str(self.fspath)
name = self.fspath.basename
globs = dict(getfixture=fixture_request.getfuncargvalue)
if '__name__' not in globs:
globs['__name__'] = '__main__'
optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_unicode_checker())
parser = doctest.DocTestParser()
test = parser.get_doctest(text, globs, name, filename, 0)
runner.run(test)
class DoctestModule(pytest.File):
def collect(self):
@ -139,7 +155,8 @@ class DoctestModule(pytest.File):
# uses internal doctest module parsing mechanism
finder = doctest.DocTestFinder()
optionflags = get_optionflags(self)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags)
runner = doctest.DebugRunner(verbose=0, optionflags=optionflags,
checker=_get_unicode_checker())
for test in finder.find(module, module.__name__,
extraglobs=doctest_globals):
if test.examples: # skip empty doctests
@ -160,3 +177,59 @@ def _setup_fixtures(doctest_item):
fixture_request = FixtureRequest(doctest_item)
fixture_request._fillfixtures()
return fixture_request
def _get_unicode_checker():
"""
Returns a doctest.OutputChecker subclass that takes in account the
ALLOW_UNICODE option to ignore u'' prefixes in strings. Useful
when the same doctest should run in Python 2 and Python 3.
An inner class is used to avoid importing "doctest" at the module
level.
"""
if hasattr(_get_unicode_checker, 'UnicodeOutputChecker'):
return _get_unicode_checker.UnicodeOutputChecker()
import doctest
import re
class UnicodeOutputChecker(doctest.OutputChecker):
"""
Copied from doctest_nose_plugin.py from the nltk project:
https://github.com/nltk/nltk
"""
_literal_re = re.compile(r"(\W|^)[uU]([rR]?[\'\"])", re.UNICODE)
def check_output(self, want, got, optionflags):
res = doctest.OutputChecker.check_output(self, want, got,
optionflags)
if res:
return True
if not (optionflags & _get_allow_unicode_flag()):
return False
else: # pragma: no cover
# the code below will end up executed only in Python 2 in
# our tests, and our coverage check runs in Python 3 only
def remove_u_prefixes(txt):
return re.sub(self._literal_re, r'\1\2', txt)
want = remove_u_prefixes(want)
got = remove_u_prefixes(got)
res = doctest.OutputChecker.check_output(self, want, got,
optionflags)
return res
_get_unicode_checker.UnicodeOutputChecker = UnicodeOutputChecker
return _get_unicode_checker.UnicodeOutputChecker()
def _get_allow_unicode_flag():
"""
Registers and returns the ALLOW_UNICODE flag.
"""
import doctest
return doctest.register_optionflag('ALLOW_UNICODE')

View File

@ -1,4 +1,4 @@
""" generate a single-file self-contained version of pytest """
""" (deprecated) generate a single-file self-contained version of pytest """
import os
import sys
import pkgutil
@ -31,6 +31,11 @@ def pkg_to_mapping(name):
else: # package
for pyfile in toplevel.visit('*.py'):
pkg = pkgname(name, toplevel, pyfile)
if pkg == '_pytest.__init__':
# remove the coding comment line to avoid python bug
lines = pyfile.read().splitlines(True)
name2src[pkg] = ''.join(lines[1:])
else:
name2src[pkg] = pyfile.read()
# with wheels py source code might be not be installed
# and the resulting genscript is useless, just bail out.
@ -72,6 +77,8 @@ def pytest_cmdline_main(config):
genscript = config.getvalue("genscript")
if genscript:
tw = _pytest.config.create_terminal_writer(config)
tw.line("WARNING: usage of genscript is deprecated.",
red=True)
deps = ['py', 'pluggy', '_pytest', 'pytest']
if sys.version_info < (2,7):
deps.append("argparse")

View File

@ -9,6 +9,7 @@ import os
import re
import sys
import time
import pytest
# Python 2.X and 3.X compatibility
if sys.version_info[0] < 3:
@ -53,6 +54,20 @@ def bin_xml_escape(arg):
return unicode('#x%04X') % i
return py.xml.raw(illegal_xml_re.sub(repl, py.xml.escape(arg)))
@pytest.fixture
def record_xml_property(request):
"""Fixture that adds extra xml properties to the tag for the calling test.
The fixture is callable with (name, value), with value being automatically
xml-encoded.
"""
def inner(name, value):
if hasattr(request.config, "_xml"):
request.config._xml.add_custom_property(name, value)
msg = 'record_xml_property is an experimental feature'
request.config.warn(code='C3', message=msg,
fslocation=request.node.location[:2])
return inner
def pytest_addoption(parser):
group = parser.getgroup("terminal reporting")
group.addoption('--junitxml', '--junit-xml', action="store",
@ -75,7 +90,6 @@ def pytest_unconfigure(config):
del config._xml
config.pluginmanager.unregister(xml)
def mangle_testnames(names):
names = [x.replace(".py", "") for x in names if x != '()']
names[0] = names[0].replace("/", '.')
@ -89,6 +103,10 @@ class LogXML(object):
self.tests = []
self.passed = self.skipped = 0
self.failed = self.errors = 0
self.custom_properties = {}
def add_custom_property(self, name, value):
self.custom_properties[str(name)] = bin_xml_escape(str(value))
def _opentestcase(self, report):
names = mangle_testnames(report.nodeid.split("::"))
@ -118,6 +136,10 @@ class LogXML(object):
def append(self, obj):
self.tests[-1].append(obj)
def append_custom_properties(self):
self.tests[-1].attr.__dict__.update(self.custom_properties)
self.custom_properties.clear()
def append_pass(self, report):
self.passed += 1
self._write_captured_output(report)
@ -179,6 +201,7 @@ class LogXML(object):
if report.when == "setup":
self._opentestcase(report)
self.tests[-1].attr.time += getattr(report, 'duration', 0)
self.append_custom_properties()
if report.passed:
if report.when == "call": # ignore setup/teardown
self.append_pass(report)

View File

@ -19,6 +19,7 @@ EXIT_TESTSFAILED = 1
EXIT_INTERRUPTED = 2
EXIT_INTERNALERROR = 3
EXIT_USAGEERROR = 4
EXIT_NOTESTSCOLLECTED = 5
name_re = re.compile("^[a-zA-Z_]\w*$")
@ -100,8 +101,10 @@ def wrap_session(config, doit):
if excinfo.errisinstance(SystemExit):
sys.stderr.write("mainloop: caught Spurious SystemExit!\n")
else:
if session._testsfailed:
if session.testsfailed:
session.exitstatus = EXIT_TESTSFAILED
elif session.testscollected == 0:
session.exitstatus = EXIT_NOTESTSCOLLECTED
finally:
excinfo = None # Explicitly break reference cycle.
session.startdir.chdir()
@ -509,7 +512,8 @@ class Session(FSCollector):
FSCollector.__init__(self, config.rootdir, parent=None,
config=config, session=self)
self._fs2hookproxy = {}
self._testsfailed = 0
self.testsfailed = 0
self.testscollected = 0
self.shouldstop = False
self.trace = config.trace.root.get("collection")
self._norecursepatterns = config.getini("norecursedirs")
@ -527,11 +531,11 @@ class Session(FSCollector):
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_logreport(self, report):
if report.failed and not hasattr(report, 'wasxfail'):
self._testsfailed += 1
self.testsfailed += 1
maxfail = self.config.getvalue("maxfail")
if maxfail and self._testsfailed >= maxfail:
if maxfail and self.testsfailed >= maxfail:
self.shouldstop = "stopping after %d failures" % (
self._testsfailed)
self.testsfailed)
pytest_collectreport = pytest_runtest_logreport
def isinitpath(self, path):
@ -564,6 +568,7 @@ class Session(FSCollector):
config=self.config, items=items)
finally:
hook.pytest_collection_finish(session=self)
self.testscollected = len(items)
return items
def _perform_collect(self, args, genitems):

View File

@ -291,7 +291,7 @@ class MarkInfo:
#: positional argument list, empty if none specified
self.args = args
#: keyword argument dictionary, empty if nothing specified
self.kwargs = kwargs
self.kwargs = kwargs.copy()
self._arglist = [(args, kwargs.copy())]
def __repr__(self):

View File

@ -1,4 +1,5 @@
""" Python test discovery, setup and run of test functions. """
import re
import fnmatch
import functools
import py
@ -8,6 +9,12 @@ import pytest
from _pytest.mark import MarkDecorator, MarkerError
from py._code.code import TerminalRepr
try:
import enum
except ImportError: # pragma: no cover
# Only available in Python 3.4+ or as a backport
enum = None
import _pytest
import pluggy
@ -22,6 +29,8 @@ isclass = inspect.isclass
callable = py.builtin.callable
# used to work around a python2 exception info leak
exc_clear = getattr(sys, 'exc_clear', lambda: None)
# The type of re.compile objects is not exposed in Python.
REGEX_TYPE = type(re.compile(''))
def filter_traceback(entry):
return entry.path != cutdir1 and not entry.path.relto(cutdir2)
@ -55,6 +64,17 @@ def getimfunc(func):
except AttributeError:
return func
def safe_getattr(object, name, default):
""" Like getattr but return default upon any Exception.
Attribute access can potentially fail for 'evil' Python objects.
See issue214
"""
try:
return getattr(object, name, default)
except Exception:
return default
class FixtureFunctionMarker:
def __init__(self, scope, params,
@ -160,10 +180,13 @@ def pytest_cmdline_main(config):
def pytest_generate_tests(metafunc):
# this misspelling is common - raise a specific error to alert the user
if hasattr(metafunc.function, 'parameterize'):
msg = "{0} has 'parameterize', spelling should be 'parametrize'"
raise MarkerError(msg.format(metafunc.function.__name__))
# those alternative spellings are common - raise a specific error to alert
# the user
alt_spellings = ['parameterize', 'parametrise', 'parameterise']
for attr in alt_spellings:
if hasattr(metafunc.function, attr):
msg = "{0} has '{1}', spelling should be 'parametrize'"
raise MarkerError(msg.format(metafunc.function.__name__, attr))
try:
markers = metafunc.function.parametrize
except AttributeError:
@ -245,11 +268,10 @@ def pytest_pycollect_makeitem(collector, name, obj):
raise StopIteration
# nothing was collected elsewhere, let's do it here
if isclass(obj):
if collector.classnamefilter(name):
if collector.istestclass(obj, name):
Class = collector._getcustomclass("Class")
outcome.force_result(Class(name, parent=collector))
elif collector.funcnamefilter(name) and hasattr(obj, "__call__") and\
getfixturemarker(obj) is None:
elif collector.istestfunction(obj, name):
# mock seems to store unbound methods (issue473), normalize it
obj = getattr(obj, "__func__", obj)
if not isfunction(obj):
@ -335,9 +357,24 @@ class PyCollector(PyobjMixin, pytest.Collector):
def funcnamefilter(self, name):
return self._matches_prefix_or_glob_option('python_functions', name)
def isnosetest(self, obj):
""" Look for the __test__ attribute, which is applied by the
@nose.tools.istest decorator
"""
return safe_getattr(obj, '__test__', False)
def classnamefilter(self, name):
return self._matches_prefix_or_glob_option('python_classes', name)
def istestfunction(self, obj, name):
return (
(self.funcnamefilter(name) or self.isnosetest(obj))
and safe_getattr(obj, "__call__", False) and getfixturemarker(obj) is None
)
def istestclass(self, obj, name):
return self.classnamefilter(name) or self.isnosetest(obj)
def _matches_prefix_or_glob_option(self, option_name, name):
"""
checks if the given name matches the prefix or glob-pattern defined
@ -480,6 +517,19 @@ class FuncFixtureInfo:
self.names_closure = names_closure
self.name2fixturedefs = name2fixturedefs
def _marked(func, mark):
""" Returns True if :func: is already marked with :mark:, False otherwise.
This can happen if marker is applied to class and the test file is
invoked more than once.
"""
try:
func_mark = getattr(func, mark.name)
except AttributeError:
return False
return mark.args == func_mark.args and mark.kwargs == func_mark.kwargs
def transfer_markers(funcobj, cls, mod):
# XXX this should rather be code in the mark plugin or the mark
# plugin should merge with the python plugin.
@ -490,8 +540,10 @@ def transfer_markers(funcobj, cls, mod):
continue
if isinstance(pytestmark, list):
for mark in pytestmark:
if not _marked(funcobj, mark):
mark(funcobj)
else:
if not _marked(funcobj, pytestmark):
pytestmark(funcobj)
class Module(pytest.File, PyCollector):
@ -973,8 +1025,15 @@ def _idval(val, argname, idx, idfn):
return s
except Exception:
pass
if isinstance(val, (float, int, str, bool, NoneType)):
return str(val)
elif isinstance(val, REGEX_TYPE):
return val.pattern
elif enum is not None and isinstance(val, enum.Enum):
return str(val)
elif isclass(val) and hasattr(val, '__name__'):
return val.__name__
return str(argname)+str(idx)
def _idvalset(idx, valset, argnames, idfn):
@ -1049,8 +1108,8 @@ def getlocation(function, curdir):
# builtin pytest.raises helper
def raises(ExpectedException, *args, **kwargs):
""" assert that a code block/function call raises @ExpectedException
def raises(expected_exception, *args, **kwargs):
""" assert that a code block/function call raises @expected_exception
and raise a failure exception otherwise.
This helper produces a ``py.code.ExceptionInfo()`` object.
@ -1098,23 +1157,23 @@ def raises(ExpectedException, *args, **kwargs):
"""
__tracebackhide__ = True
if ExpectedException is AssertionError:
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 ExpectedException
as expected_exception
msg = ("exceptions must be old-style classes or"
" derived from BaseException, not %s")
if isinstance(ExpectedException, tuple):
for exc in ExpectedException:
if not inspect.isclass(exc):
if isinstance(expected_exception, tuple):
for exc in expected_exception:
if not isclass(exc):
raise TypeError(msg % type(exc))
elif not inspect.isclass(ExpectedException):
raise TypeError(msg % type(ExpectedException))
elif not isclass(expected_exception):
raise TypeError(msg % type(expected_exception))
if not args:
return RaisesContext(ExpectedException)
return RaisesContext(expected_exception)
elif isinstance(args[0], str):
code, = args
assert isinstance(code, str)
@ -1127,19 +1186,19 @@ def raises(ExpectedException, *args, **kwargs):
py.builtin.exec_(code, frame.f_globals, loc)
# XXX didn'T mean f_globals == f_locals something special?
# this is destroyed here ...
except ExpectedException:
except expected_exception:
return py.code.ExceptionInfo()
else:
func = args[0]
try:
func(*args[1:], **kwargs)
except ExpectedException:
except expected_exception:
return py.code.ExceptionInfo()
pytest.fail("DID NOT RAISE")
class RaisesContext(object):
def __init__(self, ExpectedException):
self.ExpectedException = ExpectedException
def __init__(self, expected_exception):
self.expected_exception = expected_exception
self.excinfo = None
def __enter__(self):
@ -1158,7 +1217,7 @@ class RaisesContext(object):
exc_type, value, traceback = tp
tp = exc_type, exc_type(value), traceback
self.excinfo.__init__(tp)
return issubclass(self.excinfo.type, self.ExpectedException)
return issubclass(self.excinfo.type, self.expected_exception)
#
# the basic pytest Function item
@ -1771,7 +1830,7 @@ class FixtureManager:
if fixturedef.params is not None:
func_params = getattr(getattr(metafunc.function, 'parametrize', None), 'args', [[None]])
# skip directly parametrized arguments
if argname not in func_params and argname not in func_params[0]:
if argname not in func_params:
metafunc.parametrize(argname, fixturedef.params,
indirect=True, scope=fixturedef.scope,
ids=fixturedef.ids)
@ -2120,4 +2179,3 @@ def get_scope_node(node, scope):
return node.session
raise ValueError("unknown scope")
return node.getparent(cls)

View File

@ -1,10 +1,14 @@
""" recording warnings during test function execution. """
import inspect
import py
import sys
import warnings
import pytest
def pytest_funcarg__recwarn(request):
@pytest.yield_fixture
def recwarn(request):
"""Return a WarningsRecorder instance that provides these methods:
* ``pop(category=None)``: return last warning matching the category.
@ -13,83 +17,173 @@ def pytest_funcarg__recwarn(request):
See http://docs.python.org/library/warnings.html for information
on warning categories.
"""
if sys.version_info >= (2,7):
oldfilters = warnings.filters[:]
warnings.simplefilter('default')
def reset_filters():
warnings.filters[:] = oldfilters
request.addfinalizer(reset_filters)
wrec = WarningsRecorder()
request.addfinalizer(wrec.finalize)
return wrec
with wrec:
warnings.simplefilter('default')
yield wrec
def pytest_namespace():
return {'deprecated_call': deprecated_call}
return {'deprecated_call': deprecated_call,
'warns': warns}
def deprecated_call(func, *args, **kwargs):
""" assert that calling ``func(*args, **kwargs)``
triggers a DeprecationWarning.
"""Assert that ``func(*args, **kwargs)`` triggers a DeprecationWarning.
"""
l = []
oldwarn_explicit = getattr(warnings, 'warn_explicit')
def warn_explicit(*args, **kwargs):
l.append(args)
oldwarn_explicit(*args, **kwargs)
oldwarn = getattr(warnings, 'warn')
def warn(*args, **kwargs):
l.append(args)
oldwarn(*args, **kwargs)
warnings.warn_explicit = warn_explicit
warnings.warn = warn
try:
wrec = WarningsRecorder()
with wrec:
warnings.simplefilter('always') # ensure all warnings are triggered
ret = func(*args, **kwargs)
finally:
warnings.warn_explicit = oldwarn_explicit
warnings.warn = oldwarn
if not l:
if not any(r.category is DeprecationWarning for r in wrec):
__tracebackhide__ = True
raise AssertionError("%r did not produce DeprecationWarning" % (func,))
return ret
class RecordedWarning:
def __init__(self, message, category, filename, lineno, line):
def warns(expected_warning, *args, **kwargs):
"""Assert that code raises a particular class of warning.
Specifically, the input @expected_warning can be a warning class or
tuple of warning classes, and the code must return that warning
(if a single class) or one of those warnings (if a tuple).
This helper produces a list of ``warnings.WarningMessage`` objects,
one for each warning raised.
This function can be used as a context manager, or any of the other ways
``pytest.raises`` can be used::
>>> with warns(RuntimeWarning):
... warnings.warn("my warning", RuntimeWarning)
"""
wcheck = WarningsChecker(expected_warning)
if not args:
return wcheck
elif isinstance(args[0], str):
code, = args
assert isinstance(code, str)
frame = sys._getframe(1)
loc = frame.f_locals.copy()
loc.update(kwargs)
with wcheck:
code = py.code.Source(code).compile()
py.builtin.exec_(code, frame.f_globals, loc)
else:
func = args[0]
with wcheck:
return func(*args[1:], **kwargs)
class RecordedWarning(object):
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:
def __init__(self):
self.list = []
def showwarning(message, category, filename, lineno, line=0):
self.list.append(RecordedWarning(
message, category, filename, lineno, line))
try:
self.old_showwarning(message, category,
filename, lineno, line=line)
except TypeError:
# < python2.6
self.old_showwarning(message, category, filename, lineno)
self.old_showwarning = warnings.showwarning
warnings.showwarning = showwarning
class WarningsRecorder(object):
"""A context manager to record raised warnings.
Adapted from `warnings.catch_warnings`.
"""
def __init__(self, module=None):
self._module = sys.modules['warnings'] if module is None else module
self._entered = False
self._list = []
@property
def list(self):
"""The list of recorded warnings."""
return self._list
def __getitem__(self, i):
"""Get a recorded warning by index."""
return self._list[i]
def __iter__(self):
"""Iterate through the recorded warnings."""
return iter(self._list)
def __len__(self):
"""The number of recorded warnings."""
return len(self._list)
def pop(self, cls=Warning):
""" pop the first recorded warning, raise exception if not exists."""
for i, w in enumerate(self.list):
"""Pop the first recorded warning, raise exception if not exists."""
for i, w in enumerate(self._list):
if issubclass(w.category, cls):
return self.list.pop(i)
return self._list.pop(i)
__tracebackhide__ = True
assert 0, "%r not found in %r" %(cls, self.list)
#def resetregistry(self):
# warnings.onceregistry.clear()
# warnings.__warningregistry__.clear()
raise AssertionError("%r not found in warning list" % cls)
def clear(self):
self.list[:] = []
"""Clear the list of recorded warnings."""
self._list[:] = []
def finalize(self):
warnings.showwarning = self.old_showwarning
def __enter__(self):
if self._entered:
__tracebackhide__ = True
raise RuntimeError("Cannot enter %r twice" % self)
self._entered = True
self._filters = self._module.filters
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', append=True)
return self
def __exit__(self, *exc_info):
if not self._entered:
__tracebackhide__ = True
raise RuntimeError("Cannot exit %r without entering first" % self)
self._module.filters = self._filters
self._module.showwarning = self._showwarning
class WarningsChecker(WarningsRecorder):
def __init__(self, expected_warning=None, module=None):
super(WarningsChecker, self).__init__(module=module)
msg = ("exceptions must be old-style classes or "
"derived from Warning, not %s")
if isinstance(expected_warning, tuple):
for exc in expected_warning:
if not inspect.isclass(exc):
raise TypeError(msg % type(exc))
elif inspect.isclass(expected_warning):
expected_warning = (expected_warning,)
elif expected_warning is not None:
raise TypeError(msg % type(expected_warning))
self.expected_warning = expected_warning
def __exit__(self, *exc_info):
super(WarningsChecker, self).__exit__(*exc_info)
# only check if we're not currently handling an exception
if all(a is None for a in exc_info):
if self.expected_warning is not None:
if not any(r.category in self.expected_warning for r in self):
__tracebackhide__ = True
pytest.fail("DID NOT WARN")

View File

@ -68,6 +68,11 @@ class DictImporter(object):
return res
if __name__ == "__main__":
try:
import pkg_resources # noqa
except ImportError:
sys.stderr.write("ERROR: setuptools not installed\n")
sys.exit(2)
if sys.version_info >= (3, 0):
exec("def do_exec(co, loc): exec(co, loc)\n")
import pickle
@ -80,6 +85,5 @@ if __name__ == "__main__":
importer = DictImporter(sources)
sys.meta_path.insert(0, importer)
entry = "@ENTRY@"
do_exec(entry, locals()) # noqa

View File

@ -2,6 +2,8 @@
This is a good source for looking at the various reporting hooks.
"""
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, \
EXIT_USAGEERROR, EXIT_NOTESTSCOLLECTED
import pytest
import pluggy
import py
@ -298,13 +300,9 @@ class TerminalReporter:
plugininfo = config.pluginmanager.list_plugin_distinfo()
if plugininfo:
l = []
for plugin, dist in plugininfo:
name = dist.project_name
if name.startswith("pytest-"):
name = name[7:]
l.append(name)
lines.append("plugins: %s" % ", ".join(l))
lines.append(
"plugins: %s" % ", ".join(_plugin_nameversions(plugininfo)))
return lines
def pytest_collection_finish(self, session):
@ -359,12 +357,15 @@ class TerminalReporter:
outcome = yield
outcome.get_result()
self._tw.line("")
if exitstatus in (0, 1, 2, 4):
summary_exit_codes = (
EXIT_OK, EXIT_TESTSFAILED, EXIT_INTERRUPTED, EXIT_USAGEERROR,
EXIT_NOTESTSCOLLECTED)
if exitstatus in summary_exit_codes:
self.summary_errors()
self.summary_failures()
self.summary_warnings()
self.config.hook.pytest_terminal_summary(terminalreporter=self)
if exitstatus == 2:
if exitstatus == EXIT_INTERRUPTED:
self._report_keyboardinterrupt()
del self._keyboardinterrupt_memo
self.summary_deselected()
@ -549,3 +550,18 @@ def build_summary_stats_line(stats):
color = 'yellow'
return (line, color)
def _plugin_nameversions(plugininfo):
l = []
for plugin, dist in plugininfo:
# gets us name and version!
name = '{dist.project_name}-{dist.version}'.format(dist=dist)
# questionable convenience, but it keeps things short
if name.startswith("pytest-"):
name = name[7:]
# we decided to print python package names
# they can have more than one plugin
if name not in l:
l.append(name)
return l

View File

@ -56,7 +56,7 @@ class TempdirFactory:
# make_numbered_dir() call
import getpass
temproot = py.path.local.get_temproot()
rootdir = temproot.join('pytest-%s' % getpass.getuser())
rootdir = temproot.join('pytest-of-%s' % getpass.getuser())
rootdir.ensure(dir=1)
basetemp = py.path.local.make_numbered_dir(prefix='pytest-',
rootdir=rootdir)

View File

@ -46,7 +46,7 @@ installall: clean install installpdf
@echo "done"
regen:
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.txt */*.txt
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.rst */*.rst
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

View File

@ -6,7 +6,7 @@ def get_version_string():
fn = py.path.local(__file__).join("..", "..", "..",
"_pytest", "__init__.py")
for line in fn.readlines():
if "version" in line:
if "version" in line and not line.strip().startswith('#'):
return eval(line.split("=")[-1])
def get_minor_version_string():

27
doc/en/apiref.rst Normal file
View File

@ -0,0 +1,27 @@
.. _apiref:
pytest reference documentation
================================================
.. toctree::
:maxdepth: 2
builtin
customize
assert
fixture
yieldfixture
parametrize
xunit_setup
capture
monkeypatch
xdist
tmpdir
mark
skipping
recwarn
unittest
nose
doctest

View File

@ -1,27 +0,0 @@
.. _apiref:
pytest reference documentation
================================================
.. toctree::
:maxdepth: 2
builtin.txt
customize.txt
assert.txt
fixture.txt
yieldfixture.txt
parametrize.txt
xunit_setup.txt
capture.txt
monkeypatch.txt
xdist.txt
tmpdir.txt
mark.txt
skipping.txt
recwarn.txt
unittest.txt
nose.txt
doctest.txt

View File

@ -114,6 +114,16 @@ like documenting unfixed bugs (where the test describes what "should" happen)
or bugs in dependencies.
.. _`assertwarns`:
Assertions about expected warnings
-----------------------------------------
.. versionadded:: 2.8
You can check that code raises a particular warning using
:ref:`pytest.warns <warns>`.
.. _newreport:
@ -228,9 +238,7 @@ Reporting details about a failing assertion is achieved either by rewriting
assert statements before they are run or re-evaluating the assert expression and
recording the intermediate values. Which technique is used depends on the
location of the assert, ``pytest`` configuration, and Python version being used
to run ``pytest``. Note that for assert statements with a manually provided
message, i.e. ``assert expr, message``, no assertion introspection takes place
and the manually provided message will be rendered in tracebacks.
to run ``pytest``.
By default, if the Python version is greater than or equal to 2.6, ``pytest``
rewrites assert statements in test modules. Rewritten assert statements put

View File

@ -47,7 +47,7 @@ extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.autosummary',
templates_path = ['_templates']
# The suffix of source filenames.
source_suffix = '.txt'
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
@ -73,13 +73,13 @@ copyright = u'2015, holger krekel and pytest-dev team'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['links.inc', '_build', 'naming20.txt', 'test/*',
exclude_patterns = ['links.inc', '_build', 'naming20.rst', 'test/*',
"old_*",
'*attic*',
'*/attic*',
'funcargs.txt',
'setup.txt',
'example/remoteinterp.txt',
'funcargs.rst',
'setup.rst',
'example/remoteinterp.rst',
]

View File

@ -17,11 +17,11 @@ Full pytest documentation
example/index
talks
contributing
funcarg_compare.txt
funcarg_compare
announce/index
.. toctree::
:hidden:
changelog.txt
changelog

View File

@ -219,3 +219,10 @@ Builtin configuration file options
One or more doctest flag names from the standard ``doctest`` module.
:doc:`See how py.test handles doctests <doctest>`.
.. confval:: confcutdir
Sets a directory where search upwards for ``conftest.py`` files stops.
By default, pytest will stop searching for ``conftest.py`` files upwards
from ``pytest.ini``/``tox.ini``/``setup.cfg`` of the project if any,
or up to the file-system root.

View File

@ -72,3 +72,18 @@ ignore lengthy exception stack traces you can just write::
# content of pytest.ini
[pytest]
doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL
py.test also introduces a new ``ALLOW_UNICODE`` option flag: when enabled, the
``u`` prefix is stripped from unicode strings in expected doctest output. This
allows doctests which use unicode to run in Python 2 and 3 unchanged.
As with any other option flag, this flag can be enabled in ``pytest.ini`` using
the ``doctest_optionflags`` ini option or by an inline comment in the doc test
itself::
# content of example.rst
>>> get_unicode_greeting() # doctest: +ALLOW_UNICODE
'Hello'

View File

@ -25,10 +25,10 @@ The following examples aim at various use cases you might encounter.
.. toctree::
:maxdepth: 2
reportingdemo.txt
simple.txt
parametrize.txt
markers.txt
special.txt
pythoncollection.txt
nonpython.txt
reportingdemo
simple
parametrize
markers
special
pythoncollection
nonpython

View File

@ -81,7 +81,7 @@ Numbers, strings, booleans and None will have their usual string representation
used in the test ID. For other objects, pytest will make a string based on
the argument name::
# contents of test_time.py
# content of test_time.py
from datetime import datetime, timedelta

View File

@ -534,23 +534,24 @@ case we just write some informations out to a ``failures`` file::
import pytest
import os.path
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_makereport(item, call, __multicall__):
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
# execute all other hooks to obtain the report object
rep = __multicall__.execute()
outcome = yield
rep = outcome.get_result()
# we only look at actual failing test calls, not setup/teardown
if rep.when == "call" and rep.failed:
mode = "a" if os.path.exists("failures") else "w"
with open("failures", mode) as f:
# let's also access a fixture for the fun of it
if "tmpdir" in item.funcargs:
if "tmpdir" in item.fixturenames:
extra = " (%s)" % item.funcargs["tmpdir"]
else:
extra = ""
f.write(rep.nodeid + extra + "\n")
return rep
if you then have failing tests::
@ -606,16 +607,16 @@ here is a little example implemented via a local plugin::
import pytest
@pytest.hookimpl(tryfirst=True)
def pytest_runtest_makereport(item, call, __multicall__):
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
# execute all other hooks to obtain the report object
rep = __multicall__.execute()
outcome = yield
rep = outcome.get_result()
# set an report attribute for each phase of a call, which can
# be "setup", "call", "teardown"
setattr(item, "rep_" + rep.when, rep)
return rep
@pytest.fixture
@ -742,5 +743,4 @@ over to ``pytest`` instead. For example::
This makes it convenient to execute your tests from within your frozen
application, using standard ``py.test`` command-line options::
$ ./app_main --pytest --verbose --tb=long --junit-xml=results.xml test-suite/
/bin/sh: ./app_main: No such file or directory
./app_main --pytest --verbose --tb=long --junit-xml=results.xml test-suite/

View File

@ -154,8 +154,8 @@ to create a JUnitXML file that Jenkins_ can pick up and generate reports.
.. _standalone:
.. _`genscript method`:
Create a pytest standalone script
-------------------------------------------
(deprecated) Create a pytest standalone script
-----------------------------------------------
If you are a maintainer or application developer and want people
who don't deal with python much to easily run tests you may generate

View File

@ -5,10 +5,10 @@ Getting started basics
.. toctree::
:maxdepth: 2
index.txt
getting-started.txt
usage.txt
goodpractises.txt
projects.txt
faq.txt
index
getting-started
usage
goodpractises
projects
faq

View File

@ -114,6 +114,18 @@ Let's run this::
The one parameter set which caused a failure previously now
shows up as an "xfailed (expected to fail)" test.
To get all combinations of multiple parametrized arguments you can stack
``parametrize`` decorators::
import pytest
@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_foo(x, y):
pass
This will run the test with the arguments set to x=0/y=2, x=0/y=3, x=1/y=2 and
x=1/y=3.
.. note::
In versions prior to 2.4 one needed to specify the argument

116
doc/en/recwarn.rst Normal file
View File

@ -0,0 +1,116 @@
Asserting Warnings
=====================================================
.. _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).
.. _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.
.. _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``::
import pytest
def test_global():
pytest.deprecated_call(myfunction, 17)
By default, deprecation warnings will not be caught when using ``pytest.warns``
or ``recwarn``, since the default Python warnings filters hide
DeprecationWarnings. 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)

View File

@ -1,46 +0,0 @@
Asserting deprecation and other warnings
=====================================================
.. _function_argument:
The recwarn function argument
------------------------------------
You can use the ``recwarn`` funcarg to assert that code triggers
warnings through the Python warnings system. Here is a simple
self-contained test::
# content of test_recwarn.py
def test_hello(recwarn):
from warnings import warn
warn("hello", DeprecationWarning)
w = recwarn.pop(DeprecationWarning)
assert issubclass(w.category, DeprecationWarning)
assert 'hello' in str(w.message)
assert w.filename
assert w.lineno
The ``recwarn`` function argument provides these methods:
.. method:: pop(category=None)
Return last warning matching the category.
.. method:: clear()
Clear list of warnings
.. _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``::
import pytest
def test_global():
pytest.deprecated_call(myfunction, 17)

View File

@ -1,54 +0,0 @@
pytest release checklist
-------------------------
For doing a release of pytest (status April 2015) this rough checklist is used:
1. change version numbers in ``_pytest/__init__.py`` to the to-be-released version.
(the version number in ``setup.py`` reads from that init file as well)
2. finalize ``./CHANGELOG`` (don't forget the the header).
3. write ``doc/en/announce/release-VERSION.txt``
(usually copying from an earlier release version).
4. regenerate doc examples with ``tox -e regen`` and check with ``git diff``
if the differences show regressions. It's a bit of a manual process because
there a large part of the diff is about pytest headers or differences in
speed ("tests took X.Y seconds"). (XXX automate doc/example diffing to ignore
such changes and integrate it into "tox -e regen").
5. ``devpi upload`` to `your developer devpi index <http://doc.devpi.net/latest/quickstart-releaseprocess.html>`_. You can create your own user and index on https://devpi.net,
an inofficial service from the devpi authors.
6. run ``devpi use INDEX`` and ``devpi test`` from linux and windows machines
and verify test results on the index. On linux typically all environments
pass (April 2015 there is a setup problem with a cx_freeze environment)
but on windows all involving ``pexpect`` fail because pexpect does not exist
on windows and tox does not allow to have platform-specific environments.
Also on windows ``py33-trial`` fails but should probably pass (March 2015).
In any case, py26,py27,py33,py34 are required to pass for all platforms.
7. You can fix tests/code and repeat number 6. until everything passes.
8. Once you have sufficiently passing tox tests you can do the actual release::
cd doc/en/
make install # will install to 2.7, 2.8, ... according to _pytest/__init__.py
make install-pdf # optional, requires latex packages installed
ssh pytest-dev@pytest.org # MANUAL: symlink "pytest.org/latest" to the just
# installed release docs
# browse to pytest.org to see
devpi push pytest-VERSION pypi:NAME
git commit -a -m "... finalized pytest-VERSION"
git tag VERSION
git push
9. send out release announcement to pytest-dev@python.org,
testing-in-python@lists.idyll.org and python-announce-list@python.org .
10. **after the release** bump the version number in ``_pytest/__init__.py``,
to the next Minor release version (i.e. if you released ``pytest-2.8.0``,
set it to ``pytest-2.9.0.dev1``).
11. already done :)

View File

@ -83,7 +83,7 @@ As with all function :ref:`marking <mark>` you can skip test functions at the
`whole class- or module level`_. If your code targets python2.6 or above you
use the skipif decorator (and any other marker) on classes::
@pytest.mark.skipif(sys.platform == 'win32',
@pytest.mark.skipif(sys.platform != 'win32',
reason="requires windows")
class TestPosixCalls:
@ -97,7 +97,7 @@ If your code targets python2.5 where class-decorators are not available,
you can set the ``pytestmark`` attribute of a class::
class TestPosixCalls:
pytestmark = pytest.mark.skipif(sys.platform == 'win32',
pytestmark = pytest.mark.skipif(sys.platform != 'win32',
reason="requires Windows")
def test_function(self):

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