Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
527bc472a8 | ||
|
|
007f0daeb9 | ||
|
|
55657d6c51 | ||
|
|
1a7c6ecc42 | ||
|
|
f2670651b3 | ||
|
|
5470cadbff | ||
|
|
f8e3fe8fbf | ||
|
|
c552b58dc5 | ||
|
|
18e784c9c9 | ||
|
|
5bef795ba7 | ||
|
|
a6c518e68c | ||
|
|
7e44c38570 | ||
|
|
9c952b3ce0 | ||
|
|
bfe6e98abb | ||
|
|
07cee24122 | ||
|
|
22fac92ca0 | ||
|
|
318e8a404b | ||
|
|
fadd1a2313 | ||
|
|
070c73ff2f | ||
|
|
682773e0cb | ||
|
|
6f3b84da9f | ||
|
|
f1b5dae1fb | ||
|
|
8d62e4c71c | ||
|
|
f6c1e49287 | ||
|
|
27577170e1 | ||
|
|
2f2586af72 | ||
|
|
70ceb946e4 | ||
|
|
d2f9b41519 | ||
|
|
2bd0c98801 | ||
|
|
5a5a618dcb | ||
|
|
98cd8edb71 | ||
|
|
e7b69a2ac0 | ||
|
|
74b9ebc1cd | ||
|
|
3004fe3915 | ||
|
|
eb225456d7 | ||
|
|
b04f87b1a6 | ||
|
|
35b0b376f0 | ||
|
|
762ea71f67 | ||
|
|
adacd3491d | ||
|
|
709d5e3f2c | ||
|
|
d8d88ede65 | ||
|
|
b8f0d10f80 | ||
|
|
aea4d1bd7a | ||
|
|
2b750074f4 | ||
|
|
88cfaebbcb | ||
|
|
426e056d2b | ||
|
|
4445685285 | ||
|
|
ae9b7a8bea | ||
|
|
5daef51000 | ||
|
|
647b56614a | ||
|
|
1b3fb3d229 | ||
|
|
170c78cef9 | ||
|
|
8f5d837ef6 | ||
|
|
0ec5f3fd6c | ||
|
|
8631c1f57a | ||
|
|
821f493378 | ||
|
|
4086d46378 | ||
|
|
a15983cb33 | ||
|
|
9ab256c296 | ||
|
|
7db9e98b55 | ||
|
|
e6541ed14e | ||
|
|
fc4f72cb1f | ||
|
|
feea4ea3d5 | ||
|
|
513482f4f7 | ||
|
|
2e80512bb8 | ||
|
|
c7531705fc | ||
|
|
752965c298 | ||
|
|
96a687b97c | ||
|
|
d894bae281 | ||
|
|
f1fc6e5eb6 | ||
|
|
ca72c162c8 | ||
|
|
9bcb66d9a5 |
2
.hgtags
2
.hgtags
@@ -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
103
CHANGELOG
@@ -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
|
||||
|
||||
10
ISSUES.txt
10
ISSUES.txt
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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:
|
||||
|
||||
112
_pytest/core.py
112
_pytest/core.py
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
2
doc/_templates/indexsidebar.html
vendored
2
doc/_templates/indexsidebar.html
vendored
@@ -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>
|
||||
|
||||
1
doc/_templates/layout.html
vendored
1
doc/_templates/layout.html
vendored
@@ -19,6 +19,7 @@
|
||||
<a href="{{ pathto('getting-started') }}">install</a> |
|
||||
<a href="{{ pathto('example/index') }}">examples</a> |
|
||||
<a href="{{ pathto('customize') }}">customize</a> |
|
||||
<a href="http://https://bitbucket.org/hpk42/pytest/issues?status=new&status=open">issues</a>|
|
||||
<a href="{{ pathto('contact') }}">contact</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,5 +5,7 @@ Release announcements
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
release-2.0.2
|
||||
release-2.0.1
|
||||
release-2.0.0
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
67
doc/announce/release-2.0.1.txt
Normal file
67
doc/announce/release-2.0.1.txt
Normal 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.
|
||||
73
doc/announce/release-2.0.2.txt
Normal file
73
doc/announce/release-2.0.2.txt
Normal 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)
|
||||
@@ -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 =========================
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
---------------------------------------------------
|
||||
|
||||
14
doc/conf.py
14
doc/conf.py
@@ -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.
|
||||
|
||||
@@ -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`_
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =========================
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =========================
|
||||
|
||||
|
||||
@@ -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 =============================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =============================
|
||||
|
||||
@@ -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 =========================
|
||||
|
||||
@@ -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 =============================
|
||||
|
||||
@@ -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")
|
||||
|
||||
32
doc/extracol
32
doc/extracol
@@ -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
|
||||
65
doc/faq.txt
65
doc/faq.txt
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
20
doc/mark.txt
20
doc/mark.txt
@@ -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 ..
|
||||
|
||||
|
||||
@@ -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 =============================
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
110
doc/plugins.txt
110
doc/plugins.txt
@@ -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:
|
||||
|
||||
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -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
|
||||
|
||||
130
doc/skipping.txt
130
doc/skipping.txt
@@ -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")
|
||||
|
||||
|
||||
@@ -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/
|
||||
|
||||
@@ -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`_
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 =========================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
|
||||
6
setup.py
6
setup.py
@@ -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()
|
||||
@@ -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")
|
||||
|
||||
@@ -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*",
|
||||
])
|
||||
|
||||
@@ -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("""
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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*",
|
||||
])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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("""
|
||||
|
||||
@@ -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*",
|
||||
])
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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*",
|
||||
])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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*",
|
||||
])
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
20
tox.ini
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user