Compare commits

...

72 Commits
2.0.0 ... 2.0.2

Author SHA1 Message Date
holger krekel
527bc472a8 fix install location 2011-03-09 13:59:00 +01:00
holger krekel
007f0daeb9 bump to release version, regenerate docs 2011-03-09 10:58:36 +01:00
holger krekel
55657d6c51 simplify _locationline helper 2011-03-08 13:44:53 +01:00
holger krekel
1a7c6ecc42 fix slightly wrong verbose output for non subclasses on windows 2011-03-08 13:37:00 +01:00
holger krekel
f2670651b3 half the overhead for calling a test function by introducing some caching 2011-03-07 18:28:45 +01:00
holger krekel
5470cadbff fix issue25 --pdb and win32/python encodings cause a crash in certain situations.
The reason is not clear but avoiding a fresh copy of the terminal writer
helps, maybe because the underlying file object has some state?
2011-03-07 13:17:07 +01:00
holger krekel
f8e3fe8fbf mention that one might need to use the pypi.testrun.org repository 2011-03-07 11:53:14 +01:00
holger krekel
c552b58dc5 fix issue27 - --collectonly and -k keyword selection now work together.
internally, collectonly and terminal reporting has been unified.
2011-03-06 18:32:00 +01:00
holger krekel
18e784c9c9 re-introduce pytest._fillfuncargs - it's actually used by oejskit,
added a test documenting this.
2011-03-06 08:56:58 +01:00
holger krekel
5bef795ba7 add changelog entry about unittest change, bump version 2011-03-05 18:22:33 +01:00
Ronny Pfannschmidt
a6c518e68c unittest plugin: prune __unittest marked modules from traces 2011-03-05 17:49:51 +01:00
holger krekel
7e44c38570 avoid this test on pypy because syntax errors on pypy-1.4.1 are not precise it seems 2011-03-05 14:59:06 +01:00
holger krekel
9c952b3ce0 actually don't expose unused _fillfuncargs 2011-03-05 14:31:01 +01:00
holger krekel
bfe6e98abb don't expose _fillfuncargs (no clue why it ever was exposed) 2011-03-05 14:29:10 +01:00
holger krekel
07cee24122 avoid deprecation warnings for our internal accesses 2011-03-05 14:16:27 +01:00
holger krekel
22fac92ca0 improve and clarify skipping docs 2011-03-05 13:08:43 +01:00
holger krekel
318e8a404b fix and improve error reporting for parametrizing funcargs (originally reported by antlong) 2011-03-05 12:11:35 +01:00
holger krekel
fadd1a2313 incorporate typo/grammar fixes from Laura and respond to a number of issues she raised in comments.
Also fixed links and some other bits and pieces.
2011-03-03 23:40:38 +01:00
holger krekel
070c73ff2f fix issue30 (the second time)
put module globals into namespace for xfail and skipif expressions
2011-03-03 23:22:55 +01:00
holger krekel
682773e0cb fix issue30 - better handling and reporting of errors in xfail expressions 2011-03-03 12:19:17 +01:00
holger krekel
6f3b84da9f fix issue 28 - setup_method now works with pytest_generate_tests 2011-03-02 18:03:43 +01:00
holger krekel
f1b5dae1fb fix help string 2011-02-27 12:52:27 +01:00
holger krekel
8d62e4c71c add "issues" to layout of web page 2011-02-16 00:32:57 +01:00
Brianna Laugher
f6c1e49287 Fix mistakes in monkeypatch doc example 2011-02-18 16:52:18 +11:00
holger krekel
27577170e1 doc typo fixes from Victor Garcia, thanks! 2011-02-17 14:46:40 +01:00
Floris Bruynooghe
2f2586af72 Fix pytest_assertrepr_compare on python3 (issue24)
The maxsize argument must be an integer and the devision syntax changed
between python2 and python3.
2011-02-15 23:24:18 +00:00
holger krekel
70ceb946e4 fix typo, thanks antocuni 2011-02-14 18:49:16 +01:00
holger krekel
d2f9b41519 some doc fixes and improvements to parametrized test examples, thanks ccxCZ for review and suggestions. 2011-02-09 14:55:21 +01:00
holger krekel
2bd0c98801 up version, commit 2.0.1 annoucnement as sent out 2011-02-07 11:54:08 +01:00
holger krekel
5a5a618dcb Added tag 2.0.1 for changeset e4497c2aed35 2011-02-07 11:45:47 +01:00
holger krekel
98cd8edb71 regen docs with examples 2011-02-07 11:45:37 +01:00
holger krekel
e7b69a2ac0 doc fixes 2011-02-07 11:39:05 +01:00
holger krekel
74b9ebc1cd accept a left out "()" for ids on command line for better compatibility with pytest.vim 2011-02-07 11:09:42 +01:00
holger krekel
3004fe3915 fix the last committed laxation of a test 2011-02-04 23:20:27 +01:00
holger krekel
eb225456d7 laxer test for also passing it with pypy 2011-02-04 22:51:05 +01:00
holger krekel
b04f87b1a6 add release announcement 2011-02-03 15:58:22 +01:00
holger krekel
35b0b376f0 bumping version to pytest-2.0.1, regen docs and examples 2011-02-03 15:14:50 +01:00
holger krekel
762ea71f67 fix error reporting issue when a "pyc" file has no relating "py" 2011-01-27 21:11:21 +01:00
holger krekel
adacd3491d fix test related to "not in" 2011-01-27 11:36:12 +01:00
Floris Bruynooghe
709d5e3f2c Improve the "not in" assertion output
This cleans up the generic diff and tailors the output more to this
specific assertion (based on feedback from hpk).
2011-01-25 20:47:34 +00:00
holger krekel
d8d88ede65 refine and unify initial capturing - now works also if the logging module
is already used from an early-loaded conftest.py file (prior to option parsing)
2011-01-18 12:51:21 +01:00
holger krekel
b8f0d10f80 fix a pypy related regression - re-allow self.NAME style collection tree customization 2011-01-18 12:47:31 +01:00
holger krekel
aea4d1bd7a fix regression with yield-based tests (hopefully) 2011-01-14 13:30:36 +01:00
holger krekel
2b750074f4 fix typo (thanks derdon) 2011-01-13 23:50:10 +01:00
holger krekel
88cfaebbcb fix issue12 - show plugin versions with "--version" and "--traceconfig" and also document how to add extra information to reporting test header 2011-01-12 19:39:36 +01:00
holger krekel
426e056d2b fix issue10 - numpy arrays should now work better in assertion expressions
(or any other objects which have an exception-raising __nonzero__ method ...)
2011-01-12 19:17:54 +01:00
holger krekel
4445685285 pypy doesn't neccessarily honour -OO it seems, let's not test assertions there. 2011-01-12 18:57:40 +01:00
holger krekel
ae9b7a8bea use pypi.testrun.org so that py>1.4.0 gets picked up correctly 2011-01-12 18:03:55 +01:00
holger krekel
5daef51000 fix issue14 : it was actually issue14 instead of issue8 that was fixed with
the older https://bitbucket.org/hpk42/pytest/changeset/1c3eb86502b3

please try out with the usual "pip install -i http://pypi.testrun.org -U pytest"
2011-01-12 17:35:09 +01:00
holger krekel
647b56614a fix issue17 by requiring an update to pylib which helps to fix it 2011-01-12 17:21:11 +01:00
holger krekel
1b3fb3d229 fix issue15 - tests for python3/nose-1.0 combo work now 2011-01-11 17:27:34 +01:00
holger krekel
170c78cef9 remove same-conftest.py detection - does more harm than good
(see mail from Ralf Schmitt on py-dev)
2011-01-11 15:54:47 +01:00
Benjamin Peterson
8f5d837ef6 duplicate word 2010-12-23 14:56:38 -06:00
holger krekel
0ec5f3fd6c small improvements, add assertion improvement to CHANGELOG 2010-12-10 12:28:04 +01:00
Floris Bruynooghe
8631c1f57a Add "not in" to detailed explanations
This simply uses difflib to compare the text without the offending
string to the full text.

Also ensures the summary line uses all space available.  But the
terminal width is still hardcoded.
2010-12-10 01:03:26 +00:00
holger krekel
821f493378 check docstring at test time instead of runtime, improve and test warning on assertion turned off (thanks FND for reporting) 2010-12-09 11:00:31 +01:00
holger krekel
4086d46378 fix issue11 doc typo (thanks Eduardo) 2010-12-07 15:25:25 +01:00
holger krekel
a15983cb33 rather named the new hook cmdline_preparse 2010-12-07 12:34:18 +01:00
holger krekel
9ab256c296 make getvalueorskip() be hidden in skip-reporting. also bump version. 2010-12-07 12:18:24 +01:00
holger krekel
7db9e98b55 introduce a pytest_cmdline_processargs hook to modify/add dynamically to command line arguments. 2010-12-07 12:14:12 +01:00
holger krekel
e6541ed14e bump version and fix changelog issue reference 2010-12-06 19:01:50 +01:00
holger krekel
fc4f72cb1f fix issue7 - assert failure inside doctest doesn't prettyprint
unexpected exceptions are now reported within the doctest failure
representation context.
2010-12-06 19:00:30 +01:00
holger krekel
feea4ea3d5 fix hasplugin() method / test failures 2010-12-06 18:32:04 +01:00
holger krekel
513482f4f7 fix issue9 wrong XPass with failing setup/teardown function of xfail marked test
now when setup or teardown of a test item/function fails and the test
is marked "xfail" it will show up as an xfail-ed test.
2010-12-06 18:20:47 +01:00
holger krekel
2e80512bb8 fix issue8 : avoid errors caused by logging module wanting to close already closed streams.
The issue arose if logging was initialized while capturing was enabled
and then capturing streams were closed before process exit, leading
to the logging module to complain.
2010-12-06 16:56:12 +01:00
holger krekel
c7531705fc refine plugin registration, allow new "-p no:NAME" way to prevent/undo plugin registration 2010-12-06 16:54:42 +01:00
holger krekel
752965c298 add some docs and new projects 2010-12-06 10:41:20 +01:00
holger krekel
96a687b97c make pytest test suite pypy ready 2010-11-27 16:40:52 +01:00
holger krekel
d894bae281 bumping version to a dev version, run tests by using python PyPI by default 2010-11-26 13:37:00 +01:00
holger krekel
f1fc6e5eb6 regenerating examples 2010-11-26 13:26:56 +01:00
Benjamin Peterson
ca72c162c8 need double colon here 2010-11-25 20:55:32 -06:00
holger krekel
9bcb66d9a5 Added tag 2.0.0 for changeset e9e127acd6f0 2010-11-25 21:03:08 +01:00
92 changed files with 2142 additions and 1019 deletions

View File

@@ -31,3 +31,5 @@ c59d3fa8681a5b5966b8375b16fccd64a3a8dbeb 1.3.3
79ef6377705184c55633d456832eea318fedcf61 1.3.4
79ef6377705184c55633d456832eea318fedcf61 1.3.4
90fffd35373e9f125af233f78b19416f0938d841 1.3.4
e9e127acd6f0497324ef7f40cfb997cad4c4cd17 2.0.0
e4497c2aed358c1988cf7be83ca9394c3c707fa2 2.0.1

103
CHANGELOG
View File

@@ -1,4 +1,105 @@
Changes between 1.3.4 and 2.0.0dev0
Changes between 2.0.1 and 2.0.2
----------------------------------------------
- tackle issue32 - speed up test runs of very quick test functions
by reducing the relative overhead
- fix issue30 - extended xfail/skipif handling and improved reporting.
If you have a syntax error in your skip/xfail
expressions you now get nice error reports.
Also you can now access module globals from xfail/skipif
expressions so that this for example works now::
import pytest
import mymodule
@pytest.mark.skipif("mymodule.__version__[0] == "1")
def test_function():
pass
This will not run the test function if the module's version string
does not start with a "1". Note that specifying a string instead
of a boolean expressions allows py.test to report meaningful information
when summarizing a test run as to what conditions lead to skipping
(or xfail-ing) tests.
- fix issue28 - setup_method and pytest_generate_tests work together
The setup_method fixture method now gets called also for
test function invocations generated from the pytest_generate_tests
hook.
- fix issue27 - collectonly and keyword-selection (-k) now work together
Also, if you do "py.test --collectonly -q" you now get a flat list
of test ids that you can use to paste to the py.test commandline
in order to execute a particular test.
- fix issue25 avoid reported problems with --pdb and python3.2/encodings output
- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP
Starting with Python3.2 os.symlink may be supported. By requiring
a newer py lib version the py.path.local() implementation acknowledges
this.
- fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular
thanks to Laura Creighton who also revieved parts of the documentation.
- fix slighly wrong output of verbose progress reporting for classes
(thanks Amaury)
- more precise (avoiding of) deprecation warnings for node.Class|Function accesses
- avoid std unittest assertion helper code in tracebacks (thanks Ronny)
Changes between 2.0.0 and 2.0.1
----------------------------------------------
- refine and unify initial capturing so that it works nicely
even if the logging module is used on an early-loaded conftest.py
file or plugin.
- allow to omit "()" in test ids to allow for uniform test ids
as produced by Alfredo's nice pytest.vim plugin.
- fix issue12 - show plugin versions with "--version" and
"--traceconfig" and also document how to add extra information
to reporting test header
- fix issue17 (import-* reporting issue on python3) by
requiring py>1.4.0 (1.4.1 is going to include it)
- fix issue10 (numpy arrays truth checking) by refining
assertion interpretation in py lib
- fix issue15: make nose compatibility tests compatible
with python3 (now that nose-1.0 supports python3)
- remove somewhat surprising "same-conftest" detection because
it ignores conftest.py when they appear in several subdirs.
- improve assertions ("not in"), thanks Floris Bruynooghe
- improve behaviour/warnings when running on top of "python -OO"
(assertions and docstrings are turned off, leading to potential
false positives)
- introduce a pytest_cmdline_processargs(args) hook
to allow dynamic computation of command line arguments.
This fixes a regression because py.test prior to 2.0
allowed to set command line options from conftest.py
files which so far pytest-2.0 only allowed from ini-files now.
- fix issue7: assert failures in doctest modules.
unexpected failures in doctests will not generally
show nicer, i.e. within the doctest failing context.
- fix issue9: setup/teardown functions for an xfail-marked
test will report as xfail if they fail but report as normally
passing (not xpassing) if they succeed. This only is true
for "direct" setup/teardown invocations because teardown_class/
teardown_module cannot closely relate to a single test.
- fix issue14: no logging errors at process exit
- refinements to "collecting" output on non-ttys
- refine internal plugin registration and --traceconfig output
- introduce a mechanism to prevent/unregister plugins from the
command line, see http://pytest.org/plugins.html#cmdunregister
- activate resultlog plugin by default
- fix regression wrt yielded tests which due to the
collection-before-running semantics were not
setup as with pytest 1.3.4. Note, however, that
the recommended and much cleaner way to do test
parametraization remains the "pytest_generate_tests"
mechanism, see the docs.
Changes between 1.3.4 and 2.0.0
----------------------------------------------
- pytest-2.0 is now its own package and depends on pylib-2.0

View File

@@ -88,6 +88,16 @@ etc. Idea is to allow Python expressions which can operate
on common spellings for operating systems and python
interpreter versions.
pytest.mark.xfail signature change
-------------------------------------------------------
tags: feature 2.1
change to pytest.mark.xfail(reason, (optional)condition)
to better implement the word meaning. It also signals
better that we always have some kind of an implementation
reason that can be formualated.
Compatibility? Maybe rename to "pytest.mark.xfail"?
introduce py.test.mark registration
-----------------------------------------
tags: feature 2.1

View File

@@ -12,13 +12,13 @@ def pytest_addoption(parser):
help="disable python assert expression reinterpretation."),
def pytest_configure(config):
# The _pytesthook attribute on the AssertionError is used by
# The _reprcompare attribute on the py.code module is used by
# py._code._assertionnew to detect this plugin was loaded and in
# turn call the hooks defined here as part of the
# DebugInterpreter.
config._monkeypatch = m = monkeypatch()
warn_about_missing_assertion()
if not config.getvalue("noassert") and not config.getvalue("nomagic"):
warn_about_missing_assertion()
def callbinrepr(op, left, right):
hook_result = config.hook.pytest_assertrepr_compare(
config=config, op=op, left=left, right=right)
@@ -38,9 +38,8 @@ def warn_about_missing_assertion():
except AssertionError:
pass
else:
py.std.warnings.warn("Assertions are turned off!"
" (are you using python -O?)")
sys.stderr.write("WARNING: failing tests may report as passing because "
"assertions are turned off! (are you using python -O?)\n")
# Provide basestring in python3
try:
@@ -51,8 +50,9 @@ except NameError:
def pytest_assertrepr_compare(op, left, right):
"""return specialised explanations for some operators/operands"""
left_repr = py.io.saferepr(left, maxsize=30)
right_repr = py.io.saferepr(right, maxsize=30)
width = 80 - 15 - len(op) - 2 # 15 chars indentation, 1 space around op
left_repr = py.io.saferepr(left, maxsize=int(width/2))
right_repr = py.io.saferepr(right, maxsize=width-len(left_repr))
summary = '%s %s %s' % (left_repr, op, right_repr)
issequence = lambda x: isinstance(x, (list, tuple))
@@ -72,6 +72,9 @@ def pytest_assertrepr_compare(op, left, right):
elif isdict(left) and isdict(right):
explanation = _diff_text(py.std.pprint.pformat(left),
py.std.pprint.pformat(right))
elif op == 'not in':
if istext(left) and istext(right):
explanation = _notin_text(left, right)
except py.builtin._sysex:
raise
except:
@@ -155,3 +158,22 @@ def _compare_eq_set(left, right):
for item in diff_right:
explanation.append(py.io.saferepr(item))
return explanation
def _notin_text(term, text):
index = text.find(term)
head = text[:index]
tail = text[index+len(term):]
correct_text = head + tail
diff = _diff_text(correct_text, text)
newdiff = ['%s is contained here:' % py.io.saferepr(term, maxsize=42)]
for line in diff:
if line.startswith('Skipping'):
continue
if line.startswith('- '):
continue
if line.startswith('+ '):
newdiff.append(' ' + line[2:])
else:
newdiff.append(line)
return newdiff

View File

@@ -19,14 +19,14 @@ def addouterr(rep, outerr):
if content:
repr.addsection("Captured std%s" % secname, content.rstrip())
def pytest_configure(config):
config.pluginmanager.register(CaptureManager(), 'capturemanager')
def pytest_unconfigure(config):
# registered in config.py during early conftest.py loading
capman = config.pluginmanager.getplugin('capturemanager')
while capman._method2capture:
name, cap = capman._method2capture.popitem()
cap.reset()
# XXX logging module may wants to close it itself on process exit
# otherwise we could do finalization here and call "reset()".
cap.suspend()
class NoCapture:
def startall(self):
@@ -65,6 +65,14 @@ class CaptureManager:
else:
raise ValueError("unknown capturing method: %r" % method)
def _getmethod_preoptionparse(self, args):
if '-s' in args or "--capture=no" in args:
return "no"
elif hasattr(os, 'dup') and '--capture=sys' not in args:
return "fd"
else:
return "sys"
def _getmethod(self, config, fspath):
if config.option.capture:
method = config.option.capture
@@ -184,18 +192,16 @@ class CaptureManager:
return rep
def pytest_funcarg__capsys(request):
"""captures writes to sys.stdout/sys.stderr and makes
them available successively via a ``capsys.readouterr()`` method
which returns a ``(out, err)`` tuple of captured snapshot strings.
"""enables capturing of writes to sys.stdout/sys.stderr and makes
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple.
"""
return CaptureFuncarg(py.io.StdCapture)
def pytest_funcarg__capfd(request):
"""captures writes to file descriptors 1 and 2 and makes
snapshotted ``(out, err)`` string tuples available
via the ``capsys.readouterr()`` method. If the underlying
platform does not have ``os.dup`` (e.g. Jython) tests using
this funcarg will automatically skip.
"""enables capturing of writes to file descriptors 1 and 2 and makes
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple.
"""
if not hasattr(os, 'dup'):
py.test.skip("capfd funcarg needs os.dup")

View File

@@ -34,7 +34,7 @@ class Parser:
def getgroup(self, name, description="", after=None):
""" get (or create) a named option Group.
:name: unique name of the option group.
:description: long description for --help output.
:after: name of other group, used for ordering --help output.
@@ -125,7 +125,6 @@ class Conftest(object):
self._onimport = onimport
self._conftestpath2mod = {}
self._confcutdir = confcutdir
self._md5cache = {}
def setinitial(self, args):
""" try to find a first anchor path for looking up global values
@@ -179,14 +178,7 @@ class Conftest(object):
else:
conftestpath = path.join("conftest.py")
if conftestpath.check(file=1):
key = conftestpath.computehash()
# XXX logging about conftest loading
if key not in self._md5cache:
clist.append(self.importconftest(conftestpath))
self._md5cache[key] = conftestpath
else:
# use some kind of logging
print ("WARN: not loading %s" % conftestpath)
clist.append(self.importconftest(conftestpath))
clist[:0] = self.getconftestmodules(dp)
self._path2confmods[path] = clist
# be defensive: avoid changes from caller side to
@@ -278,13 +270,16 @@ class Config(object):
def _setinitialconftest(self, args):
# capture output during conftest init (#issue93)
name = hasattr(os, 'dup') and 'StdCaptureFD' or 'StdCapture'
cap = getattr(py.io, name)()
from _pytest.capture import CaptureManager
capman = CaptureManager()
self.pluginmanager.register(capman, 'capturemanager')
# will be unregistered in capture.py's unconfigure()
capman.resumecapture(capman._getmethod_preoptionparse(args))
try:
try:
self._conftest.setinitial(args)
finally:
out, err = cap.reset()
out, err = capman.suspendcapture() # logging might have got it
except:
sys.stdout.write(out)
sys.stderr.write(err)
@@ -300,11 +295,13 @@ class Config(object):
if addopts:
args[:] = self.getini("addopts") + args
self._checkversion()
self.pluginmanager.consider_preparse(args)
self.pluginmanager.consider_setuptools_entrypoints()
self.pluginmanager.consider_env()
self.pluginmanager.consider_preparse(args)
self._setinitialconftest(args)
self.pluginmanager.do_addoption(self._parser)
if addopts:
self.hook.pytest_cmdline_preparse(config=self, args=args)
def _checkversion(self):
minver = self.inicfg.get('minversion', None)
@@ -397,7 +394,9 @@ class Config(object):
return self._getconftest(name, path, check=False)
def getvalueorskip(self, name, path=None):
""" (deprecated) return getvalue(name) or call py.test.skip if no value exists. """
""" (deprecated) return getvalue(name) or call
py.test.skip if no value exists. """
__tracebackhide__ = True
try:
val = self.getvalue(name, path)
if val is None:
@@ -421,7 +420,7 @@ def getcfg(args, inibasenames):
if 'pytest' in iniconfig.sections:
return iniconfig['pytest']
return {}
def findupwards(current, basename):
current = py.path.local(current)
while 1:

View File

@@ -11,11 +11,9 @@ assert py.__version__.split(".")[:2] >= ['1', '4'], ("installation problem: "
"%s is too old, remove or upgrade 'py'" % (py.__version__))
default_plugins = (
"config mark session terminal runner python pdb unittest capture skipping "
"config mark main terminal runner python pdb unittest capture skipping "
"tmpdir monkeypatch recwarn pastebin helpconfig nose assertion genscript "
"junitxml doctest").split()
IMPORTPREFIX = "pytest_"
"junitxml resultlog doctest").split()
class TagTracer:
def __init__(self, prefix="[pytest] "):
@@ -62,9 +60,11 @@ class TagTracerSub:
class PluginManager(object):
def __init__(self, load=False):
self._name2plugin = {}
self._listattrcache = {}
self._plugins = []
self._hints = []
self.trace = TagTracer().get("pluginmanage")
self._plugin_distinfo = []
if os.environ.get('PYTEST_DEBUG'):
err = sys.stderr
encoding = getattr(err, 'encoding', 'utf8')
@@ -79,20 +79,12 @@ class PluginManager(object):
for spec in default_plugins:
self.import_plugin(spec)
def _getpluginname(self, plugin, name):
if name is None:
if hasattr(plugin, '__name__'):
name = plugin.__name__.split(".")[-1]
else:
name = id(plugin)
return name
def register(self, plugin, name=None, prepend=False):
assert not self.isregistered(plugin), plugin
assert not self.isregistered(plugin), plugin
name = self._getpluginname(plugin, name)
name = name or getattr(plugin, '__name__', str(id(plugin)))
if name in self._name2plugin:
return False
#self.trace("registering", name, plugin)
self._name2plugin[name] = plugin
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
@@ -112,7 +104,7 @@ class PluginManager(object):
del self._name2plugin[name]
def isregistered(self, plugin, name=None):
if self._getpluginname(plugin, name) in self._name2plugin:
if self.getplugin(name) is not None:
return True
for val in self._name2plugin.values():
if plugin == val:
@@ -129,18 +121,15 @@ class PluginManager(object):
py.test.skip("plugin %r is missing" % name)
def hasplugin(self, name):
try:
self.getplugin(name)
return True
except KeyError:
return False
return bool(self.getplugin(name))
def getplugin(self, name):
if name is None:
return None
try:
return self._name2plugin[name]
except KeyError:
impname = canonical_importname(name)
return self._name2plugin[impname]
return self._name2plugin.get("_pytest." + name, None)
# API for bootstrapping
#
@@ -160,19 +149,29 @@ class PluginManager(object):
except ImportError:
return # XXX issue a warning
for ep in iter_entry_points('pytest11'):
name = canonical_importname(ep.name)
if name in self._name2plugin:
name = ep.name
if name.startswith("pytest_"):
name = name[7:]
if ep.name in self._name2plugin or name in self._name2plugin:
continue
try:
plugin = ep.load()
except DistributionNotFound:
continue
self._plugin_distinfo.append((ep.dist, plugin))
self.register(plugin, name=name)
def consider_preparse(self, args):
for opt1,opt2 in zip(args, args[1:]):
if opt1 == "-p":
self.import_plugin(opt2)
if opt2.startswith("no:"):
name = opt2[3:]
if self.getplugin(name) is not None:
self.unregister(None, name=name)
self._name2plugin[name] = -1
else:
if self.getplugin(opt2) is None:
self.import_plugin(opt2)
def consider_conftest(self, conftestmodule):
if self.register(conftestmodule, name=conftestmodule.__file__):
@@ -186,15 +185,19 @@ class PluginManager(object):
for spec in attr:
self.import_plugin(spec)
def import_plugin(self, spec):
assert isinstance(spec, str)
modname = canonical_importname(spec)
if modname in self._name2plugin:
def import_plugin(self, modname):
assert isinstance(modname, str)
if self.getplugin(modname) is not None:
return
try:
#self.trace("importing", modname)
mod = importplugin(modname)
except KeyboardInterrupt:
raise
except ImportError:
if modname.startswith("pytest_"):
return self.import_plugin(modname[7:])
raise
except:
e = py.std.sys.exc_info()[1]
if not hasattr(py.test, 'skip'):
@@ -270,6 +273,11 @@ class PluginManager(object):
def listattr(self, attrname, plugins=None):
if plugins is None:
plugins = self._plugins
key = (attrname,) + tuple(plugins)
try:
return list(self._listattrcache[key])
except KeyError:
pass
l = []
last = []
for plugin in plugins:
@@ -284,40 +292,25 @@ class PluginManager(object):
except AttributeError:
continue
l.extend(last)
self._listattrcache[key] = list(l)
return l
def call_plugin(self, plugin, methname, kwargs):
return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
kwargs=kwargs, firstresult=True).execute()
def canonical_importname(name):
if '.' in name:
return name
name = name.lower()
if not name.startswith(IMPORTPREFIX):
name = IMPORTPREFIX + name
return name
def importplugin(importspec):
#print "importing", importspec
name = importspec
try:
return __import__(importspec, None, None, '__doc__')
mod = "_pytest." + name
return __import__(mod, None, None, '__doc__')
except ImportError:
e = py.std.sys.exc_info()[1]
if str(e).find(importspec) == -1:
raise
name = importspec
try:
if name.startswith("pytest_"):
name = importspec[7:]
return __import__("_pytest.%s" %(name), None, None, '__doc__')
except ImportError:
e = py.std.sys.exc_info()[1]
if str(e).find(name) == -1:
raise
# show the original exception, not the failing internal one
return __import__(importspec, None, None, '__doc__')
#e = py.std.sys.exc_info()[1]
#if str(e).find(name) == -1:
# raise
pass #
return __import__(importspec, None, None, '__doc__')
class MultiCall:
""" execute a call into multiple python functions/methods. """
@@ -354,14 +347,20 @@ class MultiCall:
return kwargs
def varnames(func):
try:
return func._varnames
except AttributeError:
pass
if not inspect.isfunction(func) and not inspect.ismethod(func):
func = getattr(func, '__call__', func)
ismethod = inspect.ismethod(func)
rawcode = py.code.getrawcode(func)
try:
return rawcode.co_varnames[ismethod:rawcode.co_argcount]
x = rawcode.co_varnames[ismethod:rawcode.co_argcount]
except AttributeError:
return ()
x = ()
py.builtin._getfuncdict(func)['_varnames'] = x
return x
class HookRelay:
def __init__(self, hookspecs, pm, prefix="pytest_"):
@@ -378,9 +377,6 @@ class HookRelay:
added = False
for name, method in vars(hookspecs).items():
if name.startswith(prefix):
if not method.__doc__:
raise ValueError("docstring required for hook %r, in %r"
% (method, hookspecs))
firstresult = getattr(method, 'firstresult', False)
hc = HookCaller(self, name, firstresult=firstresult)
setattr(self, name, hc)

View File

@@ -34,7 +34,9 @@ class ReprFailDoctest(TerminalRepr):
class DoctestItem(pytest.Item):
def repr_failure(self, excinfo):
if excinfo.errisinstance(py.std.doctest.DocTestFailure):
doctest = py.std.doctest
if excinfo.errisinstance((doctest.DocTestFailure,
doctest.UnexpectedException)):
doctestfailure = excinfo.value
example = doctestfailure.example
test = doctestfailure.test
@@ -50,12 +52,15 @@ class DoctestItem(pytest.Item):
for line in filelines[i:lineno]:
lines.append("%03d %s" % (i+1, line))
i += 1
lines += checker.output_difference(example,
doctestfailure.got, REPORT_UDIFF).split("\n")
if excinfo.errisinstance(doctest.DocTestFailure):
lines += checker.output_difference(example,
doctestfailure.got, REPORT_UDIFF).split("\n")
else:
inner_excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
lines += ["UNEXPECTED EXCEPTION: %s" %
repr(inner_excinfo.value)]
return ReprFailDoctest(reprlocation, lines)
elif excinfo.errisinstance(py.std.doctest.UnexpectedException):
excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
return super(DoctestItem, self).repr_failure(excinfo)
else:
return super(DoctestItem, self).repr_failure(excinfo)

View File

@@ -2,6 +2,7 @@
import py
import pytest
import inspect, sys
from _pytest.core import varnames
def pytest_addoption(parser):
group = parser.getgroup('debugconfig')
@@ -28,6 +29,10 @@ def pytest_cmdline_main(config):
p = py.path.local(pytest.__file__)
sys.stderr.write("This is py.test version %s, imported from %s\n" %
(pytest.__version__, p))
plugininfo = getpluginversioninfo(config)
if plugininfo:
for line in plugininfo:
sys.stderr.write(line + "\n")
return 0
elif config.option.help:
config.pluginmanager.do_configure(config)
@@ -69,18 +74,37 @@ conftest_options = [
('pytest_plugins', 'list of plugin names to load'),
]
def getpluginversioninfo(config):
lines = []
plugininfo = config.pluginmanager._plugin_distinfo
if plugininfo:
lines.append("setuptools registered plugins:")
for dist, plugin in plugininfo:
loc = getattr(plugin, '__file__', repr(plugin))
content = "%s-%s at %s" % (dist.project_name, dist.version, loc)
lines.append(" " + content)
return lines
def pytest_report_header(config):
lines = []
if config.option.debug or config.option.traceconfig:
lines.append("using: pytest-%s pylib-%s" %
(pytest.__version__,py.__version__))
verinfo = getpluginversioninfo(config)
if verinfo:
lines.extend(verinfo)
if config.option.traceconfig:
lines.append("active plugins:")
plugins = []
items = config.pluginmanager._name2plugin.items()
for name, plugin in items:
lines.append(" %-20s: %s" %(name, repr(plugin)))
if hasattr(plugin, '__file__'):
r = plugin.__file__
else:
r = repr(plugin)
lines.append(" %-20s: %s" %(name, r))
return lines
@@ -112,12 +136,11 @@ def pytest_plugin_registered(manager, plugin):
fail = True
else:
#print "checking", method
method_args = getargs(method)
#print "method_args", method_args
method_args = list(varnames(method))
if '__multicall__' in method_args:
method_args.remove('__multicall__')
hook = hooks[name]
hookargs = getargs(hook)
hookargs = varnames(hook)
for arg in method_args:
if arg not in hookargs:
Print("argument %r not available" %(arg, ))
@@ -139,11 +162,6 @@ def isgenerichook(name):
return name == "pytest_plugins" or \
name.startswith("pytest_funcarg__")
def getargs(func):
args = inspect.getargs(py.code.getrawcode(func))[0]
startindex = inspect.ismethod(func) and 1 or 0
return args[startindex:]
def collectattr(obj):
methods = {}
for apiname in dir(obj):

View File

@@ -19,6 +19,9 @@ def pytest_cmdline_parse(pluginmanager, args):
"""return initialized config object, parsing the specified args. """
pytest_cmdline_parse.firstresult = True
def pytest_cmdline_preparse(config, args):
"""modify command line arguments before option parsing. """
def pytest_addoption(parser):
"""add optparse-style options and ini-style config values via calls
to ``parser.addoption`` and ``parser.addini(...)``.
@@ -202,7 +205,6 @@ def pytest_doctest_prepare_content(content):
""" return processed content for a given doctest"""
pytest_doctest_prepare_content.firstresult = True
# -------------------------------------------------------------------------
# error handling and internal debugging hooks
# -------------------------------------------------------------------------

View File

@@ -40,7 +40,7 @@ def pytest_addoption(parser):
help="only load conftest.py's relative to specified dir.")
group = parser.getgroup("debugconfig",
"test process debugging and configuration")
"test session debugging and configuration")
group.addoption('--basetemp', dest="basetemp", default=None, metavar="dir",
help="base temporary directory for this test run.")
@@ -121,9 +121,6 @@ class HookProxy:
def compatproperty(name):
def fget(self):
#print "retrieving %r property from %s" %(name, self.fspath)
py.log._apiwarn("2.0", "use pytest.%s for "
"test collection and item classes" % name)
return getattr(pytest, name)
return property(fget, None, None,
"deprecated attribute %r, use pytest.%s" % (name,name))
@@ -152,10 +149,19 @@ class Node(object):
Module = compatproperty("Module")
Class = compatproperty("Class")
Instance = compatproperty("Instance")
Function = compatproperty("Function")
File = compatproperty("File")
Item = compatproperty("Item")
def _getcustomclass(self, name):
cls = getattr(self, name)
if cls != getattr(pytest, name):
py.log._apiwarn("2.0", "use of node.%s is deprecated, "
"use pytest_pycollect_makeitem(...) to create custom "
"collection nodes" % name)
return cls
def __repr__(self):
return "<%s %r>" %(self.__class__.__name__, getattr(self, 'name', None))
@@ -320,7 +326,13 @@ class Item(Node):
return self._location
except AttributeError:
location = self.reportinfo()
fspath = self.session.fspath.bestrelpath(location[0])
# bestrelpath is a quite slow function
cache = self.config.__dict__.setdefault("_bestrelpathcache", {})
try:
fspath = cache[location[0]]
except KeyError:
fspath = self.session.fspath.bestrelpath(location[0])
cache[location[0]] = fspath
location = (fspath, location[1], str(location[2]))
self._location = location
return location
@@ -336,7 +348,7 @@ class Session(FSCollector):
def __init__(self, config):
super(Session, self).__init__(py.path.local(), parent=None,
config=config, session=self)
self.config.pluginmanager.register(self, name="session", prepend=True)
assert self.config.pluginmanager.register(self, name="session", prepend=True)
self._testsfailed = 0
self.shouldstop = False
self.trace = config.trace.root.get("collection")
@@ -448,7 +460,7 @@ class Session(FSCollector):
p = p.dirpath()
else:
p = p.new(basename=p.purebasename+".py")
return p
return str(p)
def _parsearg(self, arg):
""" return (fspath, names) tuple after checking the file exists. """
@@ -494,9 +506,15 @@ class Session(FSCollector):
node.ihook.pytest_collectstart(collector=node)
rep = node.ihook.pytest_make_collect_report(collector=node)
if rep.passed:
has_matched = False
for x in rep.result:
if x.name == name:
resultnodes.extend(self.matchnodes([x], nextnames))
has_matched = True
# XXX accept IDs that don't have "()" for class instances
if not has_matched and len(rep.result) == 1 and x.name == "()":
nextnames.insert(0, name)
resultnodes.extend(self.matchnodes([x], nextnames))
node.ihook.pytest_collectreport(report=rep)
return resultnodes

View File

@@ -89,8 +89,8 @@ class MarkGenerator:
class MarkDecorator:
""" A decorator for test functions and test classes. When applied
it will create :class:`MarkInfo` objects which may be
:ref:`retrieved by hooks as item keywords` MarkDecorator instances
are usually created by writing::
:ref:`retrieved by hooks as item keywords <excontrolskip>`.
MarkDecorator instances are often created like this::
mark1 = py.test.mark.NAME # simple MarkDecorator
mark2 = py.test.mark.NAME(name1=value) # parametrized MarkDecorator

View File

@@ -14,8 +14,8 @@ def pytest_funcarg__monkeypatch(request):
monkeypatch.delenv(name, value, raising=True)
monkeypatch.syspath_prepend(path)
All modifications will be undone when the requesting
test function finished its execution. The ``raising``
All modifications will be undone after the requesting
test function has finished. The ``raising``
parameter determines if a KeyError or AttributeError
will be raised if the set/deletion operation has no target.
"""

View File

@@ -52,7 +52,10 @@ class PdbInvoke:
if "xfail" in rep.keywords:
return rep
# we assume that the above execute() suspended capturing
tw = py.io.TerminalWriter()
# XXX we re-use the TerminalReporter's terminalwriter
# because this seems to avoid some encoding related troubles
# for not completely clear reasons.
tw = item.config.pluginmanager.getplugin("terminalreporter")._tw
tw.line()
tw.sep(">", "traceback")
rep.toterminal(tw)

View File

@@ -6,7 +6,7 @@ import re
import inspect
import time
from fnmatch import fnmatch
from _pytest.session import Session
from _pytest.main import Session
from py.builtin import print_
from _pytest.core import HookRelay
@@ -515,6 +515,8 @@ class TmpTestdir:
def spawn(self, cmd, expect_timeout=10.0):
pexpect = py.test.importorskip("pexpect", "2.4")
if hasattr(sys, 'pypy_version_info') and '64' in py.std.platform.machine():
pytest.skip("pypy-64 bit not supported")
logfile = self.tmpdir.join("spawn.out")
child = pexpect.spawn(cmd, logfile=logfile.open("w"))
child.timeout = expect_timeout

View File

@@ -73,6 +73,7 @@ def pytest_pycollect_makeitem(__multicall__, collector, name, obj):
if collector._istestclasscandidate(name, obj):
#if hasattr(collector.obj, 'unittest'):
# return # we assume it's a mixin class for a TestCase derived one
Class = collector._getcustomclass("Class")
return Class(name, parent=collector)
elif collector.funcnamefilter(name) and hasattr(obj, '__call__'):
if is_generator(obj):
@@ -160,7 +161,7 @@ class PyCollectorMixin(PyobjMixin, pytest.Collector):
for prefix in self.config.getini("python_functions"):
if name.startswith(prefix):
return True
def classnamefilter(self, name):
for prefix in self.config.getini("python_classes"):
if name.startswith(prefix):
@@ -213,6 +214,7 @@ class PyCollectorMixin(PyobjMixin, pytest.Collector):
extra.append(cls())
plugins = self.getplugins() + extra
gentesthook.pcall(plugins, metafunc=metafunc)
Function = self._getcustomclass("Function")
if not metafunc._calls:
return Function(name, parent=self)
l = []
@@ -223,6 +225,7 @@ class PyCollectorMixin(PyobjMixin, pytest.Collector):
l.append(function)
return l
class Module(pytest.File, PyCollectorMixin):
def _getobj(self):
return self._memoizedcall('_obj', self._importtestmodule)
@@ -272,7 +275,7 @@ class Module(pytest.File, PyCollectorMixin):
class Class(PyCollectorMixin, pytest.Collector):
def collect(self):
return [Instance(name="()", parent=self)]
return [self._getcustomclass("Instance")(name="()", parent=self)]
def setup(self):
setup_class = getattr(self.obj, 'setup_class', None)
@@ -297,18 +300,19 @@ class Instance(PyCollectorMixin, pytest.Collector):
class FunctionMixin(PyobjMixin):
""" mixin for the code common to Function and Generator.
"""
def setup(self):
""" perform setup for this test function. """
if inspect.ismethod(self.obj):
name = 'setup_method'
else:
name = 'setup_function'
if isinstance(self.parent, Instance):
if hasattr(self, '_preservedparent'):
obj = self._preservedparent
elif isinstance(self.parent, Instance):
obj = self.parent.newinstance()
self.obj = self._getobj()
else:
obj = self.parent.obj
if inspect.ismethod(self.obj):
name = 'setup_method'
else:
name = 'setup_function'
setup_func_or_method = getattr(obj, name, None)
if setup_func_or_method is not None:
setup_func_or_method(self.obj)
@@ -377,7 +381,8 @@ class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector):
# invoke setup/teardown on popular request
# (induced by the common "test_*" naming shared with normal tests)
self.config._setupstate.prepare(self)
# see FunctionMixin.setup and test_setupstate_is_preserved_134
self._preservedparent = self.parent.obj
l = []
seen = {}
for i, x in enumerate(self.obj()):
@@ -391,7 +396,7 @@ class Generator(FunctionMixin, PyCollectorMixin, pytest.Collector):
if name in seen:
raise ValueError("%r generated tests with non-unique name %r" %(self, name))
seen[name] = True
l.append(Function(name, self, args=args, callobj=call))
l.append(self.Function(name, self, args=args, callobj=call))
return l
def getcallargs(self, obj):
@@ -484,10 +489,11 @@ def hasinit(obj):
return True
def getfuncargnames(function):
def getfuncargnames(function, startindex=None):
# XXX merge with main.py's varnames
argnames = py.std.inspect.getargs(py.code.getrawcode(function))[0]
startindex = py.std.inspect.ismethod(function) and 1 or 0
if startindex is None:
startindex = py.std.inspect.ismethod(function) and 1 or 0
defaults = getattr(function, 'func_defaults',
getattr(function, '__defaults__', None)) or ()
numdefaults = len(defaults)
@@ -516,7 +522,8 @@ class Metafunc:
self.config = config
self.module = module
self.function = function
self.funcargnames = getfuncargnames(function)
self.funcargnames = getfuncargnames(function,
startindex=int(cls is not None))
self.cls = cls
self.module = module
self._calls = []
@@ -524,24 +531,29 @@ class Metafunc:
def addcall(self, funcargs=None, id=_notexists, param=_notexists):
""" add a new call to the underlying test function during the
collection phase of a test run.
collection phase of a test run. Note that request.addcall() is
called during the test collection phase prior and independently
to actual test execution. Therefore you should perform setup
of resources in a funcarg factory which can be instrumented
with the ``param``.
:arg funcargs: argument keyword dictionary used when invoking
the test function.
:arg id: used for reporting and identification purposes. If you
don't supply an `id` the length of the currently
list of calls to the test function will be used.
:arg param: will be exposed to a later funcarg factory invocation
through the ``request.param`` attribute. Setting it (instead of
directly providing a ``funcargs`` ditionary) is called
*indirect parametrization*. Indirect parametrization is
preferable if test values are expensive to setup or can
only be created after certain fixtures or test-run related
initialization code has been run.
through the ``request.param`` attribute. It allows to
defer test fixture setup activities to when an actual
test is run.
"""
assert funcargs is None or isinstance(funcargs, dict)
if funcargs is not None:
for name in funcargs:
if name not in self.funcargnames:
pytest.fail("funcarg %r not used in this function." % name)
if id is None:
raise ValueError("id=None not allowed")
if id is _notexists:
@@ -553,13 +565,19 @@ class Metafunc:
self._calls.append(CallSpec(funcargs, id, param))
class FuncargRequest:
""" A request for function arguments from a test function. """
""" A request for function arguments from a test function.
Note that there is an optional ``param`` attribute in case
there was an invocation to metafunc.addcall(param=...).
If no such call was done in a ``pytest_generate_tests``
hook, the attribute will not be present.
"""
_argprefix = "pytest_funcarg__"
_argname = None
class LookupError(LookupError):
""" error on performing funcarg request. """
def __init__(self, pyfuncitem):
self._pyfuncitem = pyfuncitem
if hasattr(pyfuncitem, '_requestparam'):
@@ -587,7 +605,7 @@ class FuncargRequest:
def module(self):
""" module where the test function was collected. """
return self._pyfuncitem.getparent(pytest.Module).obj
@property
def cls(self):
""" class (can be None) where the test function was collected. """
@@ -603,7 +621,7 @@ class FuncargRequest:
def config(self):
""" the pytest config object associated with this request. """
return self._pyfuncitem.config
@property
def fspath(self):
""" the file system path of the test module which collected this test. """
@@ -730,7 +748,7 @@ class FuncargRequest:
raise self.LookupError(msg)
def showfuncargs(config):
from _pytest.session import Session
from _pytest.main import Session
session = Session(config)
session.perform_collect()
if session.items:
@@ -777,7 +795,7 @@ def getlocation(function, curdir):
def raises(ExpectedException, *args, **kwargs):
""" assert that a code block/function call raises @ExpectedException
and raise a failure exception otherwise.
If using Python 2.5 or above, you may use this function as a
context manager::
@@ -800,7 +818,7 @@ def raises(ExpectedException, *args, **kwargs):
A third possibility is to use a string which which will
be executed::
>>> raises(ZeroDivisionError, "f(0)")
<ExceptionInfo ...>
"""
@@ -849,3 +867,4 @@ class RaisesContext(object):
pytest.fail("DID NOT RAISE")
self.excinfo.__init__(tp)
return issubclass(self.excinfo.type, self.ExpectedException)

View File

@@ -8,6 +8,9 @@ def pytest_funcarg__recwarn(request):
* ``pop(category=None)``: return last warning matching the category.
* ``clear()``: clear list of warnings
See http://docs.python.org/library/warnings.html for information
on warning categories.
"""
if sys.version_info >= (2,7):
import warnings

View File

@@ -1,12 +1,12 @@
""" (disabled by default) create result information in a plain text file. """
import py
from py.builtin import print_
def pytest_addoption(parser):
group = parser.getgroup("resultlog", "resultlog plugin options")
group.addoption('--resultlog', action="store", dest="resultlog", metavar="path", default=None,
help="path for machine-readable result log.")
group = parser.getgroup("terminal reporting", "resultlog plugin options")
group.addoption('--resultlog', action="store", dest="resultlog",
metavar="path", default=None,
help="path for machine-readable result log.")
def pytest_configure(config):
resultlog = config.option.resultlog
@@ -52,9 +52,9 @@ class ResultLog(object):
self.logfile = logfile # preferably line buffered
def write_log_entry(self, testpath, lettercode, longrepr):
print_("%s %s" % (lettercode, testpath), file=self.logfile)
py.builtin.print_("%s %s" % (lettercode, testpath), file=self.logfile)
for line in longrepr.splitlines():
print_(" %s" % line, file=self.logfile)
py.builtin.print_(" %s" % line, file=self.logfile)
def log_outcome(self, report, lettercode, longrepr):
testpath = getattr(report, 'nodeid', None)

View File

@@ -1,6 +1,7 @@
""" support for skip/xfail functions and markers. """
import py, pytest
import sys
def pytest_addoption(parser):
group = parser.getgroup("general")
@@ -32,9 +33,39 @@ class MarkEvaluator:
return bool(self.holder)
__nonzero__ = __bool__
def wasvalid(self):
return not hasattr(self, 'exc')
def istrue(self):
try:
return self._istrue()
except KeyboardInterrupt:
raise
except:
self.exc = sys.exc_info()
if isinstance(self.exc[1], SyntaxError):
msg = [" " * (self.exc[1].offset + 4) + "^",]
msg.append("SyntaxError: invalid syntax")
else:
msg = py.std.traceback.format_exception_only(*self.exc[:2])
pytest.fail("Error evaluating %r expression\n"
" %s\n"
"%s"
%(self.name, self.expr, "\n".join(msg)),
pytrace=False)
def _getglobals(self):
d = {'os': py.std.os, 'sys': py.std.sys, 'config': self.item.config}
func = self.item.obj
try:
d.update(func.__globals__)
except AttributeError:
d.update(func.func_globals)
return d
def _istrue(self):
if self.holder:
d = {'os': py.std.os, 'sys': py.std.sys, 'config': self.item.config}
d = self._getglobals()
if self.holder.args:
self.result = False
for expr in self.holder.args:
@@ -42,7 +73,7 @@ class MarkEvaluator:
if isinstance(expr, str):
result = cached_eval(self.item.config, expr, d)
else:
result = expr
pytest.fail("expression is not a string")
if result:
self.result = True
self.expr = expr
@@ -60,7 +91,7 @@ class MarkEvaluator:
if not hasattr(self, 'expr'):
return ""
else:
return "condition: " + self.expr
return "condition: " + str(self.expr)
return expl
@@ -97,19 +128,20 @@ def pytest_runtest_makereport(__multicall__, item, call):
rep.keywords['xfail'] = "reason: " + call.excinfo.value.msg
rep.outcome = "skipped"
return rep
if call.when == "call":
rep = __multicall__.execute()
evalxfail = getattr(item, '_evalxfail')
if not item.config.getvalue("runxfail") and evalxfail.istrue():
rep = __multicall__.execute()
evalxfail = item._evalxfail
if not item.config.option.runxfail:
if evalxfail.wasvalid() and evalxfail.istrue():
if call.excinfo:
rep.outcome = "skipped"
else:
rep.keywords['xfail'] = evalxfail.getexplanation()
elif call.when == "call":
rep.outcome = "failed"
rep.keywords['xfail'] = evalxfail.getexplanation()
else:
if 'xfail' in rep.keywords:
del rep.keywords['xfail']
return rep
rep.keywords['xfail'] = evalxfail.getexplanation()
return rep
if 'xfail' in rep.keywords:
del rep.keywords['xfail']
return rep
# called by terminalreporter progress reporting
def pytest_report_teststatus(report):
@@ -179,7 +211,8 @@ def cached_eval(config, expr, d):
except KeyError:
#import sys
#print >>sys.stderr, ("cache-miss: %r" % expr)
config._evalcache[expr] = x = eval(expr, d)
exprcode = py.code.compile(expr, mode="eval")
config._evalcache[expr] = x = eval(exprcode, d)
return x

View File

@@ -25,29 +25,26 @@ def pytest_addoption(parser):
group._addoption('--tb', metavar="style",
action="store", dest="tbstyle", default='long',
type="choice", choices=['long', 'short', 'no', 'line', 'native'],
help="traceback print mode (long/short/line/no).")
help="traceback print mode (long/short/line/native/no).")
group._addoption('--fulltrace',
action="store_true", dest="fulltrace", default=False,
help="don't cut any tracebacks (default is to cut).")
def pytest_configure(config):
config.option.verbose -= config.option.quiet
if config.option.collectonly:
reporter = CollectonlyReporter(config)
else:
# we try hard to make printing resilient against
# later changes on FD level.
stdout = py.std.sys.stdout
if hasattr(os, 'dup') and hasattr(stdout, 'fileno'):
try:
newfd = os.dup(stdout.fileno())
#print "got newfd", newfd
except ValueError:
pass
else:
stdout = os.fdopen(newfd, stdout.mode, 1)
config._toclose = stdout
reporter = TerminalReporter(config, stdout)
# we try hard to make printing resilient against
# later changes on FD level.
stdout = py.std.sys.stdout
if hasattr(os, 'dup') and hasattr(stdout, 'fileno'):
try:
newfd = os.dup(stdout.fileno())
#print "got newfd", newfd
except ValueError:
pass
else:
stdout = os.fdopen(newfd, stdout.mode, 1)
config._toclose = stdout
reporter = TerminalReporter(config, stdout)
config.pluginmanager.register(reporter, 'terminalreporter')
if config.option.debug or config.option.traceconfig:
def mywriter(tags, args):
@@ -136,6 +133,9 @@ class TerminalReporter:
self._tw.line()
self.currentfspath = None
def write(self, content, **markup):
self._tw.write(content, **markup)
def write_line(self, line, **markup):
line = str(line)
self.ensure_newline()
@@ -215,7 +215,7 @@ class TerminalReporter:
def pytest_collection(self):
if not self.hasmarkup:
self.write_line("collecting ...", bold=True)
self.write("collecting ... ", bold=True)
def pytest_collectreport(self, report):
if report.failed:
@@ -270,12 +270,45 @@ class TerminalReporter:
for line in flatten(lines):
self.write_line(line)
def pytest_collection_finish(self):
def pytest_collection_finish(self, session):
if self.config.option.collectonly:
self._printcollecteditems(session.items)
if self.stats.get('failed'):
self._tw.sep("!", "collection failures")
for rep in self.stats.get('failed'):
rep.toterminal(self._tw)
return 1
return 0
if not self.showheader:
return
#for i, testarg in enumerate(self.config.args):
# self.write_line("test path %d: %s" %(i+1, testarg))
def _printcollecteditems(self, items):
# to print out items and their parent collectors
# we take care to leave out Instances aka ()
# because later versions are going to get rid of them anyway
if self.config.option.verbose < 0:
for item in items:
nodeid = item.nodeid
nodeid = nodeid.replace("::()::", "::")
self._tw.line(nodeid)
return
stack = []
indent = ""
for item in items:
needed_collectors = item.listchain()[1:] # strip root node
while stack:
if stack == needed_collectors[:len(stack)]:
break
stack.pop()
for col in needed_collectors[len(stack):]:
stack.append(col)
#if col.name == "()":
# continue
indent = (len(stack)-1) * " "
self._tw.line("%s%s" %(indent, col))
def pytest_sessionfinish(self, exitstatus, __multicall__):
__multicall__.execute()
self._tw.line("")
@@ -302,19 +335,19 @@ class TerminalReporter:
excrepr.reprcrash.toterminal(self._tw)
def _locationline(self, collect_fspath, fspath, lineno, domain):
if fspath and fspath != collect_fspath:
# collect_fspath comes from testid which has a "/"-normalized path
if fspath and fspath.replace("\\", "/") != collect_fspath:
fspath = "%s <- %s" % (collect_fspath, fspath)
if lineno is not None:
lineno += 1
if fspath and lineno and domain:
line = "%(fspath)s:%(lineno)s: %(domain)s"
elif fspath and domain:
line = "%(fspath)s: %(domain)s"
elif fspath and lineno:
line = "%(fspath)s:%(lineno)s %(extrapath)s"
if fspath:
line = str(fspath)
if lineno is not None:
lineno += 1
line += ":" + str(lineno)
if domain:
line += ": " + str(domain)
else:
line = "[nolocation]"
return line % locals() + " "
line = "[location]"
return line + " "
def _getfailureheadline(self, rep):
if hasattr(rep, 'location'):
@@ -400,52 +433,6 @@ class TerminalReporter:
self.write_sep("=", "%d tests deselected by %r" %(
len(self.stats['deselected']), self.config.option.keyword), bold=True)
class CollectonlyReporter:
INDENT = " "
def __init__(self, config, out=None):
self.config = config
if out is None:
out = py.std.sys.stdout
self._tw = py.io.TerminalWriter(out)
self.indent = ""
self._failed = []
def outindent(self, line):
self._tw.line(self.indent + str(line))
def pytest_internalerror(self, excrepr):
for line in str(excrepr).split("\n"):
self._tw.line("INTERNALERROR> " + line)
def pytest_collectstart(self, collector):
if collector.session != collector:
self.outindent(collector)
self.indent += self.INDENT
def pytest_itemcollected(self, item):
self.outindent(item)
def pytest_collectreport(self, report):
if not report.passed:
if hasattr(report.longrepr, 'reprcrash'):
msg = report.longrepr.reprcrash.message
else:
# XXX unify (we have CollectErrorRepr here)
msg = str(report.longrepr[2])
self.outindent("!!! %s !!!" % msg)
#self.outindent("!!! error !!!")
self._failed.append(report)
self.indent = self.indent[:-len(self.INDENT)]
def pytest_collection_finish(self):
if self._failed:
self._tw.sep("!", "collection failures")
for rep in self._failed:
rep.toterminal(self._tw)
return self._failed and 1 or 0
def repr_pythonversion(v=None):
if v is None:
v = sys.version_info

View File

@@ -59,7 +59,7 @@ def pytest_unconfigure(config):
def pytest_funcarg__tmpdir(request):
"""return a temporary directory path object
unique to each test function invocation,
which is unique to each test function invocation,
created as a sub directory of the base temporary
directory. The returned object is a `py.path.local`_
path object.

View File

@@ -102,6 +102,10 @@ class TestCaseFunction(pytest.Function):
def runtest(self):
self._testcase(result=self)
def _prunetraceback(self, excinfo):
pytest.Function._prunetraceback(self, excinfo)
excinfo.traceback = excinfo.traceback.filter(lambda x:not x.frame.f_globals.get('__unittest'))
@pytest.mark.tryfirst
def pytest_runtest_makereport(item, call):
if isinstance(item, TestCaseFunction):

View File

@@ -15,7 +15,7 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
regen:
COLUMNS=76 regendoc --update *.txt */*.txt
PYTHONDONTWRITEBYTECODE=1 COLUMNS=76 regendoc --update *.txt */*.txt
help:
@echo "Please use \`make <target>' where <target> is one of"
@@ -40,7 +40,7 @@ clean:
-rm -rf $(BUILDDIR)/*
install: clean html
rsync -avz _build/html/ code:www-pytest/2.0.0
rsync -avz _build/html/ code:www-pytest/
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html

View File

@@ -7,7 +7,7 @@
released versions in the <a href="http://pypi.python.org/pypi/pytest">Python
Package Index</a>.</p>
{% else %}
<p><b><a href="{{ pathto('announce/index')}}">{{ version }} release</a></b>
<p><b><a href="{{ pathto('announce/index')}}">{{ release }} release</a></b>
[<a href="{{ pathto('changelog') }}">Changelog</a>]</p>
<p>
<a href="http://pypi.python.org/pypi/pytest">pytest on PyPI</a>

View File

@@ -19,6 +19,7 @@
<a href="{{ pathto('getting-started') }}">install</a>&nbsp;|&nbsp;
<a href="{{ pathto('example/index') }}">examples</a>&nbsp;|&nbsp;
<a href="{{ pathto('customize') }}">customize</a>&nbsp;|&nbsp;
<a href="http://https://bitbucket.org/hpk42/pytest/issues?status=new&status=open">issues</a>|&nbsp;
<a href="{{ pathto('contact') }}">contact</a>&nbsp;
</div>
</div>

View File

@@ -5,5 +5,7 @@ Release announcements
.. toctree::
:maxdepth: 2
release-2.0.2
release-2.0.1
release-2.0.0

View File

@@ -117,7 +117,7 @@ Important Notes
- py.test.collect.Directory does not exist anymore and it
is not possible to provide an own "Directory" object.
If you have used this and don't know what to do, get
in contact. We'll figure someting out.
in contact. We'll figure something out.
Note that pytest_collect_directory() is still called but
any return value will be ignored. This allows to keep

View File

@@ -0,0 +1,67 @@
py.test 2.0.1: bug fixes
===========================================================================
Welcome to pytest-2.0.1, a maintenance and bug fix release of pytest,
a mature testing tool for Python, supporting CPython 2.4-3.2, Jython
and latest PyPy interpreters. See extensive docs with tested examples here:
http://pytest.org/
If you want to install or upgrade pytest, just type one of::
pip install -U pytest # or
easy_install -U pytest
Many thanks to all issue reporters and people asking questions or
complaining. Particular thanks to Floris Bruynooghe and Ronny Pfannschmidt
for their great coding contributions and many others for feedback and help.
best,
holger krekel
Changes between 2.0.0 and 2.0.1
----------------------------------------------
- refine and unify initial capturing so that it works nicely
even if the logging module is used on an early-loaded conftest.py
file or plugin.
- fix issue12 - show plugin versions with "--version" and
"--traceconfig" and also document how to add extra information
to reporting test header
- fix issue17 (import-* reporting issue on python3) by
requiring py>1.4.0 (1.4.1 is going to include it)
- fix issue10 (numpy arrays truth checking) by refining
assertion interpretation in py lib
- fix issue15: make nose compatibility tests compatible
with python3 (now that nose-1.0 supports python3)
- remove somewhat surprising "same-conftest" detection because
it ignores conftest.py when they appear in several subdirs.
- improve assertions ("not in"), thanks Floris Bruynooghe
- improve behaviour/warnings when running on top of "python -OO"
(assertions and docstrings are turned off, leading to potential
false positives)
- introduce a pytest_cmdline_processargs(args) hook
to allow dynamic computation of command line arguments.
This fixes a regression because py.test prior to 2.0
allowed to set command line options from conftest.py
files which so far pytest-2.0 only allowed from ini-files now.
- fix issue7: assert failures in doctest modules.
unexpected failures in doctests will not generally
show nicer, i.e. within the doctest failing context.
- fix issue9: setup/teardown functions for an xfail-marked
test will report as xfail if they fail but report as normally
passing (not xpassing) if they succeed. This only is true
for "direct" setup/teardown invocations because teardown_class/
teardown_module cannot closely relate to a single test.
- fix issue14: no logging errors at process exit
- refinements to "collecting" output on non-ttys
- refine internal plugin registration and --traceconfig output
- introduce a mechanism to prevent/unregister plugins from the
command line, see http://pytest.org/plugins.html#cmdunregister
- activate resultlog plugin by default
- fix regression wrt yielded tests which due to the
collection-before-running semantics were not
setup as with pytest 1.3.4. Note, however, that
the recommended and much cleaner way to do test
parametrization remains the "pytest_generate_tests"
mechanism, see the docs.

View File

@@ -0,0 +1,73 @@
py.test 2.0.2: bug fixes, improved xfail/skip expressions, speedups
===========================================================================
Welcome to pytest-2.0.2, a maintenance and bug fix release of pytest,
a mature testing tool for Python, supporting CPython 2.4-3.2, Jython
and latest PyPy interpreters. See the extensive docs with tested examples here:
http://pytest.org/
If you want to install or upgrade pytest, just type one of::
pip install -U pytest # or
easy_install -U pytest
Many thanks to all issue reporters and people asking questions
or complaining, particularly Jurko for his insistence,
Laura, Victor and Brianna for helping with improving
and Ronny for his general advise.
best,
holger krekel
Changes between 2.0.1 and 2.0.2
----------------------------------------------
- tackle issue32 - speed up test runs of very quick test functions
by reducing the relative overhead
- fix issue30 - extended xfail/skipif handling and improved reporting.
If you have a syntax error in your skip/xfail
expressions you now get nice error reports.
Also you can now access module globals from xfail/skipif
expressions so that this for example works now::
import pytest
import mymodule
@pytest.mark.skipif("mymodule.__version__[0] == "1")
def test_function():
pass
This will not run the test function if the module's version string
does not start with a "1". Note that specifying a string instead
of a boolean expressions allows py.test to report meaningful information
when summarizing a test run as to what conditions lead to skipping
(or xfail-ing) tests.
- fix issue28 - setup_method and pytest_generate_tests work together
The setup_method fixture method now gets called also for
test function invocations generated from the pytest_generate_tests
hook.
- fix issue27 - collectonly and keyword-selection (-k) now work together
Also, if you do "py.test --collectonly -q" you now get a flat list
of test ids that you can use to paste to the py.test commandline
in order to execute a particular test.
- fix issue25 avoid reported problems with --pdb and python3.2/encodings output
- fix issue23 - tmpdir argument now works on Python3.2 and WindowsXP
Starting with Python3.2 os.symlink may be supported. By requiring
a newer py lib version the py.path.local() implementation acknowledges
this.
- fixed typos in the docs (thanks Victor Garcia, Brianna Laugher) and particular
thanks to Laura Creighton who also revieved parts of the documentation.
- fix slighly wrong output of verbose progress reporting for classes
(thanks Amaury)
- more precise (avoiding of) deprecation warnings for node.Class|Function accesses
- avoid std unittest assertion helper code in tracebacks (thanks Ronny)

View File

@@ -1,15 +1,15 @@
Writing and reporting of assertions in tests
============================================
The writing and reporting of assertions in tests
==================================================
.. _`assert with the assert statement`:
assert with the ``assert`` statement
---------------------------------------------------------
``py.test`` allows to use the standard python ``assert`` for verifying
``py.test`` allows you to use the standard python ``assert`` for verifying
expectations and values in Python tests. For example, you can write the
following in your tests::
following::
# content of test_assert1.py
def f():
@@ -18,49 +18,52 @@ following in your tests::
def test_function():
assert f() == 4
to state that your object has a certain ``attribute``. In case this
to assert that your object returns a certain value. If this
assertion fails you will see the value of ``x``::
$ py.test test_assert1.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_assert1.py
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 1 items
test_assert1.py F
================================= FAILURES =================================
______________________________ test_function _______________________________
def test_function():
> assert f() == 4
E assert 3 == 4
E + where 3 = f()
test_assert1.py:5: AssertionError
========================= 1 failed in 0.03 seconds =========================
========================= 1 failed in 0.07 seconds =========================
Reporting details about the failing assertion is achieved by re-evaluating
the assert expression and recording intermediate values.
the assert expression and recording the intermediate values.
Note: If evaluating the assert expression has side effects you may get a
warning that the intermediate values could not be determined safely. A
common example for this issue is reading from a file and comparing in one
line::
common example of this issue is an assertion which reads from a file::
assert f.read() != '...'
This might fail but when re-interpretation comes along it might pass.
You can rewrite this (and any other expression with side effects) easily, though:
If this assertion fails then the re-evaluation will probably succeed!
This is because ``f.read()`` will return an empty string when it is
called the second time during the re-evaluation. However, it is
easy to rewrite the assertion and avoid any trouble::
content = f.read()
assert content != '...'
assertions about expected exceptions
------------------------------------------
In order to write assertions about raised exceptions, you can use
``pytest.raises`` as a context manager like this::
import pytest
with pytest.raises(ZeroDivisionError):
1 / 0
@@ -91,7 +94,7 @@ Making use of context-sensitive comparisons
.. versionadded:: 2.0
py.test has rich support for providing context-sensitive informations
py.test has rich support for providing context-sensitive information
when it encounters comparisons. For example::
# content of test_assert2.py
@@ -105,14 +108,14 @@ if you run this module::
$ py.test test_assert2.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_assert2.py
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 1 items
test_assert2.py F
================================= FAILURES =================================
___________________________ test_set_comparison ____________________________
def test_set_comparison():
set1 = set("1308")
set2 = set("8035")
@@ -122,7 +125,7 @@ if you run this module::
E '1'
E Extra items in the right set:
E '5'
test_assert2.py:5: AssertionError
========================= 1 failed in 0.02 seconds =========================

View File

@@ -12,7 +12,7 @@ You can always use an interactive Python prompt and type::
import pytest
help(pytest)
to get an overview on available globally available helpers.
to get an overview on the globally available helpers.
.. automodule:: pytest
:members:
@@ -27,28 +27,26 @@ You can ask for available builtin or project-custom
pytestconfig
the pytest config object with access to command line opts.
capsys
captures writes to sys.stdout/sys.stderr and makes
them available successively via a ``capsys.readouterr()`` method
which returns a ``(out, err)`` tuple of captured snapshot strings.
enables capturing of writes to sys.stdout/sys.stderr and makes
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple.
capfd
captures writes to file descriptors 1 and 2 and makes
snapshotted ``(out, err)`` string tuples available
via the ``capsys.readouterr()`` method. If the underlying
platform does not have ``os.dup`` (e.g. Jython) tests using
this funcarg will automatically skip.
enables capturing of writes to file descriptors 1 and 2 and makes
captured output available via ``capsys.readouterr()`` method calls
which return a ``(out, err)`` tuple.
tmpdir
return a temporary directory path object
unique to each test function invocation,
which is unique to each test function invocation,
created as a sub directory of the base temporary
directory. The returned object is a `py.path.local`_
path object.
monkeypatch
The returned ``monkeypatch`` funcarg provides these
helper methods to modify objects, dictionaries or os.environ::
monkeypatch.setattr(obj, name, value, raising=True)
monkeypatch.delattr(obj, name, raising=True)
monkeypatch.setitem(mapping, name, value)
@@ -56,15 +54,18 @@ You can ask for available builtin or project-custom
monkeypatch.setenv(name, value, prepend=False)
monkeypatch.delenv(name, value, raising=True)
monkeypatch.syspath_prepend(path)
All modifications will be undone when the requesting
test function finished its execution. The ``raising``
All modifications will be undone after the requesting
test function has finished. The ``raising``
parameter determines if a KeyError or AttributeError
will be raised if the set/deletion operation has no target.
recwarn
Return a WarningsRecorder instance that provides these methods:
* ``pop(category=None)``: return last warning matching the category.
* ``clear()``: clear list of warnings
See http://docs.python.org/library/warnings.html for information
on warning categories.

View File

@@ -1,31 +1,36 @@
.. _`captures`:
Capturing of stdout/stderr output
Capturing of the stdout/stderr output
=========================================================
By default ``stdout`` and ``stderr`` output is captured separately for
setup and test execution code. If a test or a setup method fails its
according output will usually be shown along with the failure traceback.
In addition, ``stdin`` is set to a "null" object which will fail all
attempts to read from it. This is important if some code paths in
test otherwise might lead to waiting for input - which is usually
not desired when running automated tests.
Default stdout/stderr/stdin capturing behaviour
---------------------------------------------------------
During test execution any output sent to ``stdout`` and ``stderr`` is
captured. If a test or a setup method fails its according captured
output will usually be shown along with the failure traceback.
In addition, ``stdin`` is set to a "null" object which will
fail on attempts to read from it because it is rarely desired
to wait for interactive input when running automated tests.
By default capturing is done by intercepting writes to low level
file descriptors. This allows to capture output from simple
print statements as well as output from a subprocess started by
a test.
Setting capturing methods or disabling capturing
-------------------------------------------------
There are two ways in which ``py.test`` can perform capturing:
* ``fd`` level capturing (default): All writes going to the operating
system file descriptors 1 and 2 will be captured, for example writes such
as ``os.write(1, 'hello')``. Capturing on ``fd``-level also includes
**output from subprocesses**.
* file descriptor (FD) level capturing (default): All writes going to the
operating system file descriptors 1 and 2 will be captured.
* ``sys`` level capturing: The ``sys.stdout`` and ``sys.stderr`` will
will be replaced with in-memory files and the ``print`` builtin or
output from code like ``sys.stderr.write(...)`` will be captured with
this method.
* ``sys`` level capturing: Only writes to Python files ``sys.stdout``
and ``sys.stderr`` will be captured. No capturing of writes to
filedescriptors is performed.
.. _`disable capturing`:
@@ -35,14 +40,46 @@ You can influence output capturing mechanisms from the command line::
py.test --capture=sys # replace sys.stdout/stderr with in-mem files
py.test --capture=fd # also point filedescriptors 1 and 2 to temp file
If you set capturing values in a conftest file like this::
.. _printdebugging:
# conftest.py
option_capture = 'fd'
Using print statements for debugging
---------------------------------------------------
then all tests in that directory will execute with "fd" style capturing.
One primary benefit of the default capturing of stdout/stderr output
is that you can use print statements for debugging::
_ `printdebugging`:
# content of test_module.py
def setup_function(function):
print ("setting up %s" % function)
def test_func1():
assert True
def test_func2():
assert False
and running this module will show you precisely the output
of the failing function and hide the other one::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 2 items
test_module.py .F
================================= FAILURES =================================
________________________________ test_func2 ________________________________
def test_func2():
> assert False
E assert False
test_module.py:9: AssertionError
----------------------------- Captured stdout ------------------------------
setting up <function test_func2 at 0x1cda9b0>
==================== 1 failed, 1 passed in 0.02 seconds ====================
Accessing captured output from a test function
---------------------------------------------------

View File

@@ -42,14 +42,14 @@ master_doc = 'index'
# General information about the project.
project = u'pytest'
copyright = u'2010, holger krekel et aliter'
copyright = u'2011, holger krekel et alii'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '2.0.0'
version = '2.0'
# The full version, including alpha/beta/rc tags.
import py, pytest
assert py.path.local().relto(py.path.local(pytest.__file__).dirpath().dirpath())
@@ -185,7 +185,7 @@ htmlhelp_basename = 'pytestdoc'
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'pytest.tex', u'pytest Documentation',
u'holger krekel et aliter', 'manual'),
u'holger krekel et alii', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -218,7 +218,7 @@ latex_documents = [
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'pytest', u'pytest Documentation',
[u'holger krekel et aliter'], 1)
[u'holger krekel et alii'], 1)
]
@@ -226,9 +226,9 @@ man_pages = [
# Bibliographic Dublin Core info.
epub_title = u'pytest'
epub_author = u'holger krekel et aliter'
epub_publisher = u'holger krekel et aliter'
epub_copyright = u'2010, holger krekel et aliter'
epub_author = u'holger krekel et alii'
epub_publisher = u'holger krekel et alii'
epub_copyright = u'2010, holger krekel et alii'
# The language of the text. It defaults to the language option
# or en if the language is not set.

View File

@@ -15,7 +15,7 @@ Contact channels
- #pylib on irc.freenode.net IRC channel for random questions.
- private mail to Holger.Krekel at gmail com if you have sensitive issues to communicate
- private mail to Holger.Krekel at gmail com if you want to communicate sensitive issues
- `commit mailing list`_

View File

@@ -4,20 +4,20 @@ basic test configuration
Command line options and configuration file settings
-----------------------------------------------------------------
You can get help on options and ini-config values by running::
You can get help on command line options and values in INI-style
configurations files by using the general help option::
py.test -h # prints options _and_ config file settings
This will display command line and configuration file settings
which were registered by installed plugins.
How test configuration is read from configuration INI-files
-------------------------------------------------------------
how test configuration is read from setup/tox ini-files
--------------------------------------------------------
py.test searched for the first matching ini-style configuration file
py.test searches for the first matching ini-style configuration file
in the directories of command line argument and the directories above.
It looks for filenames in this order::
It looks for file basenames in this order::
pytest.ini
tox.ini
@@ -44,29 +44,27 @@ is used to start the search.
.. _`how to change command line options defaults`:
.. _`adding default options`:
how to change command line options defaults
How to change command line options defaults
------------------------------------------------
py.test provides a simple way to set some default
command line options. For example, if you want
to always see detailed info on skipped and xfailed
tests, as well as have terser "dot progress output",
you can add this to your root directory::
It can be tedious to type the same series of command line options
every time you use py.test . For example, if you always want to see
detailed info on skipped and xfailed tests, as well as have terser "dot"
progress output, you can write it into a configuration file::
# content of pytest.ini
# (or tox.ini or setup.cfg)
[pytest]
addopts = -rsxX -q
From now on, running ``py.test`` will implicitely add
the specified options.
From now on, running ``py.test`` will add the specified options.
builtin configuration file options
----------------------------------------------
.. confval:: minversion
specifies a minimal pytest version needed for running tests.
specifies a minimal pytest version required for running tests.
minversion = 2.1 # will fail if we run with pytest-2.0
@@ -97,14 +95,14 @@ builtin configuration file options
[!seq] matches any char not in seq
Default patterns are ``.* _* CVS {args}``. Setting a ``norecurse``
replaces the default. Here is a customizing example for avoiding
a different set of directories::
replaces the default. Here is an example of how to avoid
certain directories::
# content of setup.cfg
[pytest]
norecursedirs = .svn _build tmp*
This would tell py.test to not recurse into typical subversion or
This would tell py.test to not look into typical subversion or
sphinx-build directories or into any ``tmp`` prefixed directory.
.. confval:: python_files

View File

@@ -22,7 +22,7 @@ download and unpack a TAR file::
http://pypi.python.org/pypi/pytest/
activating a checkout with setuptools
Activating a checkout with setuptools
--------------------------------------------
With a working Distribute_ or setuptools_ installation you can type::
@@ -31,4 +31,10 @@ With a working Distribute_ or setuptools_ installation you can type::
in order to work inline with the tools and the lib of your checkout.
If this command complains that it could not find the required version
of "py" then you need to use the development pypi repository::
python setup.py develop -i http://pypi.testrun.org
.. include:: links.inc

View File

@@ -1,5 +1,5 @@
doctest integration for modules and test files.
doctest integration for modules and test files
=========================================================
By default all files matching the ``test*.txt`` pattern will
@@ -15,11 +15,11 @@ python test modules)::
py.test --doctest-modules
You can make these changes permanent in your project by
putting them into a conftest.py file like this::
putting them into a pytest.ini file like this::
# content of conftest.py
option_doctestmodules = True
option_doctestglob = "*.rst"
# content of pytest.ini
[pytest]
addopts = --doctest-modules
If you then have a text file like this::
@@ -35,7 +35,7 @@ and another like this::
# content of mymodule.py
def something():
""" a doctest in a docstring
>>> something()
>>> something()
42
"""
return 42
@@ -44,7 +44,9 @@ then you can just invoke ``py.test`` without command line options::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: /tmp/doc-exec-66
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 1 items
============================= in 0.00 seconds =============================
mymodule.py .
========================= 1 passed in 0.01 seconds =========================

View File

@@ -77,6 +77,22 @@ class TestSpecialisedExplanations(object):
def test_in_list(self):
assert 1 in [0, 2, 3, 4, 5]
def test_not_in_text_multiline(self):
text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail'
assert 'foo' not in text
def test_not_in_text_single(self):
text = 'single foo line'
assert 'foo' not in text
def test_not_in_text_single_long(self):
text = 'head ' * 50 + 'foo ' + 'tail ' * 20
assert 'foo' not in text
def test_not_in_text_single_long_term(self):
text = 'head ' * 50 + 'f'*70 + 'tail ' * 20
assert 'f'*70 not in text
def test_attribute():
class Foo(object):

View File

@@ -9,6 +9,6 @@ def test_failure_demo_fails_properly(testdir):
failure_demo.copy(testdir.tmpdir.join(failure_demo.basename))
result = testdir.runpytest(target)
result.stdout.fnmatch_lines([
"*35 failed*"
"*39 failed*"
])
assert result.ret != 0

View File

@@ -48,7 +48,7 @@ the output.
example: decorating a funcarg in a test module
--------------------------------------------------------------
For larger scale setups it's sometimes useful to decorare
For larger scale setups it's sometimes useful to decorate
a funcarg just for a particular test module. We can
extend the `accept example`_ by putting this in our test module:

View File

@@ -12,7 +12,7 @@ need more examples or have questions. Also take a look at the :ref:`comprehensiv
reportingdemo.txt
simple.txt
pythoncollection.txt
mysetup.txt
parametrize.txt
pythoncollection.txt
nonpython.txt

View File

@@ -26,7 +26,7 @@ Let's write a simple test function using a ``mysetup`` funcarg::
To run this test py.test needs to find and call a factory to
obtain the required ``mysetup`` function argument. To make
an according factory findable we write down a specifically named factory
method in a :ref:`local plugin`::
method in a :ref:`local plugin <localplugin>` ::
# content of conftest.py
from myapp import MyApp
@@ -49,15 +49,15 @@ You can now run the test::
$ py.test test_sample.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_sample.py
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 1 items
test_sample.py F
================================= FAILURES =================================
_______________________________ test_answer ________________________________
mysetup = <conftest.MySetup instance at 0x16f5998>
mysetup = <conftest.MySetup instance at 0xfc64d0>
def test_answer(mysetup):
app = mysetup.myapp()
@@ -122,12 +122,12 @@ Running it yields::
$ py.test test_ssh.py -rs
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_ssh.py
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 1 items
test_ssh.py s
========================= short test summary info ==========================
SKIP [1] /tmp/doc-exec-107/conftest.py:22: specify ssh host with --ssh
SKIP [1] /tmp/doc-exec-36/conftest.py:22: specify ssh host with --ssh
======================== 1 skipped in 0.02 seconds =========================

View File

@@ -27,8 +27,8 @@ now execute the test specification::
nonpython $ py.test test_simple.yml
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_simple.yml
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 2 items
test_simple.yml .F
@@ -37,9 +37,7 @@ now execute the test specification::
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
========================= short test summary info ==========================
FAIL test_simple.yml::hello
==================== 1 failed, 1 passed in 0.06 seconds ====================
==================== 1 failed, 1 passed in 0.51 seconds ====================
You get one dot for the passing ``sub1: sub1`` check and one failure.
Obviously in the above ``conftest.py`` you'll want to implement a more
@@ -58,8 +56,8 @@ reporting in ``verbose`` mode::
nonpython $ py.test -v
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30 -- /home/hpk/venv/0/bin/python
test path 1: /home/hpk/p/pytest/doc/example/nonpython
platform linux2 -- Python 2.6.6 -- pytest-2.0.2 -- /home/hpk/venv/0/bin/python
collecting ... collected 2 items
test_simple.yml:1: usecase: ok PASSED
test_simple.yml:1: usecase: hello FAILED
@@ -69,14 +67,17 @@ reporting in ``verbose`` mode::
usecase execution failed
spec failed: 'some': 'other'
no further details known at this point.
========================= short test summary info ==========================
FAIL test_simple.yml::hello
==================== 1 failed, 1 passed in 0.06 seconds ====================
While developing your custom test collection and execution it's also
interesting to just look at the collection tree::
nonpython $ py.test --collectonly
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 2 items
<YamlFile 'test_simple.yml'>
<YamlItem 'ok'>
<YamlItem 'hello'>
============================= in 0.06 seconds =============================

View File

@@ -1,4 +1,6 @@
.. _paramexamples:
parametrizing tests
=================================================
@@ -6,6 +8,142 @@ py.test allows to easily implement your own custom
parametrization scheme for tests. Here we provide
some examples for inspiration and re-use.
generating parameters combinations, depending on command line
----------------------------------------------------------------------------
.. regendoc:wipe
Let's say we want to execute a test with different parameters
and the parameter range shall be determined by a command
line argument. Let's first write a simple computation test::
# content of test_compute.py
def test_compute(param1):
assert param1 < 4
Now we add a test configuration like this::
# content of conftest.py
def pytest_addoption(parser):
parser.addoption("--all", action="store_true",
help="run all combinations")
def pytest_generate_tests(metafunc):
if 'param1' in metafunc.funcargnames:
if metafunc.config.option.all:
end = 5
else:
end = 2
for i in range(end):
metafunc.addcall(funcargs={'param1': i})
This means that we only run 2 tests if we do not pass ``--all``::
$ py.test -q test_compute.py
collecting ... collected 2 items
..
2 passed in 0.01 seconds
We run only two computations, so we see two dots.
let's run the full monty::
$ py.test -q --all
collecting ... collected 5 items
....F
================================= FAILURES =================================
_____________________________ test_compute[4] ______________________________
param1 = 4
def test_compute(param1):
> assert param1 < 4
E assert 4 < 4
test_compute.py:3: AssertionError
1 failed, 4 passed in 0.02 seconds
As expected when running the full range of ``param1`` values
we'll get an error on the last one.
Deferring the setup of parametrizing resources
---------------------------------------------------
.. regendoc:wipe
The parametrization of test functions happens at collection
time. It is often a good idea to setup possibly expensive
resources only when the actual test is run. Here is a simple
example how you can achieve that::
# content of test_backends.py
import pytest
def test_db_initialized(db):
# a dummy test
if db.__class__.__name__ == "DB2":
pytest.fail("deliberately failing for demo purposes")
Now we add a test configuration that takes care to generate
two invocations of the ``test_db_initialized`` function and
furthermore a factory that creates a database object when
each test is actually run::
# content of conftest.py
def pytest_generate_tests(metafunc):
if 'db' in metafunc.funcargnames:
metafunc.addcall(param="d1")
metafunc.addcall(param="d2")
class DB1:
"one database object"
class DB2:
"alternative database object"
def pytest_funcarg__db(request):
if request.param == "d1":
return DB1()
elif request.param == "d2":
return DB2()
else:
raise ValueError("invalid internal test config")
Let's first see how it looks like at collection time::
$ py.test test_backends.py --collectonly
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 2 items
<Module 'test_backends.py'>
<Function 'test_db_initialized[0]'>
<Function 'test_db_initialized[1]'>
============================= in 0.00 seconds =============================
And then when we run the test::
$ py.test -q test_backends.py
collecting ... collected 2 items
.F
================================= FAILURES =================================
__________________________ test_db_initialized[1] __________________________
db = <conftest.DB2 instance at 0x18afef0>
def test_db_initialized(db):
# a dummy test
if db.__class__.__name__ == "DB2":
> pytest.fail("deliberately failing for demo purposes")
E Failed: deliberately failing for demo purposes
test_backends.py:6: Failed
1 failed, 1 passed in 0.02 seconds
Now you see that one invocation of the test passes and another fails,
as it to be expected.
Parametrizing test methods through per-class configuration
--------------------------------------------------------------
@@ -41,11 +179,23 @@ Running it means we are two tests for each test functions, using
the respective settings::
$ py.test -q
F..F
collecting ... collected 6 items
.FF..F
================================= FAILURES =================================
__________________________ test_db_initialized[1] __________________________
db = <conftest.DB2 instance at 0x214ef38>
def test_db_initialized(db):
# a dummy test
if db.__class__.__name__ == "DB2":
> pytest.fail("deliberately failing for demo purposes")
E Failed: deliberately failing for demo purposes
test_backends.py:6: Failed
_________________________ TestClass.test_equals[0] _________________________
self = <test_parametrize.TestClass instance at 0x128a638>, a = 1, b = 2
self = <test_parametrize.TestClass instance at 0x2165050>, a = 1, b = 2
def test_equals(self, a, b):
> assert a == b
@@ -54,14 +204,14 @@ the respective settings::
test_parametrize.py:17: AssertionError
______________________ TestClass.test_zerodivision[1] ______________________
self = <test_parametrize.TestClass instance at 0x1296440>, a = 3, b = 2
self = <test_parametrize.TestClass instance at 0x2159248>, a = 3, b = 2
def test_zerodivision(self, a, b):
> pytest.raises(ZeroDivisionError, "a/b")
E Failed: DID NOT RAISE
test_parametrize.py:20: Failed
2 failed, 2 passed in 0.03 seconds
3 failed, 3 passed in 0.03 seconds
Parametrizing test methods through a decorator
--------------------------------------------------------------
@@ -97,11 +247,12 @@ for parametrizing test methods::
Running it gives similar results as before::
$ py.test -q test_parametrize2.py
collecting ... collected 4 items
F..F
================================= FAILURES =================================
_________________________ TestClass.test_equals[0] _________________________
self = <test_parametrize2.TestClass instance at 0x1dbcc68>, a = 1, b = 2
self = <test_parametrize2.TestClass instance at 0x1548518>, a = 1, b = 2
@params([dict(a=1, b=2), dict(a=3, b=3), ])
def test_equals(self, a, b):
@@ -111,7 +262,7 @@ Running it gives similar results as before::
test_parametrize2.py:19: AssertionError
______________________ TestClass.test_zerodivision[1] ______________________
self = <test_parametrize2.TestClass instance at 0x1dd0488>, a = 3, b = 2
self = <test_parametrize2.TestClass instance at 0x1553998>, a = 3, b = 2
@params([dict(a=1, b=0), dict(a=3, b=2)])
def test_zerodivision(self, a, b):
@@ -119,13 +270,13 @@ Running it gives similar results as before::
E Failed: DID NOT RAISE
test_parametrize2.py:23: Failed
2 failed, 2 passed in 0.03 seconds
2 failed, 2 passed in 0.02 seconds
checking serialization between Python interpreters
--------------------------------------------------------------
Here is a stripped down real-life example of using parametrized
testing for testing serialization betwee different interpreters.
testing for testing serialization between different interpreters.
We define a ``test_basic_objects`` function which is to be run
with different sets of arguments for its three arguments::
@@ -138,5 +289,6 @@ with different sets of arguments for its three arguments::
Running it (with Python-2.4 through to Python2.7 installed)::
. $ py.test -q multipython.py
collecting ... collected 75 items
....s....s....s....ssssss....s....s....s....ssssss....s....s....s....ssssss
48 passed, 27 skipped in 2.55 seconds
48 passed, 27 skipped in 3.74 seconds

View File

@@ -42,11 +42,16 @@ in functions and classes. For example, if we have::
then the test collection looks like this::
$ py.test --collectonly
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 2 items
<Module 'check_myapp.py'>
<Class 'CheckMyApp'>
<Instance '()'>
<Function 'check_simple'>
<Function 'check_complex'>
============================= in 0.01 seconds =============================
interpret cmdline arguments as Python packages
-----------------------------------------------------
@@ -76,9 +81,14 @@ finding out what is collected
You can always peek at the collection tree without running tests like this::
. $ py.test --collectonly pythoncollection.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 3 items
<Module 'pythoncollection.py'>
<Function 'test_function'>
<Class 'TestClass'>
<Instance '()'>
<Function 'test_method'>
<Function 'test_anothermethod'>
============================= in 0.06 seconds =============================

View File

@@ -13,11 +13,10 @@ get on the terminal - we are working on that):
assertion $ py.test failure_demo.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev38
collecting ...
collected 35 items
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 39 items
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
failure_demo.py FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
================================= FAILURES =================================
____________________________ test_generative[0] ____________________________
@@ -31,7 +30,7 @@ get on the terminal - we are working on that):
failure_demo.py:15: AssertionError
_________________________ TestFailing.test_simple __________________________
self = <failure_demo.TestFailing object at 0x2c9da90>
self = <failure_demo.TestFailing object at 0x20bd310>
def test_simple(self):
def f():
@@ -41,21 +40,21 @@ get on the terminal - we are working on that):
> assert f() == g()
E assert 42 == 43
E + where 42 = <function f at 0x2c447d0>()
E + and 43 = <function g at 0x2c44cf8>()
E + where 42 = <function f at 0x20b5ed8>()
E + and 43 = <function g at 0x210e230>()
failure_demo.py:28: AssertionError
____________________ TestFailing.test_simple_multiline _____________________
self = <failure_demo.TestFailing object at 0x2c9dc90>
self = <failure_demo.TestFailing object at 0x20bddd0>
def test_simple_multiline(self):
otherfunc_multi(
42,
> 6*9)
failure_demo.py:33:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:33:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
a = 42, b = 54
@@ -67,19 +66,19 @@ get on the terminal - we are working on that):
failure_demo.py:12: AssertionError
___________________________ TestFailing.test_not ___________________________
self = <failure_demo.TestFailing object at 0x2c93f10>
self = <failure_demo.TestFailing object at 0x210fa10>
def test_not(self):
def f():
return 42
> assert not f()
E assert not 42
E + where 42 = <function f at 0x2ca1050>()
E + where 42 = <function f at 0x210e1b8>()
failure_demo.py:38: AssertionError
_________________ TestSpecialisedExplanations.test_eq_text _________________
self = <failure_demo.TestSpecialisedExplanations object at 0x2c9d9d0>
self = <failure_demo.TestSpecialisedExplanations object at 0x210fc10>
def test_eq_text(self):
> assert 'spam' == 'eggs'
@@ -90,7 +89,7 @@ get on the terminal - we are working on that):
failure_demo.py:42: AssertionError
_____________ TestSpecialisedExplanations.test_eq_similar_text _____________
self = <failure_demo.TestSpecialisedExplanations object at 0x2a04e90>
self = <failure_demo.TestSpecialisedExplanations object at 0x20bd750>
def test_eq_similar_text(self):
> assert 'foo 1 bar' == 'foo 2 bar'
@@ -103,7 +102,7 @@ get on the terminal - we are working on that):
failure_demo.py:45: AssertionError
____________ TestSpecialisedExplanations.test_eq_multiline_text ____________
self = <failure_demo.TestSpecialisedExplanations object at 0x2c9d710>
self = <failure_demo.TestSpecialisedExplanations object at 0x2113950>
def test_eq_multiline_text(self):
> assert 'foo\nspam\nbar' == 'foo\neggs\nbar'
@@ -116,13 +115,13 @@ get on the terminal - we are working on that):
failure_demo.py:48: AssertionError
______________ TestSpecialisedExplanations.test_eq_long_text _______________
self = <failure_demo.TestSpecialisedExplanations object at 0x2c9db10>
self = <failure_demo.TestSpecialisedExplanations object at 0x20bdf10>
def test_eq_long_text(self):
a = '1'*100 + 'a' + '2'*100
b = '1'*100 + 'b' + '2'*100
> assert a == b
E assert '111111111111...2222222222222' == '111111111111...2222222222222'
E assert '111111111111...2222222222222' == '1111111111111...2222222222222'
E Skipping 90 identical leading characters in diff
E Skipping 91 identical trailing characters in diff
E - 1111111111a222222222
@@ -133,13 +132,13 @@ get on the terminal - we are working on that):
failure_demo.py:53: AssertionError
_________ TestSpecialisedExplanations.test_eq_long_text_multiline __________
self = <failure_demo.TestSpecialisedExplanations object at 0x2caf950>
self = <failure_demo.TestSpecialisedExplanations object at 0x2113e50>
def test_eq_long_text_multiline(self):
a = '1\n'*100 + 'a' + '2\n'*100
b = '1\n'*100 + 'b' + '2\n'*100
> assert a == b
E assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n...n2\n2\n2\n2\n'
E assert '1\n1\n1\n1\n...n2\n2\n2\n2\n' == '1\n1\n1\n1\n1...n2\n2\n2\n2\n'
E Skipping 190 identical leading characters in diff
E Skipping 191 identical trailing characters in diff
E 1
@@ -157,7 +156,7 @@ get on the terminal - we are working on that):
failure_demo.py:58: AssertionError
_________________ TestSpecialisedExplanations.test_eq_list _________________
self = <failure_demo.TestSpecialisedExplanations object at 0x2caf590>
self = <failure_demo.TestSpecialisedExplanations object at 0x20bdfd0>
def test_eq_list(self):
> assert [0, 1, 2] == [0, 1, 3]
@@ -167,7 +166,7 @@ get on the terminal - we are working on that):
failure_demo.py:61: AssertionError
______________ TestSpecialisedExplanations.test_eq_list_long _______________
self = <failure_demo.TestSpecialisedExplanations object at 0x2c9e310>
self = <failure_demo.TestSpecialisedExplanations object at 0x2113e10>
def test_eq_list_long(self):
a = [0]*100 + [1] + [3]*100
@@ -179,7 +178,7 @@ get on the terminal - we are working on that):
failure_demo.py:66: AssertionError
_________________ TestSpecialisedExplanations.test_eq_dict _________________
self = <failure_demo.TestSpecialisedExplanations object at 0x2c9dc50>
self = <failure_demo.TestSpecialisedExplanations object at 0x21245d0>
def test_eq_dict(self):
> assert {'a': 0, 'b': 1} == {'a': 0, 'b': 2}
@@ -192,7 +191,7 @@ get on the terminal - we are working on that):
failure_demo.py:69: AssertionError
_________________ TestSpecialisedExplanations.test_eq_set __________________
self = <failure_demo.TestSpecialisedExplanations object at 0x2cafc10>
self = <failure_demo.TestSpecialisedExplanations object at 0x2124f50>
def test_eq_set(self):
> assert set([0, 10, 11, 12]) == set([0, 20, 21])
@@ -208,7 +207,7 @@ get on the terminal - we are working on that):
failure_demo.py:72: AssertionError
_____________ TestSpecialisedExplanations.test_eq_longer_list ______________
self = <failure_demo.TestSpecialisedExplanations object at 0x2cba890>
self = <failure_demo.TestSpecialisedExplanations object at 0x2130650>
def test_eq_longer_list(self):
> assert [1,2] == [1,2,3]
@@ -218,13 +217,70 @@ get on the terminal - we are working on that):
failure_demo.py:75: AssertionError
_________________ TestSpecialisedExplanations.test_in_list _________________
self = <failure_demo.TestSpecialisedExplanations object at 0x2cba6d0>
self = <failure_demo.TestSpecialisedExplanations object at 0x20bd5d0>
def test_in_list(self):
> assert 1 in [0, 2, 3, 4, 5]
E assert 1 in [0, 2, 3, 4, 5]
failure_demo.py:78: AssertionError
__________ TestSpecialisedExplanations.test_not_in_text_multiline __________
self = <failure_demo.TestSpecialisedExplanations object at 0x21136d0>
def test_not_in_text_multiline(self):
text = 'some multiline\ntext\nwhich\nincludes foo\nand a\ntail'
> assert 'foo' not in text
E assert 'foo' not in 'some multiline\ntext\nw...ncludes foo\nand a\ntail'
E 'foo' is contained here:
E some multiline
E text
E which
E includes foo
E ? +++
E and a
E tail
failure_demo.py:82: AssertionError
___________ TestSpecialisedExplanations.test_not_in_text_single ____________
self = <failure_demo.TestSpecialisedExplanations object at 0x2124950>
def test_not_in_text_single(self):
text = 'single foo line'
> assert 'foo' not in text
E assert 'foo' not in 'single foo line'
E 'foo' is contained here:
E single foo line
E ? +++
failure_demo.py:86: AssertionError
_________ TestSpecialisedExplanations.test_not_in_text_single_long _________
self = <failure_demo.TestSpecialisedExplanations object at 0x2130610>
def test_not_in_text_single_long(self):
text = 'head ' * 50 + 'foo ' + 'tail ' * 20
> assert 'foo' not in text
E assert 'foo' not in 'head head head head hea...ail tail tail tail tail '
E 'foo' is contained here:
E head head foo tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
E ? +++
failure_demo.py:90: AssertionError
______ TestSpecialisedExplanations.test_not_in_text_single_long_term _______
self = <failure_demo.TestSpecialisedExplanations object at 0x2130690>
def test_not_in_text_single_long_term(self):
text = 'head ' * 50 + 'f'*70 + 'tail ' * 20
> assert 'f'*70 not in text
E assert 'fffffffffff...ffffffffffff' not in 'head head he...l tail tail '
E 'ffffffffffffffffff...fffffffffffffffffff' is contained here:
E head head fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffftail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail tail
E ? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
failure_demo.py:94: AssertionError
______________________________ test_attribute ______________________________
def test_attribute():
@@ -233,9 +289,9 @@ get on the terminal - we are working on that):
i = Foo()
> assert i.b == 2
E assert 1 == 2
E + where 1 = <failure_demo.Foo object at 0x2c9d750>.b
E + where 1 = <failure_demo.Foo object at 0x2113c10>.b
failure_demo.py:85: AssertionError
failure_demo.py:101: AssertionError
_________________________ test_attribute_instance __________________________
def test_attribute_instance():
@@ -243,10 +299,10 @@ get on the terminal - we are working on that):
b = 1
> assert Foo().b == 2
E assert 1 == 2
E + where 1 = <failure_demo.Foo object at 0x2cafdd0>.b
E + where <failure_demo.Foo object at 0x2cafdd0> = <class 'failure_demo.Foo'>()
E + where 1 = <failure_demo.Foo object at 0x2124d90>.b
E + where <failure_demo.Foo object at 0x2124d90> = <class 'failure_demo.Foo'>()
failure_demo.py:91: AssertionError
failure_demo.py:107: AssertionError
__________________________ test_attribute_failure __________________________
def test_attribute_failure():
@@ -257,16 +313,16 @@ get on the terminal - we are working on that):
i = Foo()
> assert i.b == 2
failure_demo.py:100:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:116:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <failure_demo.Foo object at 0x2cba790>
self = <failure_demo.Foo object at 0x2130b90>
def _get_b(self):
> raise Exception('Failed to get attrib')
E Exception: Failed to get attrib
failure_demo.py:97: Exception
failure_demo.py:113: Exception
_________________________ test_attribute_multiple __________________________
def test_attribute_multiple():
@@ -276,57 +332,57 @@ get on the terminal - we are working on that):
b = 2
> assert Foo().b == Bar().b
E assert 1 == 2
E + where 1 = <failure_demo.Foo object at 0x2cba210>.b
E + where <failure_demo.Foo object at 0x2cba210> = <class 'failure_demo.Foo'>()
E + and 2 = <failure_demo.Bar object at 0x2cba850>.b
E + where <failure_demo.Bar object at 0x2cba850> = <class 'failure_demo.Bar'>()
E + where 1 = <failure_demo.Foo object at 0x2130150>.b
E + where <failure_demo.Foo object at 0x2130150> = <class 'failure_demo.Foo'>()
E + and 2 = <failure_demo.Bar object at 0x2130850>.b
E + where <failure_demo.Bar object at 0x2130850> = <class 'failure_demo.Bar'>()
failure_demo.py:108: AssertionError
failure_demo.py:124: AssertionError
__________________________ TestRaises.test_raises __________________________
self = <failure_demo.TestRaises instance at 0x2cc2560>
self = <failure_demo.TestRaises instance at 0x213ac68>
def test_raises(self):
s = 'qwe'
> raises(TypeError, "int(s)")
failure_demo.py:117:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:133:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
> int(s)
E ValueError: invalid literal for int() with base 10: 'qwe'
<0-codegen /home/hpk/p/pytest/_pytest/python.py:819>:1: ValueError
<0-codegen /home/hpk/p/pytest/_pytest/python.py:837>:1: ValueError
______________________ TestRaises.test_raises_doesnt _______________________
self = <failure_demo.TestRaises instance at 0x2cb6bd8>
self = <failure_demo.TestRaises instance at 0x21373b0>
def test_raises_doesnt(self):
> raises(IOError, "int('3')")
E Failed: DID NOT RAISE
failure_demo.py:120: Failed
failure_demo.py:136: Failed
__________________________ TestRaises.test_raise ___________________________
self = <failure_demo.TestRaises instance at 0x2cc4830>
self = <failure_demo.TestRaises instance at 0x213bf80>
def test_raise(self):
> raise ValueError("demo error")
E ValueError: demo error
failure_demo.py:123: ValueError
failure_demo.py:139: ValueError
________________________ TestRaises.test_tupleerror ________________________
self = <failure_demo.TestRaises instance at 0x2cc5560>
self = <failure_demo.TestRaises instance at 0x213ccb0>
def test_tupleerror(self):
> a,b = [1]
E ValueError: need more than 1 value to unpack
failure_demo.py:126: ValueError
failure_demo.py:142: ValueError
______ TestRaises.test_reinterpret_fails_with_print_for_the_fun_of_it ______
self = <failure_demo.TestRaises instance at 0x2cc6248>
self = <failure_demo.TestRaises instance at 0x213d998>
def test_reinterpret_fails_with_print_for_the_fun_of_it(self):
l = [1,2,3]
@@ -334,18 +390,18 @@ get on the terminal - we are working on that):
> a,b = l.pop()
E TypeError: 'int' object is not iterable
failure_demo.py:131: TypeError
failure_demo.py:147: TypeError
----------------------------- Captured stdout ------------------------------
l is [1, 2, 3]
________________________ TestRaises.test_some_error ________________________
self = <failure_demo.TestRaises instance at 0x2cc6f38>
self = <failure_demo.TestRaises instance at 0x213f710>
def test_some_error(self):
> if namenotexi:
E NameError: global name 'namenotexi' is not defined
failure_demo.py:134: NameError
failure_demo.py:150: NameError
____________________ test_dynamic_compile_shows_nicely _____________________
def test_dynamic_compile_shows_nicely():
@@ -357,17 +413,17 @@ get on the terminal - we are working on that):
py.std.sys.modules[name] = module
> module.foo()
failure_demo.py:149:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:165:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
def foo():
> assert 1 == 0
E assert 1 == 0
<2-codegen 'abc-123' /home/hpk/p/pytest/doc/example/assertion/failure_demo.py:146>:2: AssertionError
<2-codegen 'abc-123' /home/hpk/p/pytest/doc/example/assertion/failure_demo.py:162>:2: AssertionError
____________________ TestMoreErrors.test_complex_error _____________________
self = <failure_demo.TestMoreErrors instance at 0x2cc4050>
self = <failure_demo.TestMoreErrors instance at 0x213b5a8>
def test_complex_error(self):
def f():
@@ -376,16 +432,16 @@ get on the terminal - we are working on that):
return 43
> somefunc(f(), g())
failure_demo.py:159:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:175:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
x = 44, y = 43
def somefunc(x,y):
> otherfunc(x,y)
failure_demo.py:8:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
failure_demo.py:8:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
a = 44, b = 43
@@ -396,39 +452,40 @@ get on the terminal - we are working on that):
failure_demo.py:5: AssertionError
___________________ TestMoreErrors.test_z1_unpack_error ____________________
self = <failure_demo.TestMoreErrors instance at 0x2cc7ab8>
self = <failure_demo.TestMoreErrors instance at 0x2143440>
def test_z1_unpack_error(self):
l = []
> a,b = l
E ValueError: need more than 0 values to unpack
failure_demo.py:163: ValueError
failure_demo.py:179: ValueError
____________________ TestMoreErrors.test_z2_type_error _____________________
self = <failure_demo.TestMoreErrors instance at 0x2ccb8c0>
self = <failure_demo.TestMoreErrors instance at 0x21440e0>
def test_z2_type_error(self):
l = 3
> a,b = l
E TypeError: 'int' object is not iterable
failure_demo.py:167: TypeError
failure_demo.py:183: TypeError
______________________ TestMoreErrors.test_startswith ______________________
self = <failure_demo.TestMoreErrors instance at 0x2ccd5f0>
self = <failure_demo.TestMoreErrors instance at 0x2144d88>
def test_startswith(self):
s = "123"
g = "456"
> assert s.startswith(g)
E assert <built-in method startswith of str object at 0x2c321b0>('456')
E + where <built-in method startswith of str object at 0x2c321b0> = '123'.startswith
E assert False
E + where False = <built-in method startswith of str object at 0x20a3810>('456')
E + where <built-in method startswith of str object at 0x20a3810> = '123'.startswith
failure_demo.py:172: AssertionError
failure_demo.py:188: AssertionError
__________________ TestMoreErrors.test_startswith_nested ___________________
self = <failure_demo.TestMoreErrors instance at 0x2ccbc20>
self = <failure_demo.TestMoreErrors instance at 0x2144320>
def test_startswith_nested(self):
def f():
@@ -436,47 +493,49 @@ get on the terminal - we are working on that):
def g():
return "456"
> assert f().startswith(g())
E assert <built-in method startswith of str object at 0x2c321b0>('456')
E + where <built-in method startswith of str object at 0x2c321b0> = '123'.startswith
E + where '123' = <function f at 0x2c2d140>()
E + and '456' = <function g at 0x2cb00c8>()
E assert False
E + where False = <built-in method startswith of str object at 0x20a3810>('456')
E + where <built-in method startswith of str object at 0x20a3810> = '123'.startswith
E + where '123' = <function f at 0x211a500>()
E + and '456' = <function g at 0x212dde8>()
failure_demo.py:179: AssertionError
failure_demo.py:195: AssertionError
_____________________ TestMoreErrors.test_global_func ______________________
self = <failure_demo.TestMoreErrors instance at 0x2cb69e0>
self = <failure_demo.TestMoreErrors instance at 0x21345a8>
def test_global_func(self):
> assert isinstance(globf(42), float)
E assert isinstance(43, float)
E + where 43 = globf(42)
E assert False
E + where False = isinstance(43, float)
E + where 43 = globf(42)
failure_demo.py:182: AssertionError
failure_demo.py:198: AssertionError
_______________________ TestMoreErrors.test_instance _______________________
self = <failure_demo.TestMoreErrors instance at 0x2cc6440>
self = <failure_demo.TestMoreErrors instance at 0x213c998>
def test_instance(self):
self.x = 6*7
> assert self.x != 42
E assert 42 != 42
E + where 42 = 42
E + where 42 = <failure_demo.TestMoreErrors instance at 0x2cc6440>.x
E + where 42 = <failure_demo.TestMoreErrors instance at 0x213c998>.x
failure_demo.py:186: AssertionError
failure_demo.py:202: AssertionError
_______________________ TestMoreErrors.test_compare ________________________
self = <failure_demo.TestMoreErrors instance at 0x2dcc200>
self = <failure_demo.TestMoreErrors instance at 0x2257998>
def test_compare(self):
> assert globf(10) < 5
E assert 11 < 5
E + where 11 = globf(10)
failure_demo.py:189: AssertionError
failure_demo.py:205: AssertionError
_____________________ TestMoreErrors.test_try_finally ______________________
self = <failure_demo.TestMoreErrors instance at 0x2dce0e0>
self = <failure_demo.TestMoreErrors instance at 0x2258908>
def test_try_finally(self):
x = 1
@@ -484,5 +543,5 @@ get on the terminal - we are working on that):
> assert x == 0
E assert 1 == 0
failure_demo.py:194: AssertionError
======================== 35 failed in 0.19 seconds =========================
failure_demo.py:210: AssertionError
======================== 39 failed in 0.19 seconds =========================

View File

@@ -7,6 +7,8 @@ basic patterns and examples
pass different values to a test function, depending on command line options
----------------------------------------------------------------------------
.. regendoc:wipe
Suppose we want to write a test that depends on a command line option.
Here is a basic pattern how to achieve this::
@@ -32,7 +34,8 @@ provide the ``cmdopt`` through a :ref:`function argument <funcarg>` factory::
Let's run this without supplying our new command line option::
$ py.test -q
$ py.test -q test_sample.py
collecting ... collected 1 items
F
================================= FAILURES =================================
_______________________________ test_answer ________________________________
@@ -55,6 +58,7 @@ Let's run this without supplying our new command line option::
And now with supplying a command line option::
$ py.test -q --cmdopt=type2
collecting ... collected 1 items
F
================================= FAILURES =================================
_______________________________ test_answer ________________________________
@@ -80,67 +84,46 @@ rather pass in different or more complex objects. See the
next example or refer to :ref:`mysetup` for more information
on real-life examples.
generating parameters combinations, depending on command line
----------------------------------------------------------------------------
Let's say we want to execute a test with different parameters
and the parameter range shall be determined by a command
line argument. Let's first write a simple computation test::
dynamically adding command line options
--------------------------------------------------------------
# content of test_compute.py
.. regendoc:wipe
def test_compute(param1):
assert param1 < 4
Now we add a test configuration like this::
Through :confval:`addopts` you can statically add command line
options for your project. You can also dynamically modify
the command line arguments before they get processed::
# content of conftest.py
import sys
def pytest_cmdline_preparse(args):
if 'xdist' in sys.modules: # pytest-xdist plugin
import multiprocessing
num = max(multiprocessing.cpu_count() / 2, 1)
args[:] = ["-n", str(num)] + args
def pytest_addoption(parser):
parser.addoption("--all", action="store_true",
help="run all combinations")
If you have the :ref:`xdist plugin <xdist>` installed
you will now always perform test runs using a number
of subprocesses close to your CPU. Running in an empty
directory with the above conftest.py::
def pytest_generate_tests(metafunc):
if 'param1' in metafunc.funcargnames:
if metafunc.config.option.all:
end = 5
else:
end = 2
for i in range(end):
metafunc.addcall(funcargs={'param1': i})
This means that we only run 2 tests if we do not pass ``--all``::
$ py.test -q test_compute.py
..
2 passed in 0.01 seconds
We run only two computations, so we see two dots.
let's run the full monty::
$ py.test -q --all test_compute.py
....F
================================= FAILURES =================================
_____________________________ test_compute[4] ______________________________
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
gw0 I / gw1 I / gw2 I / gw3 I
gw0 [0] / gw1 [0] / gw2 [0] / gw3 [0]
param1 = 4
scheduling tests via LoadScheduling
def test_compute(param1):
> assert param1 < 4
E assert 4 < 4
test_compute.py:3: AssertionError
1 failed, 4 passed in 0.03 seconds
============================= in 0.51 seconds =============================
As expected when running the full range of ``param1`` values
we'll get an error on the last one.
.. _`retrieved by hooks as item keywords`:
.. _`excontrolskip`:
control skipping of tests according to command line option
--------------------------------------------------------------
.. regendoc:wipe
Here is a ``conftest.py`` file adding a ``--runslow`` command
line option to control skipping of ``slow`` marked tests::
@@ -171,32 +154,33 @@ We can now write a test module like this::
and when running it will see a skipped "slow" test::
$ py.test test_module.py -rs # "-rs" means report details on the little 's'
$ py.test -rs # "-rs" means report details on the little 's'
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_module.py
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 2 items
test_module.py .s
========================= short test summary info ==========================
SKIP [1] /tmp/doc-exec-104/conftest.py:9: need --runslow option to run
SKIP [1] /tmp/doc-exec-41/conftest.py:9: need --runslow option to run
=================== 1 passed, 1 skipped in 0.02 seconds ====================
Or run it including the ``slow`` marked test::
$ py.test test_module.py --runslow
$ py.test --runslow
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_module.py
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 2 items
test_module.py ..
========================= 2 passed in 0.01 seconds =========================
writing well integrated assertion helpers
--------------------------------------------------
.. regendoc:wipe
If you have a test helper function called from a test you can
use the ``pytest.fail`` marker to fail a test with a certain message.
The test support function will not show up in the traceback if you
@@ -218,7 +202,8 @@ of tracebacks: the ``checkconfig`` function will not be shown
unless the ``--fulltrace`` command line option is specified.
Let's run our little function::
$ py.test -q
$ py.test -q test_checkconfig.py
collecting ... collected 1 items
F
================================= FAILURES =================================
______________________________ test_something ______________________________
@@ -228,18 +213,19 @@ Let's run our little function::
E Failed: not configured: 42
test_checkconfig.py:8: Failed
1 failed in 0.02 seconds
1 failed in 0.01 seconds
Detect if running from within a py.test run
--------------------------------------------------------------
.. regendoc:wipe
Usually it is a bad idea to make application code
behave differently if called from a test. But if you
absolutely must find out if your application code is
running from a test you can do something like this::
# content of conftest.py in your testing directory
# content of conftest.py
def pytest_configure(config):
import sys
@@ -259,3 +245,57 @@ accordingly in your application. It's also a good idea
to rather use your own application module rather than ``sys``
for handling flag.
Adding info to test report header
--------------------------------------------------------------
.. regendoc:wipe
It's easy to present extra information in a py.test run::
# content of conftest.py
def pytest_report_header(config):
return "project deps: mylib-1.1"
which will add the string to the test header accordingly::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
project deps: mylib-1.1
collecting ... collected 0 items
============================= in 0.00 seconds =============================
.. regendoc:wipe
You can also return a list of strings which will be considered as several
lines of information. You can of course also make the amount of reporting
information on e.g. the value of ``config.option.verbose`` so that
you present more information appropriately::
# content of conftest.py
def pytest_report_header(config):
if config.option.verbose > 0:
return ["info1: did you know that ...", "did you?"]
which will add info only when run with "--v"::
$ py.test -v
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.2 -- /home/hpk/venv/0/bin/python
info1: did you know that ...
did you?
collecting ... collected 0 items
============================= in 0.00 seconds =============================
and nothing when run plainly::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 0 items
============================= in 0.00 seconds =============================

View File

@@ -17,5 +17,9 @@ def test_hello3():
def test_hello4():
assert 0
@xfail('pytest.__version__[0] != "17"')
def test_hello5():
assert 0
def test_hello6():
pytest.xfail("reason")

View File

@@ -1,32 +0,0 @@
changing Python test discovery patterns
--------------------------------------------------
You can influence python test file, function and class prefixes through
the :confval:`python_patterns` configuration valueto determine which
files are checked and which test functions are found. Example for using
a scheme that builds on ``check`` rather than on ``test`` prefixes::
# content of setup.cfg
[pytest]
python_patterns =
files: check_*.py
functions: check_
classes: Check
See
:confval:`python_funcprefixes` and :confval:`python_classprefixes`
changing test file discovery
-----------------------------------------------------
You can specify patterns where python tests are found::
python_testfilepatterns =
testing/**/{purebasename}.py
testing/*.py
.. note::
conftest.py files are never considered for test discovery

View File

@@ -12,25 +12,26 @@ On naming, nosetests, licensing and magic
Why a ``py.test`` instead of a ``pytest`` command?
++++++++++++++++++++++++++++++++++++++++++++++++++
Some historic, some practical reasons: ``py.test`` used to be part of
the ``py`` package which provided several developer utitilities,
all starting with ``py.<TAB>``, providing nice TAB-completion. If
Some of the reasons are historic, others are practical. ``py.test``
used to be part of the ``py`` package which provided several developer
utilities, all starting with ``py.<TAB>``, thus providing nice
TAB-completion. If
you install ``pip install pycmd`` you get these tools from a separate
package. These days the command line tool could be called ``pytest``
but then again many people have gotten used to the old name and there
is another tool named "pytest" so we just decided to stick with
since many people have gotten used to the old name and there
is another tool named "pytest" we just decided to stick with
``py.test``.
What's the relation to nose and unittest?
How does py.test relate to nose and unittest?
+++++++++++++++++++++++++++++++++++++++++++++++++
py.test and nose_ share basic philosophy when it comes
to running Python tests. In fact, you can run many tests
written nose with py.test. nose_ was originally created
to running and writing Python tests. In fact, you can run many tests
written for nose with py.test. nose_ was originally created
as a clone of ``py.test`` when py.test was in the ``0.8`` release
cycle. As of version 2.0 support for running unittest test
suites is majorly improved and you should be able to run
many Django and Twisted test suites.
cycle. Note that starting with pytest-2.0 support for running unittest
test suites is majorly improved and you should be able to run
many Django and Twisted test suites without modification.
.. _features: test/features.html
@@ -39,22 +40,20 @@ What's this "magic" with py.test?
++++++++++++++++++++++++++++++++++++++++++
Around 2007 (version ``0.8``) some people claimed that py.test
was using too much "magic". It has been refactored a lot. Thrown
out old code. Deprecated unused approaches and code. And it is today
probably one of the smallest, most universally runnable and most
customizable testing frameworks for Python. It's true that
``py.test`` uses metaprogramming techniques, i.e. it views
test code similar to how compilers view programs, using a
somewhat abstract internal model.
was using too much "magic". Partly this has been fixed by removing
unused, deprecated or complicated code. It is today probably one
of the smallest, most universally runnable and most
customizable testing frameworks for Python. However,
``py.test`` still uses many metaprogramming techniques and
reading its source is thus likely not something for Python beginners.
It's also true that the no-boilerplate testing is implemented by making
use of the Python assert statement through "re-interpretation":
A second "magic" issue arguably the assert statement re-intepreation:
When an ``assert`` statement fails, py.test re-interprets the expression
to show intermediate values if a test fails. If your expression
has side effects the intermediate values may not be the same, obfuscating
the initial error (this is also explained at the command line if it happens).
``py.test --no-assert`` turns off assert re-intepretation.
Sidenote: it is good practise to avoid asserts with side effects.
has side effects (better to avoid them anyway!) the intermediate values
may not be the same, obfuscating the initial error (this is also
explained at the command line if it happens).
``py.test --no-assert`` turns off assert re-interpretation.
.. _`py namespaces`: index.html
.. _`py/__init__.py`: http://bitbucket.org/hpk42/py-trunk/src/trunk/py/__init__.py
@@ -69,7 +68,7 @@ Is using funcarg- versus xUnit setup a style question?
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
For simple applications and for people experienced with nose_ or
unittest-style test setup using `xUnit style setup`_ often
unittest-style test setup using `xUnit style setup`_ probably
feels natural. For larger test suites, parametrized testing
or setup of complex test resources using funcargs_ may feel more natural.
Moreover, funcargs are ideal for writing advanced test support
@@ -86,13 +85,11 @@ in a managed class/module/function scope.
Why the ``pytest_funcarg__*`` name for funcarg factories?
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
We alternatively implemented an explicit registration mechanism for
function argument factories. But lacking a good use case for this
indirection and flexibility we decided to go for `Convention over
Configuration`_ and rather have factories specified by convention.
Besides removing the need for an registration indirection it allows to
"grep" for ``pytest_funcarg__MYARG`` and will safely find all factory
functions for the ``MYARG`` function argument.
We like `Convention over Configuration`_ and didn't see much point
in allowing a more flexible or abstract mechanism. Moreover,
is is nice to be able to search for ``pytest_funcarg__MYARG`` in
a source code and safely find all factory functions for
the ``MYARG`` function argument.
.. _`Convention over Configuration`: http://en.wikipedia.org/wiki/Convention_over_Configuration
@@ -125,8 +122,8 @@ Issues with py.test, multiprocess and setuptools?
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
On windows the multiprocess package will instantiate sub processes
by pickling and thus implicitely re-import a lot of local modules.
Unfortuantely, setuptools-0.6.11 does not ``if __name__=='__main__'``
by pickling and thus implicitly re-import a lot of local modules.
Unfortunately, setuptools-0.6.11 does not ``if __name__=='__main__'``
protect its generated command line script. This leads to infinite
recursion when running a test that instantiates Processes.

View File

@@ -1,5 +1,5 @@
==============================================================
creating and managing test function arguments
Injecting objects into test functions (funcargs)
==============================================================
.. currentmodule:: _pytest.python
@@ -11,16 +11,26 @@ creating and managing test function arguments
Dependency injection through function arguments
=================================================
py.test allows to inject values into test functions through the *funcarg
mechanism*: For each argument name in a test function signature a factory is
looked up and called to create the value. The factory can live in the
same test class, test module, in a per-directory ``confest.py`` file or
in an external plugin. It has full access to the requesting test
function, can register finalizers and invoke lifecycle-caching
helpers. As can be expected from a systematic dependency
injection mechanism, this allows full de-coupling of resource and
fixture setup from test code, enabling more maintainable and
easy-to-modify test suites.
py.test lets you inject objects into test functions and precisely
control their life cycle in relation to the test execution. It is
also possible to run a test function multiple times with different objects.
The basic mechanism for injecting objects is also called the
*funcarg mechanism* because objects are ultimatly injected
by calling a test function with it as an argument. Unlike the
classical xUnit approach *funcargs* relate more to `Dependency Injection`_
because they help to de-couple test code from objects required for
them to execute.
.. _`Dependency injection`: http://en.wikipedia.org/wiki/Dependency_injection
To create a value with which to call a test function a factory function
is called which gets full access to the test function context and can
register finalizers or invoke lifecycle-caching helpers. The factory
can be implemented in same test class or test module, or in a
per-directory ``conftest.py`` file or even in an external plugin. This
allows full de-coupling of test code and objects needed for test
execution.
A test function may be invoked multiple times in which case we
speak of :ref:`parametrized testing <parametrizing-tests>`. This can be
@@ -28,11 +38,13 @@ very useful if you want to test e.g. against different database backends
or with multiple numerical arguments sets and want to reuse the same set
of test functions.
Basic funcarg example
-----------------------
Let's look at a simple self-contained example that you can put
into a test module::
.. _funcarg:
Basic injection example
--------------------------------
Let's look at a simple self-contained test module::
# content of ./test_simplefactory.py
def pytest_funcarg__myfuncarg(request):
@@ -41,12 +53,16 @@ into a test module::
def test_function(myfuncarg):
assert myfuncarg == 17
This test function needs an injected object named ``myfuncarg``.
py.test will discover and call the factory named
``pytest_funcarg__myfuncarg`` within the same module in this case.
Running the test looks like this::
$ py.test test_simplefactory.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_simplefactory.py
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 1 items
test_simplefactory.py F
@@ -62,8 +78,8 @@ Running the test looks like this::
test_simplefactory.py:5: AssertionError
========================= 1 failed in 0.02 seconds =========================
This means that the test function was called with a ``myfuncarg`` value
of ``42`` and the assert fails accordingly. Here is how py.test
This means that indeed the test function was called with a ``myfuncarg``
argument value of ``42`` and the assert fails. Here is how py.test
comes to call the test function this way:
1. py.test :ref:`finds <test discovery>` the ``test_function`` because
@@ -74,14 +90,15 @@ comes to call the test function this way:
2. ``pytest_funcarg__myfuncarg(request)`` is called and
returns the value for ``myfuncarg``.
3. the test function can now be called: ``test_function(42)``
and results in the above exception because of the assertion
3. the test function can now be called: ``test_function(42)``.
This results in the above exception because of the assertion
mismatch.
Note that if you misspell a function argument or want
to use one that isn't available, you'll see an error
with a list of available function arguments. You can
also issue::
with a list of available function arguments.
You can always issue::
py.test --funcargs test_simplefactory.py
@@ -150,8 +167,8 @@ Running this::
$ py.test test_example.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_example.py
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 10 items
test_example.py .........F
@@ -165,13 +182,16 @@ Running this::
E assert 9 < 9
test_example.py:7: AssertionError
==================== 1 failed, 9 passed in 0.03 seconds ====================
==================== 1 failed, 9 passed in 0.02 seconds ====================
Note that the ``pytest_generate_tests(metafunc)`` hook is called during
the test collection phase which is separate from the actual test running.
Let's just look at what is collected::
$ py.test --collectonly test_example.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 10 items
<Module 'test_example.py'>
<Function 'test_func[0]'>
<Function 'test_func[1]'>
@@ -183,19 +203,23 @@ Let's just look at what is collected::
<Function 'test_func[7]'>
<Function 'test_func[8]'>
<Function 'test_func[9]'>
============================= in 0.00 seconds =============================
If you want to select only the run with the value ``7`` you could do::
$ py.test -v -k 7 test_example.py # or -k test_func[7]
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30 -- /home/hpk/venv/0/bin/python
test path 1: test_example.py
platform linux2 -- Python 2.6.6 -- pytest-2.0.2 -- /home/hpk/venv/0/bin/python
collecting ... collected 10 items
test_example.py:6: test_func[7] PASSED
======================== 9 tests deselected by '7' =========================
================== 1 passed, 9 deselected in 0.01 seconds ==================
You might want to look at :ref:`more parametrization examples <paramexamples>`.
.. _`metafunc object`:
The **metafunc** object

View File

@@ -16,7 +16,9 @@ Installation options::
To check your installation has installed the correct version::
$ py.test --version
This is py.test version 2.0.0.dev30, imported from /home/hpk/p/pytest/pytest.py
This is py.test version 2.0.2, imported from /home/hpk/p/pytest/pytest.py
setuptools registered plugins:
pytest-xdist-1.6.dev2 at /home/hpk/p/pytest-xdist/xdist/plugin.pyc
If you get an error checkout :ref:`installation issues`.
@@ -38,29 +40,30 @@ That's it. You can execute the test function now::
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: /tmp/doc-exec-70
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 1 items
test_sample.py F
================================= FAILURES =================================
_______________________________ test_answer ________________________________
def test_answer():
> assert func(3) == 5
E assert 4 == 5
E + where 4 = func(3)
test_sample.py:5: AssertionError
========================= 1 failed in 0.02 seconds =========================
py.test found the ``test_answer`` function by following :ref:`standard test discovery rules <test discovery>`, basically detecting the ``test_`` prefixes. We got a failure report because our little ``func(3)`` call did not return ``5``. The report is formatted using the :ref:`standard traceback reporting`.
py.test found the ``test_answer`` function by following :ref:`standard test discovery rules <test discovery>`, basically detecting the ``test_`` prefixes. We got a failure report because our little ``func(3)`` call did not return ``5``.
.. note::
You can simply use the ``assert`` statement for coding expectations because
intermediate values will be presented to you. This is arguably easier than
learning all the `the JUnit legacy methods`_.
You can simply use the ``assert`` statement for asserting
expectations because intermediate values will be presented to you.
This is arguably easier than learning all the `the JUnit legacy
methods`_.
However, there remains one caveat to using simple asserts: your
assertion expression should better be side-effect free. Because
@@ -76,10 +79,10 @@ py.test found the ``test_answer`` function by following :ref:`standard test disc
.. _`assert statement`: http://docs.python.org/reference/simple_stmts.html#the-assert-statement
Asserting a certain exception is raised
Asserting that a certain exception is raised
--------------------------------------------------------------
If you want to assert some code raises an exception you can
If you want to assert that some code raises an exception you can
use the ``raises`` helper::
# content of test_sysexit.py
@@ -94,6 +97,7 @@ use the ``raises`` helper::
Running it with, this time in "quiet" reporting mode::
$ py.test -q test_sysexit.py
collecting ... collected 1 items
.
1 passed in 0.00 seconds
@@ -102,9 +106,9 @@ Running it with, this time in "quiet" reporting mode::
Grouping multiple tests in a class
--------------------------------------------------------------
If you start to have more than a few tests it often makes sense
to group tests logically, in classes and modules. Let's put two
tests in a class like this::
Once you start to have more than a few tests it often makes sense
to group tests logically, in classes and modules. Let's write a class
containing two tests::
# content of test_class.py
class TestClass:
@@ -121,17 +125,19 @@ There is no need to subclass anything. We can simply
run the module by passing its filename::
$ py.test -q test_class.py
collecting ... collected 2 items
.F
================================= FAILURES =================================
____________________________ TestClass.test_two ____________________________
self = <test_class.TestClass instance at 0x288fc20>
self = <test_class.TestClass instance at 0x24565a8>
def test_two(self):
x = "hello"
> assert hasattr(x, 'check')
E assert hasattr('hello', 'check')
E assert False
E + where False = hasattr('hello', 'check')
test_class.py:8: AssertionError
1 failed, 1 passed in 0.02 seconds
@@ -157,21 +163,22 @@ py.test will lookup and call a factory to create the resource
before performing the test function call. Let's just run it::
$ py.test -q test_tmpdir.py
collecting ... collected 1 items
F
================================= FAILURES =================================
_____________________________ test_needsfiles ______________________________
tmpdir = local('/tmp/pytest-122/test_needsfiles0')
tmpdir = local('/tmp/pytest-0/test_needsfiles0')
def test_needsfiles(tmpdir):
print tmpdir
> assert 0
E assert 0
test_tmpdir.py:3: AssertionError
----------------------------- Captured stdout ------------------------------
/tmp/pytest-122/test_needsfiles0
1 failed in 0.05 seconds
/tmp/pytest-0/test_needsfiles0
1 failed in 0.02 seconds
Before the test runs, a unique-per-test-invocation temporary directory
was created. More info at :ref:`tmpdir handling`.
@@ -186,7 +193,7 @@ where to go next
Here are a few suggestions where to go next:
* :ref:`cmdline` for command line invocation examples
* :ref:`good practises` for virtualenv, test layout, genscript support
* :ref:`good practises <goodpractises>` for virtualenv, test layout, genscript support
* :ref:`apiref` for documentation and examples on using py.test
* :ref:`plugins` managing and writing plugins
@@ -220,7 +227,7 @@ py.test not found on Windows despite installation?
- **Jython2.5.1 on Windows XP**: `Jython does not create command line launchers`_
so ``py.test`` will not work correctly. You may install py.test on
CPython and type ``py.test --genscript=mytest`` and then use
``jython mytest`` to run py.test for your tests to run in Jython.
``jython mytest`` to run py.test for your tests to run with Jython.
:ref:`examples` for more complex examples

View File

@@ -8,26 +8,24 @@ Good Integration Practises
Work with virtual environments
-----------------------------------------------------------
We recommend to work with virtualenv_ environments and use easy_install_
We recommend to use virtualenv_ environments and use easy_install_
(or pip_) for installing your application dependencies as well as
the ``pytest`` package itself. This way you get a much more reproducible
the ``pytest`` package itself. This way you will get a much more reproducible
environment. A good tool to help you automate test runs against multiple
dependency configurations or Python interpreters is `tox`_,
independently created by the main py.test author. The latter
is also useful for integration with the continous integration
server Hudson_.
dependency configurations or Python interpreters is `tox`_.
.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
.. _`buildout`: http://www.buildout.org/
.. _pip: http://pypi.python.org/pypi/pip
Use tox and Continous Integration servers
Use tox and Continuous Integration servers
-------------------------------------------------
If you are (often) releasing code to the public you
If you frequently relase code to the public you
may want to look into `tox`_, the virtualenv test automation
tool and its `pytest support <http://codespeak.net/tox/example/pytest.html>`_.
The basic idea is to generate a JUnitXML file through the ``--junitxml=PATH`` option and have a continous integration server like Hudson_ pick it up.
The basic idea is to generate a JUnitXML file through the ``--junitxml=PATH`` option and have a continuous integration server like Jenkins_ pick it up
and generate reports.
.. _standalone:
.. _`genscript method`:
@@ -90,7 +88,7 @@ If you now type::
this will execute your tests using ``runtest.py``. As this is a
standalone version of ``py.test`` no prior installation whatsoever is
required for calling the test command. You can also pass additional
arguments to the subprocess-calls like your test directory or other
arguments to the subprocess-calls such as your test directory or other
options.
.. _`test discovery`:
@@ -101,14 +99,14 @@ Conventions for Python test discovery
``py.test`` implements the following standard test discovery:
* collection starts from initial command line arguments
* collection starts from the initial command line arguments
which may be directories, filenames or test ids.
* recurse into directories, unless they match :confval:`norecursedirs`
* ``test_*.py`` or ``*_test.py`` files, imported by their `package name`_.
* ``Test`` prefixed test classes (without an ``__init__`` method)
* ``test_`` prefixed test functions or methods are test items
For changing and customization example, see :doc:`example/pythoncollection`.
For examples of how to cnd cusotmize your test discovery :doc:`example/pythoncollection`.
py.test additionally discovers tests using the standard
:ref:`unittest.TestCase <unittest.TestCase>` subclassing technique.
@@ -154,8 +152,8 @@ You can always run your tests by pointing to it::
Test modules are imported under their fully qualified name as follows:
* find ``basedir`` -- this is the first "upward" directory not
containing an ``__init__.py``
* find ``basedir`` -- this is the first "upward" (towards the root)
directory not containing an ``__init__.py``
* perform ``sys.path.insert(0, basedir)`` to make the fully
qualified test module path importable.

View File

@@ -4,29 +4,32 @@ Welcome to ``py.test``!
=============================================
- **a mature fully featured testing tool**
- **a mature full-featured testing tool**
- runs on Posix/Windows, Python 2.4-3.2, PyPy and Jython
- continously `tested on many Python interpreters <http://hudson.testrun.org/view/pytest/job/pytest/>`_
- used in :ref:`many projects <projects>`, ranging from 10 to 10000 tests
- continuously `tested on many Python interpreters <http://hudson.testrun.org/view/pytest/job/pytest/>`_
- used in :ref:`many projects and organisations <projects>`, in test
suites ranging from 10 to 10s of thousands of tests
- has :ref:`comprehensive documentation <toc>`
- comes with :ref:`tested examples <examples>`
- supports :ref:`good integration practises <goodpractises>`
- **provides no-boilerplate testing**
- makes it :ref:`easy to get started <getstarted>`, refined :ref:`usage options <usage>`
- makes it :ref:`easy to get started <getstarted>`,
- refined :ref:`usage options <usage>`
- :ref:`assert with the assert statement`
- helpful :ref:`traceback and failing assertion reporting <tbreportdemo>`
- allows `print debugging <printdebugging>`_ and `generic output capturing <captures>`_
- supports :pep:`8` compliant coding style in tests
- allows :ref:`print debugging <printdebugging>` and :ref:`the
capturing of standard output during test execution <captures>`
- supports :pep:`8` compliant coding styles in tests
- **supports functional testing and complex test setups**
- advanced :ref:`skip and xfail`
- generic :ref:`marking and test selection <mark>`
- can :ref:`distribute tests to multiple CPUs <xdistcpu>` through :ref:`xdist plugin <xdist>`
- can :ref:`continously re-run failing tests <looponfailing>`
- can :ref:`continuously re-run failing tests <looponfailing>`
- many :ref:`builtin helpers <pytest helpers>`
- flexible :ref:`Python test discovery`
- unique :ref:`dependency injection through funcargs <funcargs>`
@@ -38,8 +41,8 @@ Welcome to ``py.test``!
tests, including running testcases made for Django and trial
- supports extended :ref:`xUnit style setup <xunitsetup>`
- supports domain-specific :ref:`non-python tests`
- supports generating testing coverage reports
- `Javasript unit- and functional testing`_
- supports the generation of testing coverage reports
- `Javascript unit- and functional testing`_
- **extensive plugin and customization system**
@@ -47,7 +50,7 @@ Welcome to ``py.test``!
- customizations can be per-directory, per-project or per PyPI released plugins
- it is easy to add command line options or do other kind of add-ons and customizations.
.. _`Javasript unit- and functional testing`: http://pypi.python.org/pypi/oejskit
.. _`Javascript unit- and functional testing`: http://pypi.python.org/pypi/oejskit
.. _`easy`: http://bruynooghe.blogspot.com/2009/12/skipping-slow-test-by-default-in-pytest.html

View File

@@ -10,11 +10,11 @@
.. _pytest: http://pypi.python.org/pypi/pytest
.. _mercurial: http://mercurial.selenic.com/wiki/
.. _`setuptools`: http://pypi.python.org/pypi/setuptools
.. _`easy_install`:
.. _`distribute docs`:
.. _`distribute`: http://pypi.python.org/pypi/distribute
.. _`pip`: http://pypi.python.org/pypi/pip
.. _`virtualenv`: http://pypi.python.org/pypi/virtualenv
.. _hudson: http://hudson-ci.org/
.. _jenkins: http://jenkins-ci.org/
.. _tox: http://codespeak.net/tox

View File

@@ -7,12 +7,12 @@ mark test functions with attributes
.. currentmodule:: _pytest.mark
By using the ``pytest.mark`` helper you can instantiate
decorators that will set named meta data on test functions.
decorators that will set named metadata on test functions.
Marking a single function
----------------------------------------------------
You can "mark" a test function with meta data like this::
You can "mark" a test function with metadata like this::
import pytest
@pytest.mark.webtest
@@ -20,7 +20,7 @@ You can "mark" a test function with meta data like this::
...
This will set the function attribute ``webtest`` to a :py:class:`MarkInfo`
instance. You can also specify parametrized meta data like this::
instance. You can also specify parametrized metadata like this::
# content of test_mark.py
@@ -44,7 +44,7 @@ Marking whole classes or modules
----------------------------------------------------
If you are programming with Python2.6 you may use ``pytest.mark`` decorators
with classes to apply markers to all its test methods::
with classes to apply markers to all of its test methods::
# content of test_mark_classlevel.py
import pytest
@@ -88,8 +88,8 @@ You can use the ``-k`` command line option to select tests::
$ py.test -k webtest # running with the above defined examples yields
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: /tmp/doc-exec-74
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 4 items
test_mark.py ..
test_mark_classlevel.py ..
@@ -100,8 +100,8 @@ And you can also run all tests except the ones that match the keyword::
$ py.test -k-webtest
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: /tmp/doc-exec-74
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 4 items
===================== 4 tests deselected by '-webtest' =====================
======================= 4 deselected in 0.01 seconds =======================
@@ -110,8 +110,8 @@ Or to only select the class::
$ py.test -kTestClass
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: /tmp/doc-exec-74
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 4 items
test_mark_classlevel.py ..

View File

@@ -9,8 +9,8 @@ on global settings or which invokes code which cannot be easily
tested such as network access. The ``monkeypatch`` function argument
helps you to safely set/delete an attribute, dictionary item or
environment variable or to modify ``sys.path`` for importing.
See the `monkeypatch blog post`_ one some introduction material
and motivation.
See the `monkeypatch blog post`_ for some introduction material
and a discussion of its motivation.
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
@@ -18,20 +18,20 @@ and motivation.
Simple example: patching ``os.path.expanduser``
---------------------------------------------------
If you e.g. want to pretend that ``os.expanduser`` returns a certain
If, for instance, you want to pretend that ``os.expanduser`` returns a certain
directory, you can use the :py:meth:`monkeypatch.setattr` method to
patch this function before calling into a function which uses it::
import os.path
def getssh(): # pseudo application code
return os.path.join(os.expanduser("~admin"), '.ssh')
return os.path.join(os.path.expanduser("~admin"), '.ssh')
def test_mytest(monkeypatch):
def mockreturn(path):
return '/abc'
monkeypatch.setattr(os.path, 'expanduser', mockreturn)
x = getssh()
assert x == '/abc'
assert x == '/abc/.ssh'
After the test function finishes the ``os.path.expanduser`` modification
will be undone.
@@ -39,8 +39,8 @@ will be undone.
.. background check:
$ py.test
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: /tmp/doc-exec-75
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 0 items
============================= in 0.00 seconds =============================

View File

@@ -16,5 +16,5 @@ these renaming rules::
py.test.cmdline.main -> pytest.main
The old ``py.test.*`` ways to access functionality remain
valid but you are encouraged to do global renames according
valid but you are encouraged to do global renaming according
to the above rules in your test code.

View File

@@ -1,7 +1,7 @@
Writing, managing and understanding plugins
=============================================
.. _plugins:
.. _`local plugin`:
Working with plugins and conftest files
=============================================
py.test implements all aspects of configuration, collection, running and reporting by calling `well specified hooks`_. Virtually any Python module can be registered as a plugin. It can implement any number of hook functions (usually two or three) which all have a ``pytest_`` prefix, making hook functions easy to distinguish and find. There are three basic locations types:
@@ -11,14 +11,18 @@ py.test implements all aspects of configuration, collection, running and reporti
.. _`pytest/plugin`: http://bitbucket.org/hpk42/pytest/src/tip/pytest/plugin/
.. _`conftest.py plugins`:
.. _`conftest.py`:
.. _`localplugin`:
.. _`conftest`:
conftest.py: local per-directory plugins
--------------------------------------------------------------
local ``conftest.py`` plugins contain directory-specific hook
implementations. Session and test running activities will
invoke all hooks defined in "higher up" ``conftest.py`` files.
Example: Assume the following layout and content of files::
invoke all hooks defined in ``conftest.py`` files closer to the
root of the filesystem. Example: Assume the following layout
and content of files::
a/conftest.py:
def pytest_runtest_setup(item):
@@ -38,21 +42,17 @@ Here is how you might run it::
py.test test_flat.py # will not show "setting up"
py.test a/test_sub.py # will show "setting up"
A note on ordering: ``py.test`` loads all ``conftest.py`` files upwards
from the command line file arguments. It usually performs look up
right-to-left, i.e. the hooks in "closer" conftest files will be called
earlier than further away ones.
.. Note::
If you have ``conftest.py`` files which do not reside in a
python package directory (i.e. one containing an ``__init__.py``) then
"import conftest" can be ambigous because there might be other
"import conftest" can be ambiguous because there might be other
``conftest.py`` files as well on your PYTHONPATH or ``sys.path``.
It is thus good practise for projects to either put ``conftest.py``
under a package scope or to never import anything from a
conftest.py file.
.. _`external plugins`:
.. _`extplugins`:
Installing External Plugins / Searching
------------------------------------------------------
@@ -64,9 +64,26 @@ tool, for example::
pip uninstall pytest-NAME
If a plugin is installed, py.test automatically finds and integrates it,
there is no need to activate it. If you don't need a plugin anymore simply
de-install it. You can find a list of available plugins through a
`pytest- pypi.python.org search`_.
there is no need to activate it. Here is a list of known plugins:
* `pytest-capturelog <http://pypi.python.org/pypi/pytest-capturelog>`_:
to capture and assert about messages from the logging module
* `pytest-xdist <http://pypi.python.org/pypi/pytest-xdist>`_:
to distribute tests to CPUs and remote hosts, looponfailing mode,
see also :ref:`xdist`
* `pytest-cov <http://pypi.python.org/pypi/pytest-cov>`_:
coverage reporting, compatible with distributed testing
* `pytest-pep8 <http://pypi.python.org/pypi/pytest-pep8>`_:
a ``--pep8`` option to enable PEP8 compliance checking.
* `oejskit <http://pypi.python.org/pypi/oejskit>`_:
a plugin to run javascript unittests in life browsers
(**version 0.8.9 not compatible with pytest-2.0**)
You may discover more plugins through a `pytest- pypi.python.org search`_.
.. _`available installable plugins`:
.. _`pytest- pypi.python.org search`: http://pypi.python.org/pypi?%3Aaction=search&term=pytest-&submit=search
@@ -93,12 +110,12 @@ Making your plugin installable by others
-----------------------------------------------
If you want to make your plugin externally available, you
may define a so called entry point for your distribution so
may define a so-called entry point for your distribution so
that ``py.test`` finds your plugin module. Entry points are
a feature that is provided by `setuptools`_ or `Distribute`_.
The concrete entry point is ``pytest11``. To make your plugin
available you can insert the following lines in your
setuptools/distribute-based setup-invocation:
py.test looks up the ``pytest11`` entrypoint to discover its
plugins and you can thus make your plugin available by definig
it in your setuptools/distribute-based setup-invocation:
.. sourcecode:: python
@@ -118,8 +135,8 @@ setuptools/distribute-based setup-invocation:
)
If a package is installed this way, py.test will load
``myproject.pluginmodule`` and accordingly call functions
if they match the `well specified hooks`_.
``myproject.pluginmodule`` as a plugin which can define
`well specified hooks`_.
Plugin discovery order at tool startup
--------------------------------------------
@@ -170,12 +187,42 @@ the plugin manager like this:
If you want to look at the names of existing plugins, use
the ``--traceconfig`` option.
.. _`findpluginname`:
Finding out which plugins are active
----------------------------------------------------------------------------
If you want to find out which plugins are active in your
environment you can type::
py.test --traceconfig
and will get an extended test header which shows activated plugins
and their names. It will also print local plugins aka
:ref:`conftest.py <conftest>` files when they are loaded.
.. _`cmdunregister`:
deactivate / unregister a plugin by name
----------------------------------------------------------------------------
You can prevent plugins from loading or unregister them::
py.test -p no:NAME
This means that any subsequent try to activate/load the named
plugin will it already existing. See :ref:`findpluginname` for
how to obtain the name of a plugin.
.. _`builtin plugins`:
py.test default plugin reference
====================================
You can find the source code for the following plugins
in the `pytest repository <http://bitbucket.org/hpk42/pytest/>`_.
.. autosummary::
_pytest.assertion
@@ -195,7 +242,7 @@ py.test default plugin reference
_pytest.recwarn
_pytest.resultlog
_pytest.runner
_pytest.session
_pytest.main
_pytest.skipping
_pytest.terminal
_pytest.tmpdir
@@ -211,17 +258,18 @@ hook specification and validation
py.test calls hook functions to implement initialization, running,
test execution and reporting. When py.test loads a plugin it validates
that all hook functions conform to their respective hook specification.
that each hook function conforms to its respective hook specification.
Each hook function name and its argument names need to match a hook
specification exactly but it is allowed for a hook function to accept
*less* parameters than specified. If you mistype argument names or the
hook name itself you get useful errors.
specification. However, a hook function may accept *fewer* parameters
by simply not specifying them. If you mistype argument names or the
hook name itself you get an error showing the available arguments.
initialisation, command line and configuration hooks
--------------------------------------------------------------------
.. currentmodule:: _pytest.hookspec
.. autofunction:: pytest_cmdline_preparse
.. autofunction:: pytest_cmdline_parse
.. autofunction:: pytest_namespace
.. autofunction:: pytest_addoption
@@ -242,8 +290,9 @@ All all runtest related hooks receive a :py:class:`pytest.Item` object.
For deeper understanding you may look at the default implementation of
these hooks in :py:mod:`_pytest.runner` and maybe also
in :py:mod:`_pytest.pdb` which intercepts creation
of reports in order to drop to interactive debugging.
in :py:mod:`_pytest.pdb` which interacts with :py:mod:`_pytest.capture`
and its input/output capturing in order to immediately drop
into interactive debugging when a test failure occurs.
The :py:mod:`_pytest.terminal` reported specifically uses
the reporting hook to print information about a test run.
@@ -288,14 +337,14 @@ Reference of important objects involved in hooks
.. autoclass:: _pytest.config.Parser
:members:
.. autoclass:: _pytest.session.Node(name, parent)
.. autoclass:: _pytest.main.Node(name, parent)
:members:
..
.. autoclass:: _pytest.session.File(fspath, parent)
.. autoclass:: _pytest.main.File(fspath, parent)
:members:
.. autoclass:: _pytest.session.Item(name, parent)
.. autoclass:: _pytest.main.Item(name, parent)
:members:
.. autoclass:: _pytest.python.Module(name, parent)
@@ -313,4 +362,3 @@ Reference of important objects involved in hooks
.. autoclass:: _pytest.runner.TestReport
:members:

View File

@@ -3,21 +3,27 @@
Project examples
==========================
Here are some examples of projects using py.test:
Here are some examples of projects using py.test (please send notes via :ref:`contact`):
* `PyPy <http://pypy.org>`_, Python with a JIT compiler, running over `16000 tests <http://test.pypy.org>`_
* the `MoinMoin <http://moinmo.in>`_ Wiki Engine
* `tox <http://codespeak.net/tox>`_, virtualenv/Hudson integration tool
* `PIDA <http://pida.co.uk>`_ framework for integrated development
* `PyPM <http://code.activestate.com/pypm/>`_ ActiveState's package manager
* `Fom <http://packages.python.org/Fom/>`_ a fluid object mapper for FluidDB
* `applib <https://github.com/ActiveState/applib>`_ cross-platform utilities
* `six <http://pypi.python.org/pypi/six/>`_ Python 2 and 3 compatibility utilities
* `pediapress <http://code.pediapress.com/wiki/wiki>`_ MediaWiki articles
* `mwlib <http://pypi.python.org/pypi/mwlib>`_ mediawiki parser and utility library
* `The Translate Toolkit <http://translate.sourceforge.net/wiki/toolkit/index>`_ for localization and conversion
* `execnet <http://codespeak.net/execnet>`_ rapid multi-Python deployment
* `pylib <http://pylib.org>`_ cross-platform path, IO, dynamic code library
* `Pacha <http://pacha.cafepais.com/>`_ configuration management in five minutes
* `bbfreeze <http://pypi.python.org/pypi/bbfreeze>`_ create standalone executables from Python scripts
* `pdb++ <http://bitbucket.org/antocuni/pdb>`_ a fancier version of PDB
* `py-s3fuse <http://code.google.com/p/py-s3fuse/>`_ Amazon S3 FUSE based filesystem
* `waskr <http://pacha.cafepais.com/>`_ WSGI Stats Middleware
* `guachi <http://code.google.com/p/guachi/>`_ global persistent configs for Python modules
* `Circuits <http://pypi.python.org/pypi/circuits>`_ lightweight Event Driven Framework
* `pygtk-helpers <http://bitbucket.org/aafshar/pygtkhelpers-main/>`_ easy interaction with PyGTK
* `QuantumCore <http://quantumcore.org/>`_ statusmessage and repoze openid plugin
@@ -31,15 +37,16 @@ Here are some examples of projects using py.test:
* `bu <http://packages.python.org/bu/>`_ a microscopic build system
* `katcp <https://bitbucket.org/hodgestar/katcp>`_ Telescope communication protocol over Twisted
* `kss plugin timer <http://pypi.python.org/pypi/kss.plugin.timer>`_
* many more ... (please send notes via the :ref:`contact`)
Some organisations using py.test
-----------------------------------
* `Square Kilometre Array <http://ska.ac.za/>`_
* `Square Kilometre Array, Cape Town <http://ska.ac.za/>`_
* `Tandberg <http://www.tandberg.com/>`_
* `Stups department of Heinrich Heine University <http://www.stups.uni-duesseldorf.de/projects.php>`_
* `Open End <http://openend.se>`_
* `Laboraratory of Bioinformatics <http://genesilico.pl/>`_
* `merlinux <http://merlinux.eu>`_
* `Shootq <http://web.shootq.com/>`_
* `Stups department of Heinrich Heine University Düsseldorf <http://www.stups.uni-duesseldorf.de/projects.php>`_
* `cellzome <http://www.cellzome.com/>`_
* `Open End, Gothenborg <http://www.openend.se>`_
* `Laboraratory of Bioinformatics, Warsaw <http://genesilico.pl/>`_
* `merlinux, Germany <http://merlinux.eu>`_
* many more ... (please be so kind to send a note via :ref:`contact`)

View File

@@ -1,2 +1,2 @@
[pytest]
# just defined to prevent the root level tox.ini to kick in
# just defined to prevent the root level tox.ini from kicking in

View File

@@ -1,49 +1,60 @@
.. _`skip and xfail`:
skip and xfail mechanisms
skip and xfail: dealing with tests that can not succeed
=====================================================================
You can skip or "xfail" test functions, either by marking functions
through a decorator or by calling the ``pytest.skip|xfail`` helpers.
A *skip* means that you expect your test to pass unless a certain configuration or condition (e.g. wrong Python interpreter, missing dependency) prevents it to run. And *xfail* means that you expect your test to fail because there is an
implementation problem. py.test counts and lists *xfailing* tests separately
and you can provide info such as a bug number or a URL to provide a
human readable problem context.
If you have test functions that cannot be run on certain platforms
or that you expect to fail you can mark them accordingly or you
may call helper functions during execution of setup or test functions.
Usually detailed information about skipped/xfailed tests is not shown
to avoid cluttering the output. You can use the ``-r`` option to
see details corresponding to the "short" letters shown in the
test progress::
A *skip* means that you expect your test to pass unless a certain
configuration or condition (e.g. wrong Python interpreter, missing
dependency) prevents it to run. And *xfail* means that your test
can run but you expect it to fail because there is an implementation problem.
py.test -rxs # show extra info on skips and xfail tests
py.test counts and lists *skip* and *xfail* tests separately. However,
detailed information about skipped/xfailed tests is not shown by default
to avoid cluttering the output. You can use the ``-r`` option to see
details corresponding to the "short" letters shown in the test
progress::
py.test -rxs # show extra info on skips and xfails
(See :ref:`how to change command line options defaults`)
.. _skipif:
Skipping a single function
Marking a test function to be skipped
-------------------------------------------
Here is an example for marking a test function to be skipped
Here is an example of marking a test function to be skipped
when run on a Python3 interpreter::
import sys
@pytest.mark.skipif("sys.version_info >= (3,0)")
def test_function():
...
During test function setup the skipif condition is
evaluated by calling ``eval(expr, namespace)``. The namespace
contains the ``sys`` and ``os`` modules and the test
``config`` object. The latter allows you to skip based
on a test configuration value e.g. like this::
evaluated by calling ``eval('sys.version_info >= (3,0)', namespace)``.
(*New in version 2.0.2*) The namespace contains all the module globals of the test function so that
you can for example check for versions of a module you are using::
@pytest.mark.skipif("not config.getvalue('db')")
def test_function(...):
import mymodule
@pytest.mark.skipif("mymodule.__version__ < '1.2'")
def test_function():
...
The test function will not be run ("skipped") if
``mymodule`` is below the specified version. The reason
for specifying the condition as a string is mainly that
py.test can report a summary of skip conditions.
For information on the construction of the ``namespace``
see `evaluation of skipif/xfail conditions`_.
Create a shortcut for your conditional skip decorator
at module level like this::
You can of course create a shortcut for your conditional skip
decorator at module level like this::
win32only = pytest.mark.skipif("sys.platform != 'win32'")
@@ -51,13 +62,12 @@ at module level like this::
def test_function():
...
skip test functions of a class
skip all test functions of a class
--------------------------------------
As with all function :ref:`marking` you can do it at
As with all function :ref:`marking <mark>` you can skip test functions at the
`whole class- or module level`_. Here is an example
for skipping all methods of a test class based on platform::
for skipping all methods of a test class based on the platform::
class TestPosixCalls:
pytestmark = pytest.mark.skipif("sys.platform == 'win32'")
@@ -65,9 +75,10 @@ for skipping all methods of a test class based on platform::
def test_function(self):
"will not be setup or run under 'win32' platform"
The ``pytestmark`` decorator will be applied to each test function.
If your code targets python2.6 or above you can equivalently use
the skipif decorator on classes::
The ``pytestmark`` special name tells py.test to apply it to each test
function in the class. If your code targets python2.6 or above you can
more naturally use the skipif decorator (and any other marker) on
classes::
@pytest.mark.skipif("sys.platform == 'win32'")
class TestPosixCalls:
@@ -75,9 +86,7 @@ the skipif decorator on classes::
def test_function(self):
"will not be setup or run under 'win32' platform"
It is fine in general to apply multiple "skipif" decorators
on a single function - this means that if any of the conditions
apply the function will be skipped.
Using multiple "skipif" decorators on a single function is generally fine - it means that if any of the conditions apply the function execution will be skipped.
.. _`whole class- or module level`: mark.html#scoped-marking
@@ -104,16 +113,16 @@ By specifying on the commandline::
you can force the running and reporting of an ``xfail`` marked test
as if it weren't marked at all.
Same as with skipif_ you can also selectively expect a failure
depending on platform::
As with skipif_ you can also mark your expectation of a failure
on a particular platform::
@pytest.mark.xfail("sys.version_info >= (3,0)")
def test_function():
...
You can also avoid running an "xfail" test at all or
You can furthermore prevent the running of an "xfail" test or
specify a reason such as a bug ID or similar. Here is
a simple test file with usages:
a simple test file with the several usages:
.. literalinclude:: example/xfail_demo.py
@@ -121,22 +130,49 @@ Running it with the report-on-xfail option gives this output::
example $ py.test -rx xfail_demo.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev31
test path 1: xfail_demo.py
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 6 items
xfail_demo.py xxxxx
xfail_demo.py xxxxxx
========================= short test summary info ==========================
XFAIL xfail_demo.py::test_hello
XFAIL xfail_demo.py::test_hello2
reason: [NOTRUN]
reason: [NOTRUN]
XFAIL xfail_demo.py::test_hello3
condition: hasattr(os, 'sep')
XFAIL xfail_demo.py::test_hello4
bug 110
XFAIL xfail_demo.py::test_hello5
condition: pytest.__version__[0] != "17"
XFAIL xfail_demo.py::test_hello6
reason: reason
======================== 5 xfailed in 0.04 seconds =========================
======================== 6 xfailed in 0.04 seconds =========================
.. _`evaluation of skipif/xfail conditions`:
evaluation of skipif/xfail expressions
----------------------------------------------------
.. versionadded:: 2.0.2
The evaluation of a condition string in ``pytest.mark.skipif(conditionstring)``
or ``pytest.mark.xfail(conditionstring)`` takes place in a namespace
dictionary which is constructed as follows:
* the namespace is initialized by putting the ``sys`` and ``os`` modules
and the pytest ``config`` object into it.
* updated with the module globals of the test function for which the
expression is applied.
The pytest ``config`` object allows you to skip based on a test configuration value
which you might have added::
@pytest.mark.skipif("not config.getvalue('db')")
def test_function(...):
...
imperative xfail from within a test or setup function
------------------------------------------------------
@@ -147,7 +183,7 @@ within test or setup code. Example::
def test_function():
if not valid_config():
pytest.xfail("unsuppored configuration")
pytest.xfail("unsupported configuration")
skipping on a missing import dependency
@@ -159,8 +195,8 @@ or within a test or test setup function::
docutils = pytest.importorskip("docutils")
If ``docutils`` cannot be imported here, this will lead to a
skip outcome of the test. You can also skip dependeing if
if a library does not come with a high enough version::
skip outcome of the test. You can also skip based on the
version number of a library::
docutils = pytest.importorskip("docutils", minversion="0.3")
@@ -170,10 +206,10 @@ imperative skip from within a test or setup function
------------------------------------------------------
If for some reason you cannot declare skip-conditions
you can also imperatively produce a Skip-outcome from
you can also imperatively produce a skip-outcome from
within test or setup code. Example::
def test_function():
if not valid_config():
pytest.skip("unsuppored configuration")
pytest.skip("unsupported configuration")

View File

@@ -30,7 +30,7 @@ test parametrization:
distributed testing:
- `simultanously test your code on all platforms`_ (blog entry)
- `simultaneously test your code on all platforms`_ (blog entry)
plugin specific examples:
@@ -42,7 +42,7 @@ plugin specific examples:
.. _`many examples in the docs for plugins`: plugin/index.html
.. _`monkeypatch plugin`: plugin/monkeypatch.html
.. _`application setup in test functions with funcargs`: funcargs.html#appsetup
.. _`simultanously test your code on all platforms`: http://tetamap.wordpress.com/2009/03/23/new-simultanously-test-your-code-on-all-platforms/
.. _`simultaneously test your code on all platforms`: http://tetamap.wordpress.com/2009/03/23/new-simultanously-test-your-code-on-all-platforms/
.. _`monkey patching done right`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
.. _`putting test-hooks into local or global plugins`: http://tetamap.wordpress.com/2009/05/14/putting-test-hooks-into-local-and-global-plugins/
.. _`parametrizing tests, generalized`: http://tetamap.wordpress.com/2009/05/13/parametrizing-python-tests-generalized/

View File

@@ -17,7 +17,7 @@ customize_: configuration, customization, extensions
changelog_: history of changes covering last releases
**Continous Integration of py.test's own tests and plugins with Hudson**:
**Continuous Integration of py.test's own tests and plugins with Hudson**:
`http://hudson.testrun.org/view/pytest`_

View File

@@ -5,7 +5,7 @@ Mission
py.test strives to make testing a fun and no-boilerplate effort.
The tool is distributed as part of the `py` package which contains supporting APIs that
are also useable independently. The project independent ``py.test`` command line tool helps you to:
are also usable independently. The project independent ``py.test`` command line tool helps you to:
* rapidly collect and run tests
* run unit- or doctests, functional or integration tests

View File

@@ -202,7 +202,7 @@ do normal site initialisation so that the environment variables can be detected
started.
Acknowledgements
Acknowledgments
----------------
Holger Krekel for pytest with its distributed testing support.

View File

@@ -1,7 +1,7 @@
pytest_oejskit plugin (EXTERNAL)
==========================================
The `oejskit`_ offers a py.test plugin for running Javascript tests in life browers. Running inside the browsers comes with some speed cost, on the other hand it means for example the code is tested against the real-word DOM implementations.
The `oejskit`_ offers a py.test plugin for running Javascript tests in life browsers. Running inside the browsers comes with some speed cost, on the other hand it means for example the code is tested against the real-word DOM implementations.
The approach enables to write integration tests such that the JavaScript code is tested against server-side Python code mocked as necessary. Any server-side framework that can already be exposed through WSGI (or for which a subset of WSGI can be written to accommodate the jskit own needs) can play along.
For more info and download please visit the `oejskit PyPI`_ page.

View File

@@ -129,7 +129,7 @@ put options values in a ``conftest.py`` file like this::
option_tx = ['ssh=myhost//python=python2.5', 'popen//python=python2.5']
option_dist = True
Any commandline ``--tx`` specifictions will add to the list of
Any commandline ``--tx`` specifications will add to the list of
available execution environments.
Specifying "rsync" dirs in a conftest.py

View File

@@ -28,15 +28,15 @@ Running this would result in a passed test except for the last
$ py.test test_tmpdir.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_tmpdir.py
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 1 items
test_tmpdir.py F
================================= FAILURES =================================
_____________________________ test_create_file _____________________________
tmpdir = local('/tmp/pytest-123/test_create_file0')
tmpdir = local('/tmp/pytest-1/test_create_file0')
def test_create_file(tmpdir):
p = tmpdir.mkdir("sub").join("hello.txt")
@@ -54,9 +54,9 @@ Running this would result in a passed test except for the last
the default base temporary directory
-----------------------------------------------
Temporary directories are by default created as sub directories of
Temporary directories are by default created as sub-directories of
the system temporary directory. The base name will be ``pytest-NUM`` where
``NUM`` will be incremenated with each test run. Moreover, entries older
``NUM`` will be incremented with each test run. Moreover, entries older
than 3 temporary directories will be removed.
You can override the default temporary directory setting like this::
@@ -64,8 +64,8 @@ You can override the default temporary directory setting like this::
py.test --basetemp=mydir
When distributing tests on the local machine, ``py.test`` takes care to
configure a basetemp directory for the sub processes such that all
temporary data lands below below a single per-test run basetemp directory.
configure a basetemp directory for the sub processes such that all temporary
data lands below a single per-test run basetemp directory.
.. _`py.path.local`: http://pylib.org/path.html

View File

@@ -8,7 +8,7 @@ py.test has limited support for running Python `unittest.py style`_ tests.
It will automatically collect ``unittest.TestCase`` subclasses
and their ``test`` methods in test files. It will invoke
``setUp/tearDown`` methods but also perform py.test's standard ways
of treating tests like e.g. IO capturing::
of treating tests such as IO capturing::
# content of test_unittest.py
@@ -24,8 +24,8 @@ Running it yields::
$ py.test test_unittest.py
=========================== test session starts ============================
platform linux2 -- Python 2.6.5 -- pytest-2.0.0.dev30
test path 1: test_unittest.py
platform linux2 -- Python 2.6.6 -- pytest-2.0.2
collecting ... collected 1 items
test_unittest.py F
@@ -37,23 +37,9 @@ Running it yields::
def test_method(self):
x = 1
> self.assertEquals(x, 3)
E AssertionError: 1 != 3
test_unittest.py:8:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <test_unittest.MyTest testMethod=test_method>, first = 1, second = 3
msg = None
def failUnlessEqual(self, first, second, msg=None):
"""Fail if the two objects are unequal as determined by the '=='
operator.
"""
if not first == second:
raise self.failureException, \
> (msg or '%r != %r' % (first, second))
E AssertionError: 1 != 3
/usr/lib/python2.6/unittest.py:350: AssertionError
test_unittest.py:8: AssertionError
----------------------------- Captured stdout ------------------------------
hello
========================= 1 failed in 0.02 seconds =========================

View File

@@ -12,7 +12,7 @@ calling pytest through ``python -m pytest``
.. versionadded:: 2.0
If you use Python-2.5 or above you can invoke testing through the
If you use Python-2.5 or later you can invoke testing through the
Python interpreter from the command line::
python -m pytest [...]
@@ -20,8 +20,8 @@ Python interpreter from the command line::
This is equivalent to invoking the command line script ``py.test [...]``
directly.
Getting help on version, option names, environment vars
-----------------------------------------------------------
Getting help on version, option names, environment variables
--------------------------------------------------------------
::
@@ -96,12 +96,12 @@ can use a helper::
.. versionadded: 2.0.0
In previous versions you could only enter PDB tracing if
you :ref:`disable capturing`.
you disable capturing on the command line via ``py.test -s``.
creating JUnitXML format files
----------------------------------------------------
To create result files which can be read by Hudson_ or other Continous
To create result files which can be read by Hudson_ or other Continuous
integration servers, use this invocation::
py.test --junitxml=path

View File

@@ -7,13 +7,13 @@ xdist: pytest distributed testing plugin
The `pytest-xdist`_ plugin extends py.test with some unique
test execution modes:
* Looponfail: run your tests repeatedly in a subprocess. After each run py.test
waits until a file in your project changes and then re-runs the previously
failing tests. This is repeated until all tests pass after which again
a full run is performed.
* Looponfail: run your tests repeatedly in a subprocess. After each
run, py.test waits until a file in your project changes and then
re-runs the previously failing tests. This is repeated until all
tests pass. At this point a full run is again performed.
* multiprocess Load-balancing: if you have multiple CPUs or hosts you can use
those for a combined test run. This allows to speed up
them for a combined test run. This allows to speed up
development or to use special resources of remote machines.
* Multi-Platform coverage: you can specify different Python interpreters
@@ -25,8 +25,8 @@ are reported back and displayed to your local terminal.
You may specify different Python versions and interpreters.
Installation
-----------------------
Installation of xdist plugin
------------------------------
Install the plugin with::
@@ -36,7 +36,7 @@ Install the plugin with::
pip install pytest-xdist
or use the package in develope/in-place mode with
or use the package in develop/in-place mode with
a checkout of the `pytest-xdist repository`_ ::
python setup.py develop
@@ -55,13 +55,13 @@ To send tests to multiple CPUs, type::
py.test -n NUM
Especially for longer running tests or tests requiring
a lot of IO this can lead to considerable speed ups.
a lot of I/O this can lead to considerable speed ups.
Running tests in a Python subprocess
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
To instantiate a python2.4 sub process and send tests to it, you may type::
To instantiate a Python-2.4 subprocess and send tests to it, you may type::
py.test -d --tx popen//python=python2.4
@@ -70,10 +70,10 @@ Python interpreter, found in your system binary lookup path.
If you prefix the --tx option value like this::
--tx 3*popen//python=python2.4
py.test -d --tx 3*popen//python=python2.4
then three subprocesses would be created and tests
will be load-balanced across these three processes.
then three subprocesses would be created and the tests
will be distributed to three subprocesses and run simultanously.
.. _looponfailing:
@@ -82,11 +82,13 @@ Running tests in looponfailing mode
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
For refactoring a project with a medium or large test suite
you can use the looponfailing mode, simply add the ``--f`` option::
you can use the looponfailing mode. Simply add the ``--f`` option::
py.test -f
and py.test will run your tests, then wait for file changes and re-run the failing test set. Of course you can pass in more options to select tests or test files. File changes are detected by looking at the root directory - you can override this automatic default by an ini-file setting::
and py.test will run your tests. Assuming you have failures it will then
wait for file changes and re-run the failing test set. File changes are detected by looking at ``looponfailingroots`` root directories and all of their contents (recursively). If the default for this value does not work for you you
can change it in your project by setting a configuration option::
# content of a pytest.ini, setup.cfg or tox.ini file
[pytest]
@@ -98,26 +100,28 @@ Sending tests to remote SSH accounts
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Suppose you have a package ``mypkg`` which contains some
tests that you can successfully run locally. And you
tests that you can successfully run locally. And you also
have a ssh-reachable machine ``myhost``. Then
you can ad-hoc distribute your tests by typing::
py.test -d --tx ssh=myhostpopen --rsyncdir mypkg mypkg
This will synchronize your ``mypkg`` package directory
to an remote ssh account and then locally collect tests
and send them to remote places for execution.
with a remote ssh account and then collect and run your
tests at the remote side.
You can specify multiple ``--rsyncdir`` directories
to be sent to the remote side.
**NOTE:** For py.test to collect and send tests correctly
you not only need to make sure all code and tests
directories are rsynced, but that any test (sub) directory
also has an ``__init__.py`` file because internally
py.test references tests as a fully qualified python
module path. **You will otherwise get strange errors**
during setup of the remote side.
.. XXX CHECK
**NOTE:** For py.test to collect and send tests correctly
you not only need to make sure all code and tests
directories are rsynced, but that any test (sub) directory
also has an ``__init__.py`` file because internally
py.test references tests as a fully qualified python
module path. **You will otherwise get strange errors**
during setup of the remote side.
Sending tests to remote Socket Servers
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
@@ -148,7 +152,7 @@ environment this command will send each tests to all
platforms - and report back failures from all platforms
at once. The specifications strings use the `xspec syntax`_.
.. _`xspec syntax`: http://codespeak.net/execnet/trunk/basics.html#xspec
.. _`xspec syntax`: http://codespeak.net/execnet/basics.html#xspec
.. _`socketserver.py`: http://bitbucket.org/hpk42/execnet/raw/2af991418160/execnet/script/socketserver.py
@@ -157,9 +161,8 @@ at once. The specifications strings use the `xspec syntax`_.
Specifying test exec environments in an ini file
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
pytest (since version 2.0) supports ini-style cofiguration.
You can for example make running with three subprocesses
your default like this::
pytest (since version 2.0) supports ini-style configuration.
For example, you could make running with three subprocesses your default::
[pytest]
addopts = -n3

View File

@@ -65,7 +65,7 @@ Similarly, the following methods are called around each method invocation::
with a setup_method call.
"""
If you rather define test functions directly at module level
If you would rather define test functions directly at module level
you can also use the following functions to implement fixtures::
def setup_function(function):
@@ -73,7 +73,7 @@ you can also use the following functions to implement fixtures::
function. Invoked for every test function in the module.
"""
def teardown_method(function):
def teardown_function(function):
""" teardown any state that was previously setup
with a setup_function call.
"""

View File

@@ -1,7 +1,7 @@
"""
unit and functional testing with Python.
"""
__version__ = '2.0.0'
__version__ = '2.0.2'
__all__ = ['main']
from _pytest.core import main, UsageError, _preloadplugins

View File

@@ -22,14 +22,14 @@ def main():
name='pytest',
description='py.test: simple powerful testing with Python',
long_description = long_description,
version='2.0.0',
version='2.0.2',
url='http://pytest.org',
license='MIT license',
platforms=['unix', 'linux', 'osx', 'cygwin', 'win32'],
author='holger krekel, Guido Wesdorp, Carl Friedrich Bolz, Armin Rigo, Maciej Fijalkowski & others',
author_email='holger at merlinux.eu',
entry_points= make_entry_points(),
install_requires=['py>=1.4.0a2'],
install_requires=['py>1.4.1'],
classifiers=['Development Status :: 5 - Production/Stable',
'Intended Audience :: Developers',
'License :: OSI Approved :: MIT License',
@@ -67,4 +67,4 @@ def make_entry_points():
return {'console_scripts': l}
if __name__ == '__main__':
main()
main()

View File

@@ -27,7 +27,7 @@ class TestGeneralUsage:
def test_option(pytestconfig):
assert pytestconfig.option.xyz == "123"
""")
result = testdir.runpytest("-p", "xyz", "--xyz=123")
result = testdir.runpytest("-p", "pytest_xyz", "--xyz=123")
assert result.ret == 0
result.stdout.fnmatch_lines([
'*1 passed*',
@@ -89,9 +89,11 @@ class TestGeneralUsage:
import pytest
class MyFile(pytest.File):
def collect(self):
return
return [MyItem("hello", parent=self)]
def pytest_collect_file(path, parent):
return MyFile(path, parent)
class MyItem(pytest.Item):
pass
""")
p = testdir.makepyfile("def test_hello(): pass")
result = testdir.runpytest(p, "--collectonly")
@@ -218,6 +220,12 @@ class TestGeneralUsage:
assert res.ret
res.stderr.fnmatch_lines(["*ERROR*not found*"])
def test_docstring_on_hookspec(self):
from _pytest import hookspec
for name, value in vars(hookspec).items():
if name.startswith("pytest_"):
assert value.__doc__, "no docstring for %s" % name
class TestInvocationVariants:
def test_earlyinit(self, testdir):
p = testdir.makepyfile("""
@@ -340,6 +348,12 @@ class TestInvocationVariants:
result.stdout.fnmatch_lines([
"*2 passed*"
])
path.join('test_hello.py').remove()
result = testdir.runpytest("--pyargs", "tpkg.test_hello")
assert result.ret != 0
result.stderr.fnmatch_lines([
"*file*not*found*test_hello*",
])
def test_cmdline_python_package_not_exists(self, testdir):
result = testdir.runpytest("--pyargs", "tpkgwhatv")

View File

@@ -114,6 +114,14 @@ class TestAssert_reprcompare:
expl = callequal(A(), '')
assert not expl
def test_repr_no_exc(self):
expl = ' '.join(callequal('foo', 'bar'))
assert 'raised in repr()' not in expl
def test_reprcompare_notin():
detail = plugin.pytest_assertrepr_compare('not in', 'foo', 'aaafoobbb')[1:]
assert detail == ["'foo' is contained here:", ' aaafoobbb', '? +++']
@needsnewassert
def test_pytest_assertrepr_compare_integration(testdir):
testdir.makepyfile("""
@@ -201,3 +209,14 @@ def test_traceback_failure(testdir):
"*test_traceback_failure.py:4: AssertionError"
])
@pytest.mark.skipif("sys.version_info < (2,5) or '__pypy__' in sys.builtin_module_names")
def test_warn_missing(testdir):
p1 = testdir.makepyfile("")
result = testdir.run(sys.executable, "-OO", "-m", "pytest", "-h")
result.stderr.fnmatch_lines([
"*WARNING*assertion*",
])
result = testdir.run(sys.executable, "-OO", "-m", "pytest", "--no-assert")
result.stderr.fnmatch_lines([
"*WARNING*assertion*",
])

View File

@@ -306,6 +306,60 @@ class TestLoggingInteraction:
# verify proper termination
assert "closed" not in s
def test_logging_initialized_in_test(self, testdir):
p = testdir.makepyfile("""
import sys
def test_something():
# pytest does not import logging
assert 'logging' not in sys.modules
import logging
logging.basicConfig()
logging.warn("hello432")
assert 0
""")
result = testdir.runpytest(p, "--traceconfig",
"-p", "no:capturelog")
assert result.ret != 0
result.stdout.fnmatch_lines([
"*hello432*",
])
assert 'operation on closed file' not in result.stderr.str()
def test_conftestlogging_is_shown(self, testdir):
testdir.makeconftest("""
import logging
logging.basicConfig()
logging.warn("hello435")
""")
# make sure that logging is still captured in tests
result = testdir.runpytest("-s", "-p", "no:capturelog")
assert result.ret == 0
result.stderr.fnmatch_lines([
"WARNING*hello435*",
])
assert 'operation on closed file' not in result.stderr.str()
def test_conftestlogging_and_test_logging(self, testdir):
testdir.makeconftest("""
import logging
logging.basicConfig()
""")
# make sure that logging is still captured in tests
p = testdir.makepyfile("""
def test_hello():
import logging
logging.warn("hello433")
assert 0
""")
result = testdir.runpytest(p, "-p", "no:capturelog")
assert result.ret != 0
result.stdout.fnmatch_lines([
"WARNING*hello433*",
])
assert 'something' not in result.stderr.str()
assert 'operation on closed file' not in result.stderr.str()
class TestCaptureFuncarg:
def test_std_functional(self, testdir):
reprec = testdir.inline_runsource("""

View File

@@ -1,6 +1,6 @@
import pytest, py
from _pytest.session import Session
from _pytest.main import Session
class TestCollector:
def test_collect_versus_item(self):
@@ -15,15 +15,10 @@ class TestCollector:
""")
recwarn.clear()
assert modcol.Module == pytest.Module
recwarn.pop(DeprecationWarning)
assert modcol.Class == pytest.Class
recwarn.pop(DeprecationWarning)
assert modcol.Item == pytest.Item
recwarn.pop(DeprecationWarning)
assert modcol.File == pytest.File
recwarn.pop(DeprecationWarning)
assert modcol.Function == pytest.Function
recwarn.pop(DeprecationWarning)
def test_check_equality(self, testdir):
modcol = testdir.getmodulecol("""
@@ -99,6 +94,8 @@ class TestCollectFS:
tmpdir.ensure(".whatever", 'test_notfound.py')
tmpdir.ensure(".bzr", 'test_notfound.py')
tmpdir.ensure("normal", 'test_found.py')
for x in tmpdir.visit("test_*.py"):
x.write("def test_hello(): pass")
result = testdir.runpytest("--collectonly")
s = result.stdout.str()
@@ -472,6 +469,21 @@ class TestSession:
item2b, = newcol.perform_collect([item.nodeid], genitems=False)
assert item2b == item2
def test_find_byid_without_instance_parents(self, testdir):
p = testdir.makepyfile("""
class TestClass:
def test_method(self):
pass
""")
arg = p.basename + ("::TestClass::test_method")
config = testdir.parseconfig(arg)
rcol = Session(config)
rcol.perform_collect()
items = rcol.items
assert len(items) == 1
item, = items
assert item.nodeid.endswith("TestClass::()::test_method")
class Test_getinitialnodes:
def test_global_file(self, testdir, tmpdir):
x = tmpdir.ensure("x.py")

View File

@@ -82,7 +82,6 @@ class TestConfigCmdlineParsing:
class TestConfigAPI:
def test_config_trace(self, testdir):
config = testdir.Config()
l = []
@@ -112,8 +111,15 @@ class TestConfigAPI:
verbose = config.getvalueorskip("verbose")
assert verbose == config.option.verbose
config.option.hello = None
pytest.raises(pytest.skip.Exception,
"config.getvalueorskip('hello')")
try:
config.getvalueorskip('hello')
except KeyboardInterrupt:
raise
except:
excinfo = py.code.ExceptionInfo()
frame = excinfo.traceback[-2].frame
assert frame.code.name == "getvalueorskip"
assert frame.eval("__tracebackhide__")
def test_config_overwrite(self, testdir):
o = testdir.tmpdir
@@ -219,12 +225,14 @@ def test_options_on_small_file_do_not_blow_up(testdir):
['--traceconfig'], ['-v'], ['-v', '-v']):
runfiletest(opts + [path])
def test_preparse_ordering(testdir, monkeypatch):
def test_preparse_ordering_with_setuptools(testdir, monkeypatch):
pkg_resources = py.test.importorskip("pkg_resources")
def my_iter(name):
assert name == "pytest11"
class EntryPoint:
name = "mytestplugin"
class dist:
pass
def load(self):
class PseudoPlugin:
x = 42
@@ -239,3 +247,27 @@ def test_preparse_ordering(testdir, monkeypatch):
plugin = config.pluginmanager.getplugin("mytestplugin")
assert plugin.x == 42
def test_plugin_preparse_prevents_setuptools_loading(testdir, monkeypatch):
pkg_resources = py.test.importorskip("pkg_resources")
def my_iter(name):
assert name == "pytest11"
class EntryPoint:
name = "mytestplugin"
def load(self):
assert 0, "should not arrive here"
return iter([EntryPoint()])
monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
config = testdir.parseconfig("-p", "no:mytestplugin")
plugin = config.pluginmanager.getplugin("mytestplugin")
assert plugin == -1
def test_cmdline_processargs_simple(testdir):
testdir.makeconftest("""
def pytest_cmdline_preparse(args):
args.append("-h")
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines([
"*pytest*",
"*-h*",
])

View File

@@ -170,21 +170,6 @@ def test_setinitial_confcut(testdir):
assert conftest.getconftestmodules(sub) == []
assert conftest.getconftestmodules(conf.dirpath()) == []
def test_conftest_samecontent_detection(testdir):
conf = testdir.makeconftest("x=3")
p = testdir.mkdir("x")
conf.copy(p.join("conftest.py"))
conftest = Conftest()
conftest.setinitial([p])
l = conftest.getconftestmodules(p)
assert len(l) == 1
assert l[0].__file__ == p.join("conftest.py")
p2 = p.mkdir("y")
conf.copy(p2.join("conftest.py"))
l = conftest.getconftestmodules(p2)
assert len(l) == 1
assert l[0].__file__ == p.join("conftest.py")
@pytest.mark.multi(name='test tests whatever .dotdir'.split())
def test_setinitial_conftest_subdirs(testdir, name):
sub = testdir.mkdir(name)

View File

@@ -1,5 +1,5 @@
import pytest, py, os
from _pytest.core import PluginManager, canonical_importname
from _pytest.core import PluginManager
from _pytest.core import MultiCall, HookRelay, varnames
@@ -15,30 +15,47 @@ class TestBootstrapping:
pluginmanager.consider_preparse(["xyz", "-p", "hello123"])
""")
def test_plugin_prevent_register(self):
pluginmanager = PluginManager()
pluginmanager.consider_preparse(["xyz", "-p", "no:abc"])
l1 = pluginmanager.getplugins()
pluginmanager.register(42, name="abc")
l2 = pluginmanager.getplugins()
assert len(l2) == len(l1)
def test_plugin_prevent_register_unregistered_alredy_registered(self):
pluginmanager = PluginManager()
pluginmanager.register(42, name="abc")
l1 = pluginmanager.getplugins()
assert 42 in l1
pluginmanager.consider_preparse(["xyz", "-p", "no:abc"])
l2 = pluginmanager.getplugins()
assert 42 not in l2
def test_plugin_skip(self, testdir, monkeypatch):
p = testdir.makepyfile(pytest_skipping1="""
p = testdir.makepyfile(skipping1="""
import pytest
pytest.skip("hello")
""")
p.copy(p.dirpath("pytest_skipping2.py"))
p.copy(p.dirpath("skipping2.py"))
monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
result = testdir.runpytest("-p", "skipping1", "--traceconfig")
assert result.ret == 0
result.stdout.fnmatch_lines([
"*hint*skipping2*hello*",
"*hint*skipping1*hello*",
"*hint*skipping2*hello*",
])
def test_consider_env_plugin_instantiation(self, testdir, monkeypatch):
pluginmanager = PluginManager()
testdir.syspathinsert()
testdir.makepyfile(pytest_xy123="#")
testdir.makepyfile(xy123="#")
monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123')
l1 = len(pluginmanager.getplugins())
pluginmanager.consider_env()
l2 = len(pluginmanager.getplugins())
assert l2 == l1 + 1
assert pluginmanager.getplugin('pytest_xy123')
assert pluginmanager.getplugin('xy123')
pluginmanager.consider_env()
l3 = len(pluginmanager.getplugins())
assert l2 == l3
@@ -48,7 +65,8 @@ class TestBootstrapping:
def my_iter(name):
assert name == "pytest11"
class EntryPoint:
name = "mytestplugin"
name = "pytest_mytestplugin"
dist = None
def load(self):
class PseudoPlugin:
x = 42
@@ -60,8 +78,6 @@ class TestBootstrapping:
pluginmanager.consider_setuptools_entrypoints()
plugin = pluginmanager.getplugin("mytestplugin")
assert plugin.x == 42
plugin2 = pluginmanager.getplugin("pytest_mytestplugin")
assert plugin2 == plugin
def test_consider_setuptools_not_installed(self, monkeypatch):
monkeypatch.setitem(py.std.sys.modules, 'pkg_resources',
@@ -75,7 +91,7 @@ class TestBootstrapping:
p = testdir.makepyfile("""
import pytest
def test_hello(pytestconfig):
plugin = pytestconfig.pluginmanager.getplugin('x500')
plugin = pytestconfig.pluginmanager.getplugin('pytest_x500')
assert plugin is not None
""")
monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
@@ -91,14 +107,14 @@ class TestBootstrapping:
reset = testdir.syspathinsert()
pluginname = "pytest_hello"
testdir.makepyfile(**{pluginname: ""})
pluginmanager.import_plugin("hello")
pluginmanager.import_plugin("pytest_hello")
len1 = len(pluginmanager.getplugins())
pluginmanager.import_plugin("pytest_hello")
len2 = len(pluginmanager.getplugins())
assert len1 == len2
plugin1 = pluginmanager.getplugin("pytest_hello")
assert plugin1.__name__.endswith('pytest_hello')
plugin2 = pluginmanager.getplugin("hello")
plugin2 = pluginmanager.getplugin("pytest_hello")
assert plugin2 is plugin1
def test_import_plugin_dotted_name(self, testdir):
@@ -116,13 +132,13 @@ class TestBootstrapping:
def test_consider_module(self, testdir):
pluginmanager = PluginManager()
testdir.syspathinsert()
testdir.makepyfile(pytest_plug1="#")
testdir.makepyfile(pytest_plug2="#")
testdir.makepyfile(pytest_p1="#")
testdir.makepyfile(pytest_p2="#")
mod = py.std.types.ModuleType("temp")
mod.pytest_plugins = ["pytest_plug1", "pytest_plug2"]
mod.pytest_plugins = ["pytest_p1", "pytest_p2"]
pluginmanager.consider_module(mod)
assert pluginmanager.getplugin("plug1").__name__ == "pytest_plug1"
assert pluginmanager.getplugin("plug2").__name__ == "pytest_plug2"
assert pluginmanager.getplugin("pytest_p1").__name__ == "pytest_p1"
assert pluginmanager.getplugin("pytest_p2").__name__ == "pytest_p2"
def test_consider_module_import_module(self, testdir):
mod = py.std.types.ModuleType("x")
@@ -198,8 +214,7 @@ class TestBootstrapping:
mod = py.std.types.ModuleType("pytest_xyz")
monkeypatch.setitem(py.std.sys.modules, 'pytest_xyz', mod)
pp = PluginManager()
pp.import_plugin('xyz')
assert pp.getplugin('xyz') == mod
pp.import_plugin('pytest_xyz')
assert pp.getplugin('pytest_xyz') == mod
assert pp.isregistered(mod)
@@ -217,9 +232,6 @@ class TestBootstrapping:
pass
excinfo = pytest.raises(Exception, "pp.register(hello())")
def test_canonical_importname(self):
for name in 'xyz', 'pytest_xyz', 'pytest_Xyz', 'Xyz':
impname = canonical_importname(name)
def test_notify_exception(self, capfd):
pp = PluginManager()
@@ -308,25 +320,6 @@ class TestPytestPluginInteractions:
res = config.hook.pytest_myhook(xyz=10)
assert res == [11]
def test_addhooks_docstring_error(self, testdir):
newhooks = testdir.makepyfile(newhooks="""
class A: # no pytest_ prefix
pass
def pytest_myhook(xyz):
pass
""")
conf = testdir.makeconftest("""
import sys ; sys.path.insert(0, '.')
import newhooks
def pytest_addhooks(pluginmanager):
pluginmanager.addhooks(newhooks)
""")
res = testdir.runpytest()
assert res.ret != 0
res.stderr.fnmatch_lines([
"*docstring*pytest_myhook*newhooks*"
])
def test_addhooks_nohooks(self, testdir):
conf = testdir.makeconftest("""
import sys
@@ -400,7 +393,7 @@ class TestPytestPluginInteractions:
assert len(l) == 0
config.pluginmanager.do_configure(config=config)
assert len(l) == 1
config.pluginmanager.register(A()) # this should lead to a configured() plugin
config.pluginmanager.register(A()) # leads to a configured() plugin
assert len(l) == 2
assert l[0] != l[1]
@@ -440,6 +433,9 @@ class TestPytestPluginInteractions:
pluginmanager.register(p3)
methods = pluginmanager.listattr('m')
assert methods == [p2.m, p3.m, p1.m]
# listattr keeps a cache and deleting
# a function attribute requires clearing it
pluginmanager._listattrcache.clear()
del P1.m.__dict__['tryfirst']
pytest.mark.trylast(getattr(P2.m, 'im_func', P2.m))

View File

@@ -48,19 +48,16 @@ class TestDoctests:
def test_doctest_unexpected_exception(self, testdir):
p = testdir.maketxtfile("""
>>> i = 0
>>> i = 1
>>> x
>>> 0 / i
2
""")
reprec = testdir.inline_run(p)
call = reprec.getcall("pytest_runtest_logreport")
assert call.report.failed
assert call.report.longrepr
# XXX
#testitem, = items
#excinfo = pytest.raises(Failed, "testitem.runtest()")
#repr = testitem.repr_failure(excinfo, ("", ""))
#assert repr.reprlocation
result = testdir.runpytest("--doctest-modules")
result.stdout.fnmatch_lines([
"*unexpected_exception*",
"*>>> i = 0*",
"*>>> 0 / i*",
"*UNEXPECTED*ZeroDivision*",
])
def test_doctestmodule(self, testdir):
p = testdir.makepyfile("""

View File

@@ -1,13 +1,18 @@
import py, pytest,os
from _pytest.helpconfig import collectattr
def test_version(testdir):
def test_version(testdir, pytestconfig):
result = testdir.runpytest("--version")
assert result.ret == 0
#p = py.path.local(py.__file__).dirpath()
result.stderr.fnmatch_lines([
'*py.test*%s*imported from*' % (pytest.__version__, )
])
if pytestconfig.pluginmanager._plugin_distinfo:
result.stderr.fnmatch_lines([
"*setuptools registered plugins:",
"*at*",
])
def test_help(testdir):
result = testdir.runpytest("--help")
@@ -51,3 +56,9 @@ def test_hookvalidation_optional(testdir):
result = testdir.runpytest()
assert result.ret == 0
def test_traceconfig(testdir):
result = testdir.runpytest("--traceconfig")
result.stdout.fnmatch_lines([
"*using*pytest*py*",
"*active plugins*",
])

View File

@@ -6,7 +6,9 @@ def setup_module(mod):
def test_nose_setup(testdir):
p = testdir.makepyfile("""
l = []
from nose.tools import with_setup
@with_setup(lambda: l.append(1), lambda: l.append(2))
def test_hello():
assert l == [1]
@@ -24,6 +26,8 @@ def test_nose_setup(testdir):
def test_nose_setup_func(testdir):
p = testdir.makepyfile("""
from nose.tools import with_setup
l = []
def my_setup():
@@ -34,16 +38,15 @@ def test_nose_setup_func(testdir):
b = 2
l.append(b)
@with_setup(my_setup, my_teardown)
def test_hello():
print l
print (l)
assert l == [1]
def test_world():
print l
print (l)
assert l == [1,2]
test_hello.setup = my_setup
test_hello.teardown = my_teardown
""")
result = testdir.runpytest(p, '-p', 'nose')
result.stdout.fnmatch_lines([
@@ -53,25 +56,25 @@ def test_nose_setup_func(testdir):
def test_nose_setup_func_failure(testdir):
p = testdir.makepyfile("""
l = []
from nose.tools import with_setup
l = []
my_setup = lambda x: 1
my_teardown = lambda x: 2
@with_setup(my_setup, my_teardown)
def test_hello():
print l
print (l)
assert l == [1]
def test_world():
print l
print (l)
assert l == [1,2]
test_hello.setup = my_setup
test_hello.teardown = my_teardown
""")
result = testdir.runpytest(p, '-p', 'nose')
result.stdout.fnmatch_lines([
"*TypeError: <lambda>() takes exactly 1 argument (0 given)*"
"*TypeError: <lambda>() takes exactly 1*0 given*"
])
@@ -83,11 +86,11 @@ def test_nose_setup_func_failure_2(testdir):
my_teardown = 2
def test_hello():
print l
print (l)
assert l == [1]
def test_world():
print l
print (l)
assert l == [1,2]
test_hello.setup = my_setup
@@ -118,11 +121,11 @@ def test_nose_setup_partial(testdir):
my_teardown_partial = partial(my_teardown, 2)
def test_hello():
print l
print (l)
assert l == [1]
def test_world():
print l
print (l)
assert l == [1,2]
test_hello.setup = my_setup_partial
@@ -173,21 +176,21 @@ def test_nose_test_generator_fixtures(testdir):
class TestClass(object):
def setup(self):
print "setup called in", self
print ("setup called in %s" % self)
self.called = ['setup']
def teardown(self):
print "teardown called in", self
print ("teardown called in %s" % self)
eq_(self.called, ['setup'])
self.called.append('teardown')
def test(self):
print "test called in", self
print ("test called in %s" % self)
for i in range(0, 5):
yield self.check, i
def check(self, i):
print "check called in", self
print ("check called in %s" % self)
expect = ['setup']
#for x in range(0, i):
# expect.append('setup')

View File

@@ -196,6 +196,49 @@ class TestGenerator:
assert passed == 4
assert not skipped and not failed
def test_setupstate_is_preserved_134(self, testdir):
# yield-based tests are messy wrt to setupstate because
# during collection they already invoke setup functions
# and then again when they are run. For now, we want to make sure
# that the old 1.3.4 behaviour is preserved such that all
# yielded functions all share the same "self" instance that
# has been used during collection.
o = testdir.makepyfile("""
setuplist = []
class TestClass:
def setup_method(self, func):
#print "setup_method", self, func
setuplist.append(self)
self.init = 42
def teardown_method(self, func):
self.init = None
def test_func1(self):
pass
def test_func2(self):
yield self.func2
yield self.func2
def func2(self):
assert self.init
def test_setuplist():
# once for test_func2 during collection
# once for test_func1 during test run
# once for test_func2 during test run
#print setuplist
assert len(setuplist) == 3, len(setuplist)
assert setuplist[0] == setuplist[2], setuplist
assert setuplist[1] != setuplist[2], setuplist
""")
reprec = testdir.inline_run(o, '-v')
passed, skipped, failed = reprec.countoutcomes()
assert passed == 4
assert not skipped and not failed
class TestFunction:
def test_getmodulecollector(self, testdir):
item = testdir.getitem("def test_func(): pass")
@@ -316,8 +359,8 @@ class TestConftestCustomization:
if path.basename == "test_xyz.py":
return MyModule(path, parent)
""")
testdir.makepyfile("def some(): pass")
testdir.makepyfile(test_xyz="")
testdir.makepyfile("def test_some(): pass")
testdir.makepyfile(test_xyz="def test_func(): pass")
result = testdir.runpytest("--collectonly")
result.stdout.fnmatch_lines([
"*<Module*test_pytest*",
@@ -474,6 +517,10 @@ def test_callspec_repr():
repr(cs)
class TestFillFuncArgs:
def test_fillfuncargs_exposed(self):
# used by oejskit
assert pytest._fillfuncargs == funcargs.fillfuncargs
def test_funcarg_lookupfails(self, testdir):
testdir.makeconftest("""
def pytest_funcarg__xyzsomething(request):
@@ -545,7 +592,8 @@ class TestFillFuncArgs:
item.config.pluginmanager.register(Provider())
if hasattr(item, '_args'):
del item._args
pytest._fillfuncargs(item)
from _pytest.python import fillfuncargs
fillfuncargs(item)
assert len(item.funcargs) == 1
class TestRequest:
@@ -872,11 +920,12 @@ class TestMetafunc:
assert metafunc._calls[2].param == 1
def test_addcall_funcargs(self):
def func(arg1): pass
def func(x): pass
metafunc = funcargs.Metafunc(func)
class obj: pass
metafunc.addcall(funcargs={"x": 2})
metafunc.addcall(funcargs={"x": 3})
pytest.raises(pytest.fail.Exception, "metafunc.addcall({'xyz': 0})")
assert len(metafunc._calls) == 2
assert metafunc._calls[0].funcargs == {'x': 2}
assert metafunc._calls[1].funcargs == {'x': 3}
@@ -960,6 +1009,21 @@ class TestGenfuncFunctional:
"*1 failed, 3 passed*"
])
def test_noself_in_method(self, testdir):
p = testdir.makepyfile("""
def pytest_generate_tests(metafunc):
assert 'xyz' not in metafunc.funcargnames
class TestHello:
def test_hello(xyz):
pass
""")
result = testdir.runpytest(p)
result.stdout.fnmatch_lines([
"*1 pass*",
])
def test_generate_plugin_and_module(self, testdir):
testdir.makeconftest("""
def pytest_generate_tests(metafunc):
@@ -1019,6 +1083,21 @@ class TestGenfuncFunctional:
"*2 pass*",
])
def test_issue28_setup_method_in_generate_tests(self, testdir):
p = testdir.makepyfile("""
def pytest_generate_tests(metafunc):
metafunc.addcall({'arg1': 1})
class TestClass:
def test_method(self, arg1):
assert arg1 == self.val
def setup_method(self, func):
self.val = 1
""")
result = testdir.runpytest(p)
result.stdout.fnmatch_lines([
"*1 pass*",
])
def test_conftest_funcargs_only_available_in_subdir(testdir):
sub1 = testdir.mkpydir("sub1")
@@ -1237,3 +1316,47 @@ def test_customized_python_discovery(testdir):
result.stdout.fnmatch_lines([
"*2 passed*",
])
def test_collector_attributes(testdir):
testdir.makeconftest("""
import pytest
def pytest_pycollect_makeitem(collector):
assert collector.Function == pytest.Function
assert collector.Class == pytest.Class
assert collector.Instance == pytest.Instance
assert collector.Module == pytest.Module
""")
testdir.makepyfile("""
def test_hello():
pass
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines([
"*1 passed*",
])
def test_customize_through_attributes(testdir):
testdir.makeconftest("""
import pytest
class MyFunction(pytest.Function):
pass
class MyInstance(pytest.Instance):
Function = MyFunction
class MyClass(pytest.Class):
Instance = MyInstance
def pytest_pycollect_makeitem(collector, name, obj):
if name.startswith("MyTestClass"):
return MyClass(name, parent=collector)
""")
testdir.makepyfile("""
class MyTestClass:
def test_hello(self):
pass
""")
result = testdir.runpytest("--collectonly")
result.stdout.fnmatch_lines([
"*MyClass*",
"*MyInstance*",
"*MyFunction*test_hello*",
])

View File

@@ -2,10 +2,10 @@ import py, pytest
import os
from _pytest.resultlog import generic_path, ResultLog, \
pytest_configure, pytest_unconfigure
from _pytest.session import Node, Item, FSCollector
from _pytest.main import Node, Item, FSCollector
def test_generic_path(testdir):
from _pytest.session import Session
from _pytest.main import Session
config = testdir.parseconfig()
session = Session(config)
p1 = Node('a', config=config, session=session)

View File

@@ -212,8 +212,8 @@ def test_plugin_specify(testdir):
#)
def test_plugin_already_exists(testdir):
config = testdir.parseconfig("-p", "session")
assert config.option.plugins == ['session']
config = testdir.parseconfig("-p", "terminal")
assert config.option.plugins == ['terminal']
config.pluginmanager.do_configure(config)
def test_exclude(testdir):

View File

@@ -308,6 +308,37 @@ class TestXFail:
"*1 xfailed*",
])
class TestXFailwithSetupTeardown:
def test_failing_setup_issue9(self, testdir):
testdir.makepyfile("""
import pytest
def setup_function(func):
assert 0
@pytest.mark.xfail
def test_func():
pass
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines([
"*1 xfail*",
])
def test_failing_teardown_issue9(self, testdir):
testdir.makepyfile("""
import pytest
def teardown_function(func):
assert 0
@pytest.mark.xfail
def test_func():
pass
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines([
"*1 xfail*",
])
class TestSkipif:
def test_skipif_conditional(self, testdir):
@@ -439,3 +470,62 @@ def test_reportchars(testdir):
"XPASS*test_3*",
"SKIP*four*",
])
@pytest.mark.xfail("hasattr(sys, 'pypy_version_info')")
def test_errors_in_xfail_skip_expressions(testdir):
testdir.makepyfile("""
import pytest
@pytest.mark.skipif("asd")
def test_nameerror():
pass
@pytest.mark.xfail("syntax error")
def test_syntax():
pass
def test_func():
pass
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines([
"*ERROR*test_nameerror*",
"*evaluating*skipif*expression*",
"*asd*",
"*ERROR*test_syntax*",
"*evaluating*xfail*expression*",
" syntax error",
" ^",
"SyntaxError: invalid syntax",
"*1 pass*2 error*",
])
def test_xfail_skipif_with_globals(testdir):
testdir.makepyfile("""
import pytest
x = 3
@pytest.mark.skipif("x == 3")
def test_skip1():
pass
@pytest.mark.xfail("x == 3")
def test_boolean():
assert 0
""")
result = testdir.runpytest("-rsx")
result.stdout.fnmatch_lines([
"*SKIP*x == 3*",
"*XFAIL*test_boolean*",
"*x == 3*",
])
def test_direct_gives_error(testdir):
testdir.makepyfile("""
import pytest
@pytest.mark.skipif(True)
def test_skip1():
pass
""")
result = testdir.runpytest()
result.stdout.fnmatch_lines([
"*1 error*",
])

View File

@@ -4,8 +4,7 @@ terminal reporting of the full testing process.
import pytest,py
import sys
from _pytest.terminal import TerminalReporter, \
CollectonlyReporter, repr_pythonversion, getreportopt
from _pytest.terminal import TerminalReporter, repr_pythonversion, getreportopt
from _pytest import runner
def basic_run_report(item):
@@ -131,6 +130,20 @@ class TestTerminal:
"*test_p2.py <- *test_p1.py:2: TestMore.test_p1*",
])
def test_itemreport_directclasses_not_shown_as_subclasses(self, testdir):
a = testdir.mkpydir("a")
a.join("test_hello.py").write(py.code.Source("""
class TestClass:
def test_method(self):
pass
"""))
result = testdir.runpytest("-v")
assert result.ret == 0
result.stdout.fnmatch_lines([
"*a/test_hello.py*PASS*",
])
assert " <- " not in result.stdout.str()
def test_keyboard_interrupt(self, testdir, option):
p = testdir.makepyfile("""
def test_foobar():
@@ -157,53 +170,35 @@ class TestTerminal:
class TestCollectonly:
def test_collectonly_basic(self, testdir, linecomp):
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
def test_collectonly_basic(self, testdir):
testdir.makepyfile("""
def test_func():
pass
""")
rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
modcol.config.pluginmanager.register(rep)
indent = rep.indent
rep.config.hook.pytest_collectstart(collector=modcol)
linecomp.assert_contains_lines([
"<Module 'test_collectonly_basic.py'>"
])
item = modcol.collect()[0]
rep.config.hook.pytest_itemcollected(item=item)
linecomp.assert_contains_lines([
result = testdir.runpytest("--collectonly",)
result.stdout.fnmatch_lines([
"<Module 'test_collectonly_basic.py'>",
" <Function 'test_func'>",
])
report = rep.config.hook.pytest_make_collect_report(collector=modcol)
rep.config.hook.pytest_collectreport(report=report)
assert rep.indent == indent
def test_collectonly_skipped_module(self, testdir, linecomp):
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
def test_collectonly_skipped_module(self, testdir):
testdir.makepyfile("""
import pytest
pytest.skip("nomod")
""")
rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
modcol.config.pluginmanager.register(rep)
cols = list(testdir.genitems([modcol]))
assert len(cols) == 0
linecomp.assert_contains_lines("""
<Module 'test_collectonly_skipped_module.py'>
!!! Skipped: nomod !!!
pytest.skip("hello")
""")
result = testdir.runpytest("--collectonly", "-rs")
result.stdout.fnmatch_lines([
"SKIP*hello*",
"*1 skip*",
])
def test_collectonly_failed_module(self, testdir, linecomp):
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
raise ValueError(0)
""")
rep = CollectonlyReporter(modcol.config, out=linecomp.stringio)
modcol.config.pluginmanager.register(rep)
cols = list(testdir.genitems([modcol]))
assert len(cols) == 0
linecomp.assert_contains_lines("""
<Module 'test_collectonly_failed_module.py'>
!!! ValueError: 0 !!!
""")
def test_collectonly_failed_module(self, testdir):
testdir.makepyfile("""raise ValueError(0)""")
result = testdir.runpytest("--collectonly")
result.stdout.fnmatch_lines([
"*raise ValueError*",
"*1 error*",
])
def test_collectonly_fatal(self, testdir):
p1 = testdir.makeconftest("""
@@ -228,11 +223,11 @@ class TestCollectonly:
stderr = result.stderr.str().strip()
#assert stderr.startswith("inserting into sys.path")
assert result.ret == 0
extra = result.stdout.fnmatch_lines([
result.stdout.fnmatch_lines([
"*<Module '*.py'>",
"* <Function 'test_func1'*>",
"* <Class 'TestClass'>",
"* <Instance '()'>",
#"* <Instance '()'>",
"* <Function 'test_method'*>",
])
@@ -241,11 +236,11 @@ class TestCollectonly:
result = testdir.runpytest("--collectonly", p)
stderr = result.stderr.str().strip()
assert result.ret == 1
extra = result.stdout.fnmatch_lines(py.code.Source("""
*<Module '*.py'>
*ImportError*
*!!!*failures*!!!
*test_collectonly_error.py:1*
result.stdout.fnmatch_lines(py.code.Source("""
*ERROR*
*import Errlk*
*ImportError*
*1 error*
""").strip())
@@ -418,6 +413,7 @@ class TestTerminalFunctional:
"*test_verbose_reporting.py:10: test_gen*FAIL*",
])
assert result.ret == 1
pytestconfig.pluginmanager.skipifmissing("xdist")
result = testdir.runpytest(p1, '-v', '-n 1')
result.stdout.fnmatch_lines([
@@ -526,7 +522,7 @@ def test_PYTEST_DEBUG(testdir, monkeypatch):
result.stderr.fnmatch_lines([
"*registered*PluginManager*"
])
class TestGenericReporting:
""" this test class can be subclassed with a different option

View File

@@ -396,3 +396,15 @@ def test_djangolike_testcase(testdir):
"*tearDown()*",
"*_post_teardown()*",
])
def test_unittest_not_shown_in_traceback(testdir):
testdir.makepyfile("""
import unittest
class t(unittest.TestCase):
def test_hello(self):
x = 3
self.assertEquals(x, 4)
""")
res = testdir.runpytest()
assert "failUnlessEqual" not in res.stdout.str()

20
tox.ini
View File

@@ -2,8 +2,9 @@
distshare={homedir}/.tox/distshare
envlist=py26,py27,py31,py32,py27-xdist,py25,py24
indexserver=
default = http://pypi.testrun.org
pypi = http://pypi.python.org/simple
testrun = http://pypi.testrun.org
default = http://pypi.testrun.org
[testenv]
changedir=testing
@@ -15,7 +16,7 @@ deps=
[testenv:genscript]
changedir=.
commands= py.test --genscript=pytest1
deps=py>=1.4.0a2
deps=py>=1.4.0
[testenv:py27-xdist]
changedir=.
@@ -23,7 +24,7 @@ basepython=python2.7
deps=pytest-xdist
commands=
py.test -n3 -rfsxX \
--junitxml={envlogdir}/junit-{envname}.xml []
--ignore .tox --junitxml={envlogdir}/junit-{envname}.xml []
[testenv:trial]
changedir=.
@@ -48,7 +49,8 @@ commands=
make html
[testenv:py31]
deps=py>=1.4.0a2
deps=py>1.4.0
:pypi:nose>=1.0
[testenv:py31-xdist]
deps=pytest-xdist
@@ -57,22 +59,20 @@ commands=
--junitxml={envlogdir}/junit-{envname}.xml []
[testenv:py32]
deps=py>=1.4.0a2
[testenv:pypy]
basepython=pypy-c
deps=py>=1.4.0
[testenv:jython]
changedir=testing
commands=
{envpython} {envbindir}/py.test-jython --no-tools-on-path \
-rfsxX --junitxml={envlogdir}/junit-{envname}2.xml [acceptance_test.py plugin]
-rfsxX --junitxml={envlogdir}/junit-{envname}2.xml []
[pytest]
minversion=2.0
plugins=pytester
addopts= -rxf --pyargs --doctest-modules
#addopts= -rxf --pyargs --doctest-modules --ignore=.tox
rsyncdirs=tox.ini pytest.py _pytest testing
python_files=test_*.py *_test.py
python_classes=Test Acceptance
python_functions=test
pep8ignore = E401