merge 1.0.x changes back to trunk

--HG--
branch : trunk
This commit is contained in:
holger krekel 2009-08-14 18:08:48 +02:00
commit 58d7f236a7
35 changed files with 565 additions and 249 deletions

View File

@ -1,18 +1,31 @@
Changes between 1.0.0 and 1.0.1
=====================================
* various unicode fixes: capturing and prints of unicode strings now
work within tests, they are encoded as "utf8" by default, terminalwriting
* added a 'pytest_nose' plugin which handles nose.SkipTest,
nose-style function/method/generator setup/teardown and
tries to report functions correctly.
* unicode fixes: capturing and unicode writes to sys.stdout
(through e.g a print statement) now work within tests,
they are encoded as "utf8" by default, also terminalwriting
was adapted and somewhat unified between windows and linux
* fix issue #27: better reporting on non-collectable items given on commandline
(e.g. pyc files)
* "Test" prefixed classes with an __init__ method are *not* collected by default anymore
* fix issue #33: added --version flag (thanks Benjamin Peterson)
* terser reporting of collection error tracebacks
* fix issue #32: adding support for "incomplete" paths to wcpath.status()
* renaming of arguments to some special rather internal hooks
* "Test" prefixed classes are *not* collected by default anymore if they
have an __init__ method
* monkeypatch setenv() now accepts a "prepend" parameter
* improved reporting of collection error tracebacks
* simplified multicall mechanism and plugin architecture,
renamed some internal methods and argnames
Changes between 1.0.0b9 and 1.0.0
=====================================

View File

@ -0,0 +1,8 @@
import py
def pytest_runtest_call(item, __multicall__):
cap = py.io.StdCapture()
try:
return __multicall__.execute()
finally:
outerr = cap.reset()

View File

@ -90,13 +90,13 @@ Available py.test hooks
====================================
py.test calls hooks functions to implement its `test collection`_, running and
reporting process. Upon loading of a plugin py.test performs
strict checking on contained hook functions. Function and argument names
need to match exactly one of `hook definition specification`_. It thus
provides useful error reporting on mistyped hook or argument names
and minimizes version incompatibilites. Below you find some introductory
information on particular hooks. It's sensible to look at existing
plugins so see example usages and start off with your own plugin.
reporting process. When py.test loads a plugin it validates that all hook functions
conform to the `hook definition specification`_. The hook function name and its
argument names need to match exactly but it is allowed for an implementation
to accept *less* parameters. You'll get useful errors on mistyped hook or
argument names. Read on for some introductory information on particular
hooks. It's sensible to look at existing plugins so see example usages
and start off with your own plugin.
.. _`hook definition specification`: plugin/hookspec.html

64
doc/test/plugin/nose.txt Normal file
View File

@ -0,0 +1,64 @@
pytest_nose plugin
==================
nose-compatibility plugin: allow to run nose test suites natively.
.. contents::
:local:
This is an experimental plugin for allowing to run tests written
in the 'nosetests' style with py.test.
nosetests is a popular clone
of py.test and thus shares some philosophy. This plugin is an
attempt to understand and neutralize differences. It allows to
run nosetests' own test suite and a number of other test suites
without problems.
Usage
-------------
If you type::
py.test -p nose
where you would type ``nosetests``, you can run your nose style tests.
You might also try to run without the nose plugin to see where your test
suite is incompatible to the default py.test.
To avoid the need for specifying a command line option you can set an environment
variable::
PYTEST_PLUGINS=nose
or create a ``conftest.py`` file in your test directory or below::
# conftest.py
pytest_plugins = "nose",
If you find issues or have suggestions you may run::
py.test -p nose --pastebin=all
to create a URL of a test run session and send it with comments to the issue
tracker or mailing list.
Known issues
------------------
- nose-style doctests are not collected and executed correctly,
also fixtures don't work.
Start improving this plugin in 30 seconds
=========================================
Do you find the above documentation or the plugin itself lacking?
1. Download `pytest_nose.py`_ plugin source code
2. put it somewhere as ``pytest_nose.py`` into your import path
3. a subsequent ``py.test`` run will use your local version
Further information: extend_ documentation, other plugins_ or contact_.
.. include:: links.txt

View File

@ -7,7 +7,7 @@ plugins = [
('Plugins related to Python test functions and programs',
'xfail figleaf monkeypatch capture recwarn',),
('Plugins for other testing styles and languages',
'unittest doctest oejskit restdoc'),
'oejskit unittest nose doctest restdoc'),
('Plugins for generic reporting and failure logging',
'pastebin resultlog terminal',),
('internal plugins / core functionality',

View File

@ -52,7 +52,7 @@ initpkg(__name__,
'_com.Registry' : ('./_com.py', 'Registry'),
'_com.MultiCall' : ('./_com.py', 'MultiCall'),
'_com.comregistry' : ('./_com.py', 'comregistry'),
'_com.Hooks' : ('./_com.py', 'Hooks'),
'_com.HookRelay' : ('./_com.py', 'HookRelay'),
# py lib cmdline tools
'cmdline.pytest' : ('./cmdline/pytest.py', 'main',),

View File

@ -5,77 +5,53 @@ py lib plugins and plugin call management
import py
class MultiCall:
""" Manage a specific call into many python functions/methods.
""" execute a call into multiple python functions/methods. """
Simple example:
MultiCall([list1.append, list2.append], 42).execute()
"""
def __init__(self, methods, *args, **kwargs):
def __init__(self, methods, kwargs, firstresult=False):
self.methods = methods[:]
self.args = args
self.kwargs = kwargs
self.kwargs = kwargs.copy()
self.kwargs['__multicall__'] = self
self.results = []
self.firstresult = firstresult
def __repr__(self):
args = []
if self.args:
args.append("posargs=%r" %(self.args,))
kw = self.kwargs
args.append(", ".join(["%s=%r" % x for x in self.kwargs.items()]))
args = " ".join(args)
status = "results: %r, rmethods: %r" % (self.results, self.methods)
return "<MultiCall %s %s>" %(args, status)
status = "%d results, %d meths" % (len(self.results), len(self.methods))
return "<MultiCall %s, kwargs=%r>" %(status, self.kwargs)
def execute(self, firstresult=False):
def execute(self):
while self.methods:
currentmethod = self.methods.pop()
res = self.execute_method(currentmethod)
if hasattr(self, '_ex1'):
self.results = [res]
break
method = self.methods.pop()
kwargs = self.getkwargs(method)
res = method(**kwargs)
if res is not None:
self.results.append(res)
if firstresult:
break
if not firstresult:
if self.firstresult:
return res
if not self.firstresult:
return self.results
if self.results:
return self.results[-1]
def execute_method(self, currentmethod):
self.currentmethod = currentmethod
# provide call introspection if "__call__" is the first positional argument
if hasattr(currentmethod, 'im_self'):
varnames = currentmethod.im_func.func_code.co_varnames
needscall = varnames[1:2] == ('__call__',)
else:
def getkwargs(self, method):
kwargs = {}
for argname in varnames(method):
try:
varnames = currentmethod.func_code.co_varnames
except AttributeError:
# builtin function
varnames = ()
needscall = varnames[:1] == ('__call__',)
if needscall:
return currentmethod(self, *self.args, **self.kwargs)
else:
#try:
return currentmethod(*self.args, **self.kwargs)
#except TypeError:
# print currentmethod.__module__, currentmethod.__name__, self.args, self.kwargs
# raise
def exclude_other_results(self):
self._ex1 = True
kwargs[argname] = self.kwargs[argname]
except KeyError:
pass # might be optional param
return kwargs
def varnames(rawcode):
ismethod = hasattr(rawcode, 'im_self')
rawcode = getattr(rawcode, 'im_func', rawcode)
rawcode = getattr(rawcode, 'func_code', rawcode)
try:
return rawcode.co_varnames[ismethod:]
except AttributeError:
return ()
class Registry:
"""
Manage Plugins: Load plugins and manage calls to plugins.
Manage Plugins: register/unregister call calls to plugins.
"""
logfile = None
MultiCall = MultiCall
def __init__(self, plugins=None):
if plugins is None:
plugins = []
@ -83,6 +59,7 @@ class Registry:
def register(self, plugin):
assert not isinstance(plugin, str)
assert not plugin in self._plugins
self._plugins.append(plugin)
def unregister(self, plugin):
@ -107,45 +84,39 @@ class Registry:
l.reverse()
return l
class Hooks:
def __init__(self, hookspecs, registry=None):
class HookRelay:
def __init__(self, hookspecs, registry):
self._hookspecs = hookspecs
if registry is None:
registry = py._com.comregistry
self.registry = registry
self._registry = registry
for name, method in vars(hookspecs).items():
if name[:1] != "_":
firstresult = getattr(method, 'firstresult', False)
mm = HookCall(registry, name, firstresult=firstresult)
setattr(self, name, mm)
def __repr__(self):
return "<Hooks %r %r>" %(self._hookspecs, self.registry)
setattr(self, name, self._makecall(name))
class HookCall:
def __init__(self, registry, name, firstresult, extralookup=None):
self.registry = registry
def _makecall(self, name, extralookup=None):
hookspecmethod = getattr(self._hookspecs, name)
firstresult = getattr(hookspecmethod, 'firstresult', False)
return HookCaller(self, name, firstresult=firstresult,
extralookup=extralookup)
def _getmethods(self, name, extralookup=()):
return self._registry.listattr(name, extra=extralookup)
def _performcall(self, name, multicall):
return multicall.execute()
class HookCaller:
def __init__(self, hookrelay, name, firstresult, extralookup=()):
self.hookrelay = hookrelay
self.name = name
self.firstresult = firstresult
self.extralookup = extralookup and [extralookup] or ()
def clone(self, extralookup):
return HookCall(self.registry, self.name, self.firstresult, extralookup)
def __repr__(self):
mode = self.firstresult and "firstresult" or "each"
return "<HookCall %r mode=%s %s>" %(self.name, mode, self.registry)
return "<HookCaller %r>" %(self.name,)
def __call__(self, *args, **kwargs):
if args:
raise TypeError("only keyword arguments allowed "
"for api call to %r" % self.name)
attr = self.registry.listattr(self.name, extra=self.extralookup)
mc = MultiCall(attr, **kwargs)
# XXX this should be doable from a hook impl:
if self.registry.logfile:
self.registry.logfile.write("%s(**%s) # firstresult=%s\n" %
(self.name, kwargs, self.firstresult))
self.registry.logfile.flush()
return mc.execute(firstresult=self.firstresult)
def __call__(self, **kwargs):
methods = self.hookrelay._getmethods(self.name, self.extralookup)
mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
return self.hookrelay._performcall(self.name, mc)
comregistry = Registry()
comregistry = Registry([])

View File

@ -39,8 +39,7 @@ class ExceptionInfo(object):
"""
lines = py.std.traceback.format_exception_only(self.type, self.value)
text = ''.join(lines)
if text.endswith('\n'):
text = text[:-1]
text = text.rstrip()
if tryshort:
if text.startswith(self._striptext):
text = text[len(self._striptext):]

View File

@ -200,6 +200,11 @@ def test_tbentry_reinterpret():
def test_excinfo_exconly():
excinfo = py.test.raises(ValueError, h)
assert excinfo.exconly().startswith('ValueError')
excinfo = py.test.raises(ValueError,
"raise ValueError('hello\\nworld')")
msg = excinfo.exconly(tryshort=True)
assert msg.startswith('ValueError')
assert msg.endswith("world")
def test_excinfo_repr():
excinfo = py.test.raises(ValueError, h)
@ -242,7 +247,6 @@ def test_entrysource_Queue_example():
assert s.startswith("def get")
def test_codepath_Queue_example():
py.test.skip("try harder to get at the paths of code objects.")
import Queue
try:
Queue.Queue().get(timeout=0.001)

View File

@ -173,8 +173,8 @@ class TestSourceParsingAndCompiling:
assert len(source) == 6
assert source.getstatementrange(2) == (1, 4)
@py.test.mark.xfail
def test_getstatementrange_bug2(self):
py.test.skip("fix me (issue19)")
source = Source("""\
assert (
33
@ -300,8 +300,8 @@ def test_deindent():
lines = deindent(source.splitlines())
assert lines == ['', 'def f():', ' def g():', ' pass', ' ']
@py.test.mark.xfail
def test_source_of_class_at_eof_without_newline():
py.test.skip("CPython's inspect.getsource is buggy")
# this test fails because the implicit inspect.getsource(A) below
# does not return the "x = 1" last line.
tmpdir = py.test.ensuretemp("source_write_read")

View File

@ -88,8 +88,8 @@ class Gateway(object):
self._channelfactory = ChannelFactory(self, _startcount)
self._cleanup.register(self)
if _startcount == 1: # only import 'py' on the "client" side
from py._com import Hooks
self.hook = Hooks(ExecnetAPI)
import py
self.hook = py._com.HookRelay(ExecnetAPI, py._com.comregistry)
else:
self.hook = ExecnetAPI()

View File

@ -21,7 +21,8 @@ class GatewayManager:
if not spec.chdir and not spec.popen:
spec.chdir = defaultchdir
self.specs.append(spec)
self.hook = py._com.Hooks(py.execnet._HookSpecs)
self.hook = py._com.HookRelay(
py.execnet._HookSpecs, py._com.comregistry)
def makegateways(self):
assert not self.gateways

View File

@ -1,15 +1,22 @@
import py
import os
from py._com import Registry, MultiCall
from py._com import Hooks
from py.__._com import Registry, MultiCall, HookRelay, varnames
pytest_plugins = "xfail"
def test_varnames():
def f(x):
pass
class A:
def f(self, y):
pass
assert varnames(f) == ("x",)
assert varnames(A.f) == ('y',)
assert varnames(A().f) == ('y',)
class TestMultiCall:
def test_uses_copy_of_methods(self):
l = [lambda: 42]
mc = MultiCall(l)
mc = MultiCall(l, {})
repr(mc)
l[:] = []
res = mc.execute()
@ -17,23 +24,20 @@ class TestMultiCall:
def test_call_passing(self):
class P1:
def m(self, __call__, x):
assert __call__.currentmethod == self.m
assert len(__call__.results) == 1
assert not __call__.methods
def m(self, __multicall__, x):
assert len(__multicall__.results) == 1
assert not __multicall__.methods
return 17
class P2:
def m(self, __call__, x):
assert __call__.currentmethod == self.m
assert __call__.args
assert __call__.results == []
assert __call__.methods
def m(self, __multicall__, x):
assert __multicall__.results == []
assert __multicall__.methods
return 23
p1 = P1()
p2 = P2()
multicall = MultiCall([p1.m, p2.m], 23)
multicall = MultiCall([p1.m, p2.m], {'x': 23})
assert "23" in repr(multicall)
reslist = multicall.execute()
assert len(reslist) == 2
@ -43,62 +47,43 @@ class TestMultiCall:
def test_keyword_args(self):
def f(x):
return x + 1
multicall = MultiCall([f], x=23)
assert "x=23" in repr(multicall)
class A:
def f(self, x, y):
return x + y
multicall = MultiCall([f, A().f], dict(x=23, y=24))
assert "'x': 23" in repr(multicall)
assert "'y': 24" in repr(multicall)
reslist = multicall.execute()
assert reslist == [24]
assert "24" in repr(multicall)
assert reslist == [24+23, 24]
assert "2 results" in repr(multicall)
def test_optionalcallarg(self):
class P1:
def m(self, x):
return x
call = MultiCall([P1().m], 23)
assert "23" in repr(call)
assert call.execute() == [23]
assert call.execute(firstresult=True) == 23
def test_keywords_call_error(self):
multicall = MultiCall([lambda x: x], {})
py.test.raises(TypeError, "multicall.execute()")
def test_call_subexecute(self):
def m(__call__):
subresult = __call__.execute(firstresult=True)
def m(__multicall__):
subresult = __multicall__.execute()
return subresult + 1
def n():
return 1
call = MultiCall([n, m])
res = call.execute(firstresult=True)
assert res == 2
def test_call_exclude_other_results(self):
def m(__call__):
__call__.exclude_other_results()
return 10
def n():
return 1
call = MultiCall([n, n, m, n])
call = MultiCall([n, m], {}, firstresult=True)
res = call.execute()
assert res == [10]
# doesn't really make sense for firstresult-mode - because
# we might not have had a chance to run at all.
#res = call.execute(firstresult=True)
#assert res == 10
assert res == 2
def test_call_none_is_no_result(self):
def m1():
return 1
def m2():
return None
mc = MultiCall([m1, m2])
res = mc.execute(firstresult=True)
res = MultiCall([m1, m2], {}, firstresult=True).execute()
assert res == 1
res = MultiCall([m1, m2], {}).execute()
assert res == [1]
class TestRegistry:
def test_MultiCall(self):
plugins = Registry()
assert hasattr(plugins, "MultiCall")
def test_register(self):
registry = Registry()
@ -142,14 +127,14 @@ class TestRegistry:
def test_api_and_defaults():
assert isinstance(py._com.comregistry, Registry)
class TestHooks:
class TestHookRelay:
def test_happypath(self):
registry = Registry()
class Api:
def hello(self, arg):
pass
mcm = Hooks(hookspecs=Api, registry=registry)
mcm = HookRelay(hookspecs=Api, registry=registry)
assert hasattr(mcm, 'hello')
assert repr(mcm.hello).find("hello") != -1
class Plugin:
@ -160,23 +145,21 @@ class TestHooks:
assert l == [4]
assert not hasattr(mcm, 'world')
def test_needskeywordargs(self):
def test_only_kwargs(self):
registry = Registry()
class Api:
def hello(self, arg):
pass
mcm = Hooks(hookspecs=Api, registry=registry)
excinfo = py.test.raises(TypeError, "mcm.hello(3)")
assert str(excinfo.value).find("only keyword arguments") != -1
assert str(excinfo.value).find("hello(self, arg)")
mcm = HookRelay(hookspecs=Api, registry=registry)
py.test.raises(TypeError, "mcm.hello(3)")
def test_firstresult(self):
def test_firstresult_definition(self):
registry = Registry()
class Api:
def hello(self, arg): pass
hello.firstresult = True
mcm = Hooks(hookspecs=Api, registry=registry)
mcm = HookRelay(hookspecs=Api, registry=registry)
class Plugin:
def hello(self, arg):
return arg + 1
@ -186,15 +169,16 @@ class TestHooks:
def test_default_plugins(self):
class Api: pass
mcm = Hooks(hookspecs=Api)
assert mcm.registry == py._com.comregistry
mcm = HookRelay(hookspecs=Api, registry=py._com.comregistry)
assert mcm._registry == py._com.comregistry
def test_hooks_extra_plugins(self):
registry = Registry()
class Api:
def hello(self, arg):
pass
hook_hello = Hooks(hookspecs=Api, registry=registry).hello
hookrelay = HookRelay(hookspecs=Api, registry=registry)
hook_hello = hookrelay.hello
class Plugin:
def hello(self, arg):
return arg + 1
@ -202,7 +186,7 @@ class TestHooks:
class Plugin2:
def hello(self, arg):
return arg + 2
newhook = hook_hello.clone(extralookup=Plugin2())
newhook = hookrelay._makecall("hello", extralookup=Plugin2())
l = newhook(arg=3)
assert l == [5, 4]
l2 = hook_hello(arg=3)

View File

@ -225,6 +225,12 @@ class TestWCSvnCommandPath(CommonSvnTests):
'''
XMLWCStatus.fromstring(xml, self.root)
def test_status_wrong_xml(self):
# testing for XML without author - this used to raise an exception
xml = u'<entry path="/home/jean/zope/venv/projectdb/parts/development-products/DataGridField">\n<wc-status item="incomplete" props="none" revision="784">\n</wc-status>\n</entry>'
st = XMLWCStatus.fromstring(xml, self.root)
assert len(st.incomplete) == 1
def test_diff(self):
p = self.root / 'anotherfile'
out = p.diff(rev=2)

View File

@ -671,6 +671,10 @@ class XMLWCStatus(WCStatus):
wcpath = rootwcpath.join(path, abs=1)
rootstatus.ignored.append(wcpath)
continue
elif itemstatus == 'incomplete':
wcpath = rootwcpath.join(path, abs=1)
rootstatus.incomplete.append(wcpath)
continue
rev = statusel.getAttribute('revision')
if itemstatus == 'added' or itemstatus == 'none':

View File

@ -396,6 +396,11 @@ class Directory(FSCollector):
def _ignore(self, path):
ignore_paths = self.config.getconftest_pathlist("collect_ignore", path=path)
return ignore_paths and path in ignore_paths
# XXX more refined would be:
if ignore_paths:
for p in ignore_paths:
if path == p or path.relto(p):
return True
def consider(self, path):
if self._ignore(path):

View File

@ -77,8 +77,8 @@ class DSession(Session):
self.item2nodes = {}
super(DSession, self).__init__(config=config)
#def pytest_configure(self, __call__, config):
# __call__.execute()
#def pytest_configure(self, __multicall__, config):
# __multicall__.execute()
# try:
# config.getxspecs()
# except config.Error:

View File

@ -15,10 +15,15 @@ def pytest_funcarg__testdir(request):
# testdir.plugins.append(obj.testplugin)
# break
#else:
basename = request.module.__name__.split(".")[-1]
if basename.startswith("pytest_"):
modname = request.module.__name__.split(".")[-1]
if modname.startswith("pytest_"):
testdir.plugins.append(vars(request.module))
testdir.plugins.append(basename)
testdir.plugins.append(modname)
#elif modname.startswith("test_pytest"):
# pname = modname[5:]
# assert pname not in testdir.plugins
# testdir.plugins.append(pname)
# #testdir.plugins.append(vars(request.module))
else:
pass # raise ValueError("need better support code")
return testdir

View File

@ -47,7 +47,7 @@ class HookRecorder:
recorder = RecordCalls()
self._recorders[hookspecs] = recorder
self._comregistry.register(recorder)
self.hook = py._com.Hooks(hookspecs, registry=self._comregistry)
self.hook = py._com.HookRelay(hookspecs, registry=self._comregistry)
def finish_recording(self):
for recorder in self._recorders.values():

View File

@ -181,11 +181,11 @@ class CaptureManager:
capfuncarg._finalize()
del self._capturing_funcargs
def pytest_make_collect_report(self, __call__, collector):
def pytest_make_collect_report(self, __multicall__, collector):
method = self._getmethod(collector.config, collector.fspath)
self.resumecapture(method)
try:
rep = __call__.execute(firstresult=True)
rep = __multicall__.execute()
finally:
outerr = self.suspendcapture()
addouterr(rep, outerr)
@ -204,11 +204,11 @@ class CaptureManager:
def pytest_runtest_teardown(self, item):
self.resumecapture_item(item)
def pytest__teardown_final(self, __call__, session):
def pytest__teardown_final(self, __multicall__, session):
method = self._getmethod(session.config, None)
self.resumecapture(method)
try:
rep = __call__.execute(firstresult=True)
rep = __multicall__.execute()
finally:
outerr = self.suspendcapture()
if rep:
@ -219,9 +219,9 @@ class CaptureManager:
if hasattr(self, '_capturing'):
self.suspendcapture()
def pytest_runtest_makereport(self, __call__, item, call):
def pytest_runtest_makereport(self, __multicall__, item, call):
self.deactivate_funcargs()
rep = __call__.execute(firstresult=True)
rep = __multicall__.execute()
outerr = self.suspendcapture()
outerr = (item.outerr[0] + outerr[0], item.outerr[1] + outerr[1])
if not rep.passed:

View File

@ -1,9 +1,10 @@
""" default hooks and general py.test options. """
import sys
import py
def pytest_pyfunc_call(__call__, pyfuncitem):
if not __call__.execute(firstresult=True):
def pytest_pyfunc_call(__multicall__, pyfuncitem):
if not __multicall__.execute():
testfunction = pyfuncitem.obj
if pyfuncitem._isyieldedfunction():
testfunction(*pyfuncitem._args)
@ -90,10 +91,17 @@ def pytest_addoption(parser):
help="shortcut for '--dist=load --tx=NUM*popen'")
group.addoption('--rsyncdir', action="append", default=[], metavar="dir1",
help="add directory for rsyncing to remote tx nodes.")
group.addoption('--version', action="store_true",
help="display version information")
def pytest_configure(config):
fixoptions(config)
setsession(config)
if config.option.version:
p = py.path.local(py.__file__).dirpath()
print "This is py.test version %s, imported from %s" % (
py.__version__, p)
sys.exit(0)
#xxxloadplugins(config)
def fixoptions(config):

View File

@ -32,10 +32,10 @@ class Execnetcleanup:
#for gw in l:
# gw.join()
def pytest_pyfunc_call(self, __call__, pyfuncitem):
def pytest_pyfunc_call(self, __multicall__, pyfuncitem):
if self._gateways is not None:
gateways = self._gateways[:]
res = __call__.execute(firstresult=True)
res = __multicall__.execute()
while len(self._gateways) > len(gateways):
self._gateways[-1].exit()
return res

View File

@ -8,14 +8,27 @@ def pytest_addoption(parser):
def pytest_configure(config):
hooklog = config.getvalue("hooklog")
if hooklog:
assert not config.pluginmanager.comregistry.logfile
config.pluginmanager.comregistry.logfile = open(hooklog, 'w')
config._hooklogfile = open(hooklog, 'w', 0)
config._hooklog_oldperformcall = config.hook._performcall
config.hook._performcall = (lambda name, multicall:
logged_call(name=name, multicall=multicall, config=config))
def logged_call(name, multicall, config):
f = config._hooklogfile
f.write("%s(**%s)\n" % (name, multicall.kwargs))
try:
res = config._hooklog_oldperformcall(name=name, multicall=multicall)
except:
f.write("-> exception")
raise
f.write("-> %r" % (res,))
return res
def pytest_unconfigure(config):
f = config.pluginmanager.comregistry.logfile
if f:
f.close()
config.pluginmanager.comregistry.logfile = None
try:
del config.hook.__dict__['_performcall']
except KeyError:
pass
# ===============================================================================
# plugin tests

View File

@ -4,7 +4,7 @@ safely patch object attributes, dicts and environment variables.
Usage
----------------
Use the `monkeypatch funcarg`_ to safely patch the environment
Use the `monkeypatch funcarg`_ to safely patch environment
variables, object attributes or dictionaries. For example, if you want
to set the environment variable ``ENV1`` and patch the
``os.path.abspath`` function to return a particular value during a test
@ -22,6 +22,15 @@ old state. After the test function finished execution all
modifications will be reverted. See the `monkeypatch blog post`_
for an extensive discussion.
To add to a possibly existing environment parameter you
can use this example:
.. sourcecode:: python
def test_mypath_finding(monkeypatch):
monkeypatch.setenv('PATH', 'x/y', prepend=":")
# x/y will be at the beginning of $PATH
.. _`monkeypatch blog post`: http://tetamap.wordpress.com/2009/03/03/monkeypatching-in-unit-tests-done-right/
"""
@ -57,8 +66,11 @@ class MonkeyPatch:
self._setitem.insert(0, (dictionary, name, dictionary.get(name, notset)))
dictionary[name] = value
def setenv(self, name, value):
self.setitem(os.environ, name, str(value))
def setenv(self, name, value, prepend=None):
value = str(value)
if prepend and name in os.environ:
value = value + prepend + os.environ[name]
self.setitem(os.environ, name, value)
def finalize(self):
for obj, name, value in self._setattr:
@ -111,6 +123,16 @@ def test_setenv():
monkeypatch.finalize()
assert 'XYZ123' not in os.environ
def test_setenv_prepend():
import os
monkeypatch = MonkeyPatch()
monkeypatch.setenv('XYZ123', 2, prepend="-")
assert os.environ['XYZ123'] == "2"
monkeypatch.setenv('XYZ123', 3, prepend="-")
assert os.environ['XYZ123'] == "3-2"
monkeypatch.finalize()
assert 'XYZ123' not in os.environ
def test_monkeypatch_plugin(testdir):
reprec = testdir.inline_runsource("""
pytest_plugins = 'pytest_monkeypatch',

View File

@ -0,0 +1,97 @@
"""nose-compatibility plugin: allow to run nose test suites natively.
This is an experimental plugin for allowing to run tests written
in the 'nosetests' style with py.test.
nosetests is a popular clone
of py.test and thus shares some philosophy. This plugin is an
attempt to understand and neutralize differences. It allows to
run nosetests' own test suite and a number of other test suites
without problems.
Usage
-------------
If you type::
py.test -p nose
where you would type ``nosetests``, you can run your nose style tests.
You might also try to run without the nose plugin to see where your test
suite is incompatible to the default py.test.
To avoid the need for specifying a command line option you can set an environment
variable::
PYTEST_PLUGINS=nose
or create a ``conftest.py`` file in your test directory or below::
# conftest.py
pytest_plugins = "nose",
If you find issues or have suggestions you may run::
py.test -p nose --pastebin=all
to create a URL of a test run session and send it with comments to the issue
tracker or mailing list.
Known issues
------------------
- nose-style doctests are not collected and executed correctly,
also fixtures don't work.
"""
import py
import inspect
import sys
def pytest_runtest_makereport(__multicall__, item, call):
SkipTest = getattr(sys.modules.get('nose', None), 'SkipTest', None)
if SkipTest:
if call.excinfo and call.excinfo.errisinstance(SkipTest):
# let's substitute the excinfo with a py.test.skip one
call2 = call.__class__(lambda: py.test.skip(str(call.excinfo.value)), call.when)
call.excinfo = call2.excinfo
def pytest_report_iteminfo(item):
# nose 0.11.1 uses decorators for "raises" and other helpers.
# for reporting progress by filename we fish for the filename
if isinstance(item, py.test.collect.Function):
obj = item.obj
if hasattr(obj, 'compat_co_firstlineno'):
fn = sys.modules[obj.__module__].__file__
if fn.endswith(".pyc"):
fn = fn[:-1]
#assert 0
#fn = inspect.getsourcefile(obj) or inspect.getfile(obj)
lineno = obj.compat_co_firstlineno
return py.path.local(fn), lineno, obj.__module__
def pytest_runtest_setup(item):
if isinstance(item, (py.test.collect.Function)):
if isinstance(item.parent, py.test.collect.Generator):
gen = item.parent
if not hasattr(gen, '_nosegensetup'):
call_optional(gen.obj, 'setup')
if isinstance(gen.parent, py.test.collect.Instance):
call_optional(gen.parent.obj, 'setup')
gen._nosegensetup = True
call_optional(item.obj, 'setup')
def pytest_runtest_teardown(item):
if isinstance(item, py.test.collect.Function):
call_optional(item.obj, 'teardown')
#if hasattr(item.parent, '_nosegensetup'):
# #call_optional(item._nosegensetup, 'teardown')
# del item.parent._nosegensetup
def pytest_make_collect_report(collector):
if isinstance(collector, py.test.collect.Generator):
call_optional(collector.obj, 'setup')
def call_optional(obj, name):
method = getattr(obj, name, None)
if method:
method()

View File

@ -33,9 +33,9 @@ def pytest_addoption(parser):
type="choice", choices=['failed', 'all'],
help="send failed|all info to Pocoo pastebin service.")
def pytest_configure(__call__, config):
def pytest_configure(__multicall__, config):
import tempfile
__call__.execute()
__multicall__.execute()
if config.option.pastebin == "all":
config._pastebinfile = tempfile.TemporaryFile()
tr = config.pluginmanager.impname2plugin['terminalreporter']

View File

@ -241,22 +241,17 @@ class TerminalReporter:
if self.config.option.traceconfig:
plugins = []
for plugin in self.config.pluginmanager.comregistry:
name = plugin.__class__.__name__
if name.endswith("Plugin"):
name = name[:-6]
#if name == "Conftest":
# XXX get filename
plugins.append(name)
else:
plugins.append(str(plugin))
name = getattr(plugin, '__name__', None)
if name is None:
name = plugin.__class__.__name__
plugins.append(name)
plugins = ", ".join(plugins)
self.write_line("active plugins: %s" %(plugins,))
for i, testarg in py.builtin.enumerate(self.config.args):
self.write_line("test object %d: %s" %(i+1, testarg))
def pytest_sessionfinish(self, __call__, session, exitstatus):
__call__.execute()
def pytest_sessionfinish(self, exitstatus, __multicall__):
__multicall__.execute()
self._tw.line("")
if exitstatus in (0, 1, 2):
self.summary_errors()

View File

@ -55,6 +55,9 @@ class UnitTestFunction(py.test.collect.Function):
if obj is not _dummy:
self._obj = obj
self._sort_value = sort_value
if hasattr(self.parent, 'newinstance'):
self.parent.newinstance()
self.obj = self._getobj()
def runtest(self):
target = self.obj
@ -87,7 +90,6 @@ def test_simple_unittest(testdir):
def test_setup(testdir):
testpath = testdir.makepyfile(test_two="""
import unittest
pytest_plugins = "pytest_unittest" # XXX
class MyTestCase(unittest.TestCase):
def setUp(self):
self.foo = 1
@ -98,6 +100,18 @@ def test_setup(testdir):
rep = reprec.matchreport("test_setUp")
assert rep.passed
def test_new_instances(testdir):
testpath = testdir.makepyfile("""
import unittest
class MyTestCase(unittest.TestCase):
def test_func1(self):
self.x = 2
def test_func2(self):
assert not hasattr(self, 'x')
""")
reprec = testdir.inline_run(testpath)
reprec.assertoutcome(passed=2)
def test_teardown(testdir):
testpath = testdir.makepyfile(test_three="""
import unittest

View File

@ -19,14 +19,12 @@ when it fails. Instead terminal reporting will list it in the
import py
pytest_plugins = ['keyword']
def pytest_runtest_makereport(__call__, item, call):
def pytest_runtest_makereport(__multicall__, item, call):
if call.when != "call":
return
if hasattr(item, 'obj') and hasattr(item.obj, 'func_dict'):
if 'xfail' in item.obj.func_dict:
res = __call__.execute(firstresult=True)
res = __multicall__.execute()
if call.excinfo:
res.skipped = True
res.failed = res.passed = False
@ -53,6 +51,9 @@ def pytest_terminal_summary(terminalreporter):
modpath = rep.item.getmodpath(includemodule=True)
pos = "%s %s:%d: " %(modpath, entry.path, entry.lineno)
reason = rep.longrepr.reprcrash.message
i = reason.find("\n")
if i != -1:
reason = reason[:i]
tr._tw.line("%s %s" %(pos, reason))
xpassed = terminalreporter.stats.get("xpassed")
@ -89,3 +90,4 @@ def test_xfail(testdir):
"*test_that*",
])
assert result.ret == 1

View File

@ -0,0 +1,87 @@
import py
py.test.importorskip("nose")
def test_nose_setup(testdir):
p = testdir.makepyfile("""
l = []
def test_hello():
assert l == [1]
def test_world():
assert l == [1,2]
test_hello.setup = lambda: l.append(1)
test_hello.teardown = lambda: l.append(2)
""")
result = testdir.runpytest(p, '-p', 'nose')
result.stdout.fnmatch_lines([
"*2 passed*"
])
def test_nose_test_generator_fixtures(testdir):
p = testdir.makepyfile("""
# taken from nose-0.11.1 unit_tests/test_generator_fixtures.py
from nose.tools import eq_
called = []
def outer_setup():
called.append('outer_setup')
def outer_teardown():
called.append('outer_teardown')
def inner_setup():
called.append('inner_setup')
def inner_teardown():
called.append('inner_teardown')
def test_gen():
called[:] = []
for i in range(0, 5):
yield check, i
def check(i):
expect = ['outer_setup']
for x in range(0, i):
expect.append('inner_setup')
expect.append('inner_teardown')
expect.append('inner_setup')
eq_(called, expect)
test_gen.setup = outer_setup
test_gen.teardown = outer_teardown
check.setup = inner_setup
check.teardown = inner_teardown
class TestClass(object):
def setup(self):
print "setup called in", self
self.called = ['setup']
def teardown(self):
print "teardown called in", self
eq_(self.called, ['setup'])
self.called.append('teardown')
def test(self):
print "test called in", self
for i in range(0, 5):
yield self.check, i
def check(self, i):
print "check called in", self
expect = ['setup']
#for x in range(0, i):
# expect.append('setup')
# expect.append('teardown')
#expect.append('setup')
eq_(self.called, expect)
""")
result = testdir.runpytest(p, '-p', 'nose')
result.stdout.fnmatch_lines([
"*10 passed*"
])

View File

@ -16,10 +16,9 @@ class PluginManager(object):
if comregistry is None:
comregistry = py._com.Registry()
self.comregistry = comregistry
self.MultiCall = self.comregistry.MultiCall
self.impname2plugin = {}
self.hook = py._com.Hooks(
self.hook = py._com.HookRelay(
hookspecs=hookspec,
registry=self.comregistry)
@ -135,15 +134,16 @@ class PluginManager(object):
fail = True
else:
method_args = getargs(method)
if '__call__' in method_args:
method_args.remove('__call__')
if '__multicall__' in method_args:
method_args.remove('__multicall__')
hook = hooks[name]
hookargs = getargs(hook)
for arg, hookarg in zip(method_args, hookargs):
if arg != hookarg:
Print("argument mismatch: %r != %r" %(arg, hookarg))
Print("actual : %s" %(formatdef(method)))
Print("required:", formatdef(hook))
for arg in method_args:
if arg not in hookargs:
Print("argument %r not available" %(arg, ))
Print("actual definition: %s" %(formatdef(method)))
Print("available hook arguments: %s" %
", ".join(hookargs))
fail = True
break
#if not fail:
@ -166,20 +166,24 @@ class PluginManager(object):
return self.hook.pytest_internalerror(excrepr=excrepr)
def do_addoption(self, parser):
methods = self.comregistry.listattr("pytest_addoption", reverse=True)
mc = py._com.MultiCall(methods, parser=parser)
mname = "pytest_addoption"
methods = self.comregistry.listattr(mname, reverse=True)
mc = py._com.MultiCall(methods, {'parser': parser})
mc.execute()
def pytest_plugin_registered(self, plugin):
if hasattr(self, '_config'):
self.call_plugin(plugin, "pytest_addoption", parser=self._config._parser)
self.call_plugin(plugin, "pytest_configure", config=self._config)
self.call_plugin(plugin, "pytest_addoption",
{'parser': self._config._parser})
self.call_plugin(plugin, "pytest_configure",
{'config': self._config})
#dic = self.call_plugin(plugin, "pytest_namespace")
#self._updateext(dic)
def call_plugin(self, plugin, methname, **kwargs):
return self.MultiCall(self.listattr(methname, plugins=[plugin]),
**kwargs).execute(firstresult=True)
def call_plugin(self, plugin, methname, kwargs):
return py._com.MultiCall(
methods=self.listattr(methname, plugins=[plugin]),
kwargs=kwargs, firstresult=True).execute()
def _updateext(self, dic):
if dic:
@ -200,9 +204,6 @@ class PluginManager(object):
config.hook.pytest_unconfigure(config=config)
config.pluginmanager.unregister(self)
class Ext:
""" namespace for extension objects. """
#
# XXX old code to automatically load classes
#

View File

@ -155,8 +155,8 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
cls = clscol and clscol.obj or None
metafunc = funcargs.Metafunc(funcobj, config=self.config,
cls=cls, module=module)
gentesthook = self.config.hook.pytest_generate_tests.clone(
extralookup=module)
gentesthook = self.config.hook._makecall(
"pytest_generate_tests", extralookup=module)
gentesthook(metafunc=metafunc)
if not metafunc._calls:
return self.Function(name, parent=self)

View File

@ -3,6 +3,14 @@ import py
EXPECTTIMEOUT=10.0
class TestGeneralUsage:
def test_version(self, testdir):
assert py.version == py.__version__
result = testdir.runpytest("--version")
assert result.ret == 0
p = py.path.local(py.__file__).dirpath()
assert result.stderr.fnmatch_lines([
'*py.test*%s*, imported from: %s*' % (py.version, p)
])
def test_config_error(self, testdir):
testdir.makeconftest("""
def pytest_configure(config):

View File

@ -145,7 +145,7 @@ class TestCollectFS:
names = [x.name for x in col.collect()]
assert names == ["dir1", "dir2", "test_one.py", "test_two.py", "x"]
class TestCollectPluginHooks:
class TestCollectPluginHookRelay:
def test_pytest_collect_file(self, testdir):
tmpdir = testdir.tmpdir
wascalled = []

View File

@ -4,7 +4,7 @@ from py.__.test.pluginmanager import PluginManager, canonical_importname, collec
class TestBootstrapping:
def test_consider_env_fails_to_import(self, monkeypatch):
pluginmanager = PluginManager()
monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'nonexistingmodule')
monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",")
py.test.raises(ImportError, "pluginmanager.consider_env()")
def test_preparse_args(self):
@ -50,7 +50,7 @@ class TestBootstrapping:
plugin = py.test.config.pluginmanager.getplugin('x500')
assert plugin is not None
""")
monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'pytest_x500')
monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
result = testdir.runpytest(p)
assert result.ret == 0
extra = result.stdout.fnmatch_lines(["*1 passed in*"])
@ -185,7 +185,7 @@ class TestPytestPluginInteractions:
assert hello == "world"
""")
result = testdir.runpytest(p)
assert result.stdout.fnmatch_lines([
result.stdout.fnmatch_lines([
"*1 passed*"
])
@ -222,10 +222,6 @@ class TestPytestPluginInteractions:
config.pluginmanager.register(A())
assert len(l) == 2
def test_MultiCall(self):
pp = PluginManager()
assert hasattr(pp, 'MultiCall')
# lower level API
def test_listattr(self):
@ -247,3 +243,12 @@ def test_collectattr():
assert list(methods) == ['pytest_hello', 'pytest_world']
methods = py.builtin.sorted(collectattr(B()))
assert list(methods) == ['pytest_hello', 'pytest_world']
@py.test.mark.xfail
def test_namespace_has_default_and_env_plugins(testdir):
p = testdir.makepyfile("""
import py
py.test.mark
""")
result = testdir.runpython(p)
assert result.ret == 0