[svn r62211] merge 60797:HEAD of pytestplugin branch:
this merge contains: * a new plugin architecture * a pluginized pytest core * many pytest related refactorings * refactorings/streamlining of pytest's own tests --HG-- branch : trunk
This commit is contained in:
parent
1c85d7fe9a
commit
c17a09adaf
|
|
@ -1,86 +0,0 @@
|
||||||
"""
|
|
||||||
automatically collect and run traditional "unittest.py" style tests.
|
|
||||||
|
|
||||||
drop this conftest.py into your project directory so that
|
|
||||||
all testing directories are below it.
|
|
||||||
|
|
||||||
you can mix unittest TestCase subclasses and
|
|
||||||
py.test style tests (discovery based on name).
|
|
||||||
|
|
||||||
user-extensions such as a custom test_suite()
|
|
||||||
will not be considered (see XXX).
|
|
||||||
|
|
||||||
$HeadURL: https://codespeak.net/svn/py/trunk/contrib/py_unittest/conftest.py $
|
|
||||||
$Id: conftest.py 58288 2008-09-21 08:17:11Z hpk $
|
|
||||||
"""
|
|
||||||
import py
|
|
||||||
import unittest
|
|
||||||
import sys
|
|
||||||
|
|
||||||
__version__ = "$Rev: 58288 $".split()[1]
|
|
||||||
|
|
||||||
def configproperty(name):
|
|
||||||
def fget(self):
|
|
||||||
ret = self._config.getvalue(name, self.fspath)
|
|
||||||
return ret
|
|
||||||
return property(fget)
|
|
||||||
|
|
||||||
class Module(py.test.collect.Module):
|
|
||||||
UnitTestCase = configproperty('UnitTestCase')
|
|
||||||
def makeitem(self, name, obj, usefilters=True):
|
|
||||||
# XXX add generic test_suite() support(?)
|
|
||||||
if py.std.inspect.isclass(obj) and issubclass(obj, unittest.TestCase):
|
|
||||||
return self.UnitTestCase(name, parent=self)
|
|
||||||
elif callable(obj) and getattr(obj, 'func_name', '') == 'test_suite':
|
|
||||||
return None
|
|
||||||
return super(Module, self).makeitem(name, obj, usefilters)
|
|
||||||
|
|
||||||
class UnitTestCase(py.test.collect.Class):
|
|
||||||
TestCaseInstance = configproperty('TestCaseInstance')
|
|
||||||
def collect(self):
|
|
||||||
return [self.TestCaseInstance("()", self)]
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def teardown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
_dummy = object()
|
|
||||||
class TestCaseInstance(py.test.collect.Instance):
|
|
||||||
UnitTestFunction = configproperty('UnitTestFunction')
|
|
||||||
def collect(self):
|
|
||||||
loader = unittest.TestLoader()
|
|
||||||
names = loader.getTestCaseNames(self.obj.__class__)
|
|
||||||
l = []
|
|
||||||
for name in names:
|
|
||||||
callobj = getattr(self.obj, name)
|
|
||||||
if callable(callobj):
|
|
||||||
l.append(self.UnitTestFunction(name, parent=self))
|
|
||||||
return l
|
|
||||||
|
|
||||||
def _getobj(self):
|
|
||||||
x = self.parent.obj
|
|
||||||
return self.parent.obj(methodName='run')
|
|
||||||
|
|
||||||
class UnitTestFunction(py.test.collect.Function):
|
|
||||||
def __init__(self, name, parent, args=(), obj=_dummy, sort_value=None):
|
|
||||||
super(UnitTestFunction, self).__init__(name, parent)
|
|
||||||
self._args = args
|
|
||||||
if obj is not _dummy:
|
|
||||||
self._obj = obj
|
|
||||||
self._sort_value = sort_value
|
|
||||||
|
|
||||||
def runtest(self):
|
|
||||||
target = self.obj
|
|
||||||
args = self._args
|
|
||||||
target(*args)
|
|
||||||
|
|
||||||
def setup(self):
|
|
||||||
instance = self.obj.im_self
|
|
||||||
instance.setUp()
|
|
||||||
|
|
||||||
def teardown(self):
|
|
||||||
instance = self.obj.im_self
|
|
||||||
instance.tearDown()
|
|
||||||
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
code for collecting traditional unit tests.
|
|
||||||
This conftest is based on
|
|
||||||
|
|
||||||
http://johnnydebris.net/svn/projects/py_unittest
|
|
||||||
|
|
||||||
from Guido Wesdorp.
|
|
||||||
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
import py
|
|
||||||
from py.__.test.outcome import Failed
|
|
||||||
from py.__.test.testing import suptest
|
|
||||||
conftestpath = py.magic.autopath().dirpath("conftest.py")
|
|
||||||
|
|
||||||
def test_version():
|
|
||||||
mod = conftestpath.pyimport()
|
|
||||||
assert hasattr(mod, "__version__")
|
|
||||||
|
|
||||||
class TestTestCaseInstance(suptest.InlineSession):
|
|
||||||
def setup_method(self, method):
|
|
||||||
super(TestTestCaseInstance, self).setup_method(method)
|
|
||||||
self.tmpdir.ensure("__init__.py")
|
|
||||||
conftestpath.copy(self.tmpdir.join(conftestpath.basename))
|
|
||||||
|
|
||||||
def test_simple_unittest(self):
|
|
||||||
test_one = self.makepyfile(test_one="""
|
|
||||||
import unittest
|
|
||||||
class MyTestCase(unittest.TestCase):
|
|
||||||
def testpassing(self):
|
|
||||||
self.assertEquals('foo', 'foo')
|
|
||||||
""")
|
|
||||||
sorter = self.parse_and_run(test_one)
|
|
||||||
rep = sorter.getreport("testpassing")
|
|
||||||
assert rep.passed
|
|
||||||
|
|
||||||
def test_simple_failing(self):
|
|
||||||
test_one = self.makepyfile(test_one="""
|
|
||||||
import unittest
|
|
||||||
class MyTestCase(unittest.TestCase):
|
|
||||||
def test_failing(self):
|
|
||||||
self.assertEquals('foo', 'bar')
|
|
||||||
""")
|
|
||||||
sorter = self.parse_and_run(test_one)
|
|
||||||
rep = sorter.getreport("test_failing")
|
|
||||||
assert rep.failed
|
|
||||||
|
|
||||||
def test_setup(self):
|
|
||||||
test_one = self.makepyfile(test_one="""
|
|
||||||
import unittest
|
|
||||||
class MyTestCase(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.foo = 1
|
|
||||||
def test_setUp(self):
|
|
||||||
self.assertEquals(1, self.foo)
|
|
||||||
""")
|
|
||||||
sorter = self.parse_and_run(test_one)
|
|
||||||
rep = sorter.getreport("test_setUp")
|
|
||||||
assert rep.passed
|
|
||||||
|
|
||||||
def test_teardown(self):
|
|
||||||
test_one = self.makepyfile(test_one="""
|
|
||||||
import unittest
|
|
||||||
class MyTestCase(unittest.TestCase):
|
|
||||||
l = []
|
|
||||||
def test_one(self):
|
|
||||||
pass
|
|
||||||
def tearDown(self):
|
|
||||||
self.l.append(None)
|
|
||||||
class Second(unittest.TestCase):
|
|
||||||
def test_check(self):
|
|
||||||
self.assertEquals(MyTestCase.l, [None])
|
|
||||||
""")
|
|
||||||
sorter = self.parse_and_run(test_one)
|
|
||||||
passed, skipped, failed = sorter.countoutcomes()
|
|
||||||
assert passed + skipped + failed == 2
|
|
||||||
assert failed == 0, failed
|
|
||||||
assert passed == 2
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
|
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
The py lib is a development support library featuring these tools and APIs:
|
The py lib is an extensible library for testing, distributed processing and
|
||||||
|
interacting with filesystems.
|
||||||
|
|
||||||
- `py.test`_: cross-project testing tool with many advanced features
|
- `py.test`_: cross-project testing tool with many advanced features
|
||||||
- `py.execnet`_: ad-hoc code distribution to SSH, Socket and local sub processes
|
- `py.execnet`_: ad-hoc code distribution to SSH, Socket and local sub processes
|
||||||
|
|
@ -26,8 +27,8 @@ version = "1.0.0a1"
|
||||||
|
|
||||||
initpkg(__name__,
|
initpkg(__name__,
|
||||||
description = "pylib and py.test: agile development and test support library",
|
description = "pylib and py.test: agile development and test support library",
|
||||||
revision = int('$LastChangedRevision: 58385 $'.split(':')[1][:-1]),
|
revision = int('$LastChangedRevision: 62211 $'.split(':')[1][:-1]),
|
||||||
lastchangedate = '$LastChangedDate: 2008-09-23 16:28:13 +0200 (Tue, 23 Sep 2008) $',
|
lastchangedate = '$LastChangedDate: 2009-02-27 11:18:27 +0100 (Fri, 27 Feb 2009) $',
|
||||||
version = version,
|
version = version,
|
||||||
url = "http://pylib.org",
|
url = "http://pylib.org",
|
||||||
download_url = "http://codespeak.net/py/0.9.2/download.html",
|
download_url = "http://codespeak.net/py/0.9.2/download.html",
|
||||||
|
|
@ -37,7 +38,7 @@ initpkg(__name__,
|
||||||
author_email = "holger at merlinux.eu, py-dev at codespeak.net",
|
author_email = "holger at merlinux.eu, py-dev at codespeak.net",
|
||||||
long_description = globals()['__doc__'],
|
long_description = globals()['__doc__'],
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 4 - Beta",
|
"Development Status :: 3 - Alpha",
|
||||||
"Intended Audience :: Developers",
|
"Intended Audience :: Developers",
|
||||||
"License :: OSI Approved :: MIT License",
|
"License :: OSI Approved :: MIT License",
|
||||||
"Operating System :: POSIX",
|
"Operating System :: POSIX",
|
||||||
|
|
@ -53,6 +54,12 @@ initpkg(__name__,
|
||||||
|
|
||||||
# EXPORTED API
|
# EXPORTED API
|
||||||
exportdefs = {
|
exportdefs = {
|
||||||
|
|
||||||
|
# py lib events and plugins
|
||||||
|
'_com.PyPlugins' : ('./_com.py', 'PyPlugins'),
|
||||||
|
'_com.MultiCall' : ('./_com.py', 'MultiCall'),
|
||||||
|
'_com.pyplugins' : ('./_com.py', 'pyplugins'),
|
||||||
|
|
||||||
# py lib cmdline tools
|
# py lib cmdline tools
|
||||||
'cmdline.pytest' : ('./cmdline/pytest.py', 'main',),
|
'cmdline.pytest' : ('./cmdline/pytest.py', 'main',),
|
||||||
'cmdline.pyrest' : ('./cmdline/pyrest.py', 'main',),
|
'cmdline.pyrest' : ('./cmdline/pyrest.py', 'main',),
|
||||||
|
|
@ -64,7 +71,9 @@ initpkg(__name__,
|
||||||
|
|
||||||
# helpers for use from test functions or collectors
|
# helpers for use from test functions or collectors
|
||||||
'test.__doc__' : ('./test/__init__.py', '__doc__'),
|
'test.__doc__' : ('./test/__init__.py', '__doc__'),
|
||||||
|
'test._PytestPlugins' : ('./test/pytestplugin.py', 'PytestPlugins'),
|
||||||
'test.raises' : ('./test/outcome.py', 'raises'),
|
'test.raises' : ('./test/outcome.py', 'raises'),
|
||||||
|
'test.keywords' : ('./test/outcome.py', 'keywords',),
|
||||||
'test.deprecated_call' : ('./test/outcome.py', 'deprecated_call'),
|
'test.deprecated_call' : ('./test/outcome.py', 'deprecated_call'),
|
||||||
'test.skip' : ('./test/outcome.py', 'skip'),
|
'test.skip' : ('./test/outcome.py', 'skip'),
|
||||||
'test.importorskip' : ('./test/outcome.py', 'importorskip'),
|
'test.importorskip' : ('./test/outcome.py', 'importorskip'),
|
||||||
|
|
@ -83,7 +92,6 @@ initpkg(__name__,
|
||||||
'test.collect.File' : ('./test/collect.py', 'File'),
|
'test.collect.File' : ('./test/collect.py', 'File'),
|
||||||
'test.collect.Item' : ('./test/collect.py', 'Item'),
|
'test.collect.Item' : ('./test/collect.py', 'Item'),
|
||||||
'test.collect.Module' : ('./test/pycollect.py', 'Module'),
|
'test.collect.Module' : ('./test/pycollect.py', 'Module'),
|
||||||
'test.collect.DoctestFile' : ('./test/pycollect.py', 'DoctestFile'),
|
|
||||||
'test.collect.Class' : ('./test/pycollect.py', 'Class'),
|
'test.collect.Class' : ('./test/pycollect.py', 'Class'),
|
||||||
'test.collect.Instance' : ('./test/pycollect.py', 'Instance'),
|
'test.collect.Instance' : ('./test/pycollect.py', 'Instance'),
|
||||||
'test.collect.Generator' : ('./test/pycollect.py', 'Generator'),
|
'test.collect.Generator' : ('./test/pycollect.py', 'Generator'),
|
||||||
|
|
@ -187,3 +195,7 @@ initpkg(__name__,
|
||||||
'compat.subprocess' : ('./compat/subprocess.py', '*'),
|
'compat.subprocess' : ('./compat/subprocess.py', '*'),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
import py
|
||||||
|
py._com.pyplugins.consider_env()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
"""
|
||||||
|
py lib plugins and events.
|
||||||
|
|
||||||
|
you can write plugins that extend the py lib API.
|
||||||
|
currently this is mostly used by py.test
|
||||||
|
|
||||||
|
registering a plugin
|
||||||
|
++++++++++++++++++++++++++++++++++
|
||||||
|
|
||||||
|
::
|
||||||
|
>>> class MyPlugin:
|
||||||
|
... def pyevent_plugin_registered(self, plugin):
|
||||||
|
... print "registering", plugin.__class__.__name__
|
||||||
|
...
|
||||||
|
>>> import py
|
||||||
|
>>> py._com.pyplugins.register(MyPlugin())
|
||||||
|
registering MyPlugin
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
import py
|
||||||
|
|
||||||
|
class MultiCall:
|
||||||
|
""" Manage a specific call into many python functions/methods.
|
||||||
|
|
||||||
|
Simple example:
|
||||||
|
MultiCall([list1.append, list2.append], 42).execute()
|
||||||
|
"""
|
||||||
|
NONEASRESULT = object()
|
||||||
|
|
||||||
|
def __init__(self, methods, *args, **kwargs):
|
||||||
|
self.methods = methods
|
||||||
|
self.args = args
|
||||||
|
self.kwargs = kwargs
|
||||||
|
self.results = []
|
||||||
|
|
||||||
|
def execute(self, firstresult=False):
|
||||||
|
while self.methods:
|
||||||
|
self.currentmethod = self.methods.pop()
|
||||||
|
# provide call introspection if "__call__" is the first positional argument
|
||||||
|
if hasattr(self.currentmethod, 'im_self'):
|
||||||
|
varnames = self.currentmethod.im_func.func_code.co_varnames
|
||||||
|
needscall = varnames[1:2] == ('__call__',)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
varnames = self.currentmethod.func_code.co_varnames
|
||||||
|
except AttributeError:
|
||||||
|
# builtin function
|
||||||
|
varnames = ()
|
||||||
|
needscall = varnames[:1] == ('__call__',)
|
||||||
|
if needscall:
|
||||||
|
res = self.currentmethod(self, *self.args, **self.kwargs)
|
||||||
|
else:
|
||||||
|
res = self.currentmethod(*self.args, **self.kwargs)
|
||||||
|
if res is not None:
|
||||||
|
if res is self.NONEASRESULT:
|
||||||
|
res = None
|
||||||
|
self.results.append(res)
|
||||||
|
if firstresult:
|
||||||
|
break
|
||||||
|
if not firstresult:
|
||||||
|
return self.results
|
||||||
|
if self.results:
|
||||||
|
return self.results[-1]
|
||||||
|
|
||||||
|
class PyPlugins:
|
||||||
|
"""
|
||||||
|
Manage Plugins: Load plugins and manage calls to plugins.
|
||||||
|
"""
|
||||||
|
MultiCall = MultiCall
|
||||||
|
|
||||||
|
def __init__(self, plugins=None):
|
||||||
|
if plugins is None:
|
||||||
|
plugins = []
|
||||||
|
self._plugins = plugins
|
||||||
|
self._callbacks = []
|
||||||
|
|
||||||
|
def import_module(self, modspec):
|
||||||
|
# XXX allow modspec to specify version / lookup
|
||||||
|
modpath = modspec
|
||||||
|
self.notify("importingmodule", modpath)
|
||||||
|
__import__(modpath)
|
||||||
|
|
||||||
|
def consider_env(self):
|
||||||
|
""" consider ENV variable for loading modules. """
|
||||||
|
for spec in self._envlist("PYLIB"):
|
||||||
|
self.import_module(spec)
|
||||||
|
|
||||||
|
def _envlist(self, varname):
|
||||||
|
val = py.std.os.environ.get(varname, None)
|
||||||
|
if val is not None:
|
||||||
|
return val.split(',')
|
||||||
|
return ()
|
||||||
|
|
||||||
|
def consider_module(self, mod, varname="pylib"):
|
||||||
|
speclist = getattr(mod, varname, ())
|
||||||
|
if not isinstance(speclist, (list, tuple)):
|
||||||
|
speclist = (speclist,)
|
||||||
|
for spec in speclist:
|
||||||
|
self.import_module(spec)
|
||||||
|
|
||||||
|
def register(self, plugin):
|
||||||
|
assert not isinstance(plugin, str)
|
||||||
|
self._plugins.append(plugin)
|
||||||
|
self.notify("plugin_registered", plugin)
|
||||||
|
|
||||||
|
def unregister(self, plugin):
|
||||||
|
self._plugins.remove(plugin)
|
||||||
|
self.notify("plugin_unregistered", plugin)
|
||||||
|
|
||||||
|
def getplugins(self):
|
||||||
|
return list(self._plugins)
|
||||||
|
|
||||||
|
def isregistered(self, plugin):
|
||||||
|
return plugin in self._plugins
|
||||||
|
|
||||||
|
def listattr(self, attrname, plugins=None, extra=()):
|
||||||
|
l = []
|
||||||
|
if plugins is None:
|
||||||
|
plugins = self._plugins
|
||||||
|
if extra:
|
||||||
|
plugins += list(extra)
|
||||||
|
for plugin in plugins:
|
||||||
|
try:
|
||||||
|
l.append(getattr(plugin, attrname))
|
||||||
|
except AttributeError:
|
||||||
|
continue
|
||||||
|
return l
|
||||||
|
|
||||||
|
def call_each(self, methname, *args, **kwargs):
|
||||||
|
""" return call object for executing a plugin call. """
|
||||||
|
return MultiCall(self.listattr(methname), *args, **kwargs).execute()
|
||||||
|
|
||||||
|
def call_firstresult(self, methname, *args, **kwargs):
|
||||||
|
""" return first non-None result of a plugin method. """
|
||||||
|
return MultiCall(self.listattr(methname), *args, **kwargs).execute(firstresult=True)
|
||||||
|
|
||||||
|
def call_plugin(self, plugin, methname, *args, **kwargs):
|
||||||
|
return MultiCall(self.listattr(methname, plugins=[plugin]),
|
||||||
|
*args, **kwargs).execute(firstresult=True)
|
||||||
|
|
||||||
|
def notify(self, eventname, *args, **kwargs):
|
||||||
|
#print "notifying", eventname, args, kwargs
|
||||||
|
MultiCall(self.listattr("pyevent_" + eventname),
|
||||||
|
*args, **kwargs).execute()
|
||||||
|
#print "calling anonymous hooks", args, kwargs
|
||||||
|
MultiCall(self.listattr("pyevent"),
|
||||||
|
eventname, *args, **kwargs).execute()
|
||||||
|
|
||||||
|
pyplugins = PyPlugins()
|
||||||
|
|
@ -8,7 +8,6 @@ from py.__.apigen import htmlgen
|
||||||
from py.__.apigen import linker
|
from py.__.apigen import linker
|
||||||
from py.__.apigen import project
|
from py.__.apigen import project
|
||||||
from py.__.apigen.tracer.docstorage import pkg_to_dict
|
from py.__.apigen.tracer.docstorage import pkg_to_dict
|
||||||
from py.__.doc.conftest import get_apigenpath
|
|
||||||
|
|
||||||
from layout import LayoutPage
|
from layout import LayoutPage
|
||||||
|
|
||||||
|
|
@ -26,9 +25,9 @@ def get_documentable_items_pkgdir(pkgdir):
|
||||||
|
|
||||||
def get_documentable_items(pkgdir):
|
def get_documentable_items(pkgdir):
|
||||||
pkgname, pkgdict = get_documentable_items_pkgdir(pkgdir)
|
pkgname, pkgdict = get_documentable_items_pkgdir(pkgdir)
|
||||||
from py.__.execnet.channel import Channel
|
#from py.__.execnet.channel import Channel
|
||||||
pkgdict['execnet.Channel'] = Channel
|
#pkgdict['execnet.Channel'] = Channel
|
||||||
Channel.__apigen_hide_from_nav__ = True
|
#Channel.__apigen_hide_from_nav__ = True
|
||||||
return pkgname, pkgdict
|
return pkgname, pkgdict
|
||||||
|
|
||||||
def sourcedirfilter(p):
|
def sourcedirfilter(p):
|
||||||
|
|
@ -36,7 +35,7 @@ def sourcedirfilter(p):
|
||||||
not p.basename.startswith('.') and
|
not p.basename.startswith('.') and
|
||||||
str(p).find('c-extension%sgreenlet%sbuild' % (p.sep, p.sep)) == -1)
|
str(p).find('c-extension%sgreenlet%sbuild' % (p.sep, p.sep)) == -1)
|
||||||
|
|
||||||
def build(pkgdir, dsa, capture):
|
def build(config, pkgdir, dsa, capture):
|
||||||
# create a linker (link database) for cross-linking
|
# create a linker (link database) for cross-linking
|
||||||
l = linker.TempLinker()
|
l = linker.TempLinker()
|
||||||
|
|
||||||
|
|
@ -44,8 +43,7 @@ def build(pkgdir, dsa, capture):
|
||||||
proj = project.Project()
|
proj = project.Project()
|
||||||
|
|
||||||
# output dir
|
# output dir
|
||||||
from py.__.conftest import option
|
targetdir = proj.apigenpath
|
||||||
targetdir = get_apigenpath()
|
|
||||||
targetdir.ensure(dir=True)
|
targetdir.ensure(dir=True)
|
||||||
|
|
||||||
# find out what to build
|
# find out what to build
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
import py
|
import py
|
||||||
|
|
||||||
Option = py.test.config.Option
|
class ConftestPlugin:
|
||||||
option = py.test.config.addoptions("apigen test options",
|
def pytest_addoption(self, parser):
|
||||||
Option('', '--webcheck',
|
parser.addoption('--webcheck',
|
||||||
action="store_true", dest="webcheck", default=False,
|
action="store_true", dest="webcheck", default=False,
|
||||||
help="run XHTML validation tests"
|
help="run XHTML validation tests"
|
||||||
),
|
)
|
||||||
)
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -430,8 +430,9 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
relpath = get_rel_sourcepath(self.projroot, sourcefile, sourcefile)
|
relpath = get_rel_sourcepath(self.projroot, sourcefile, sourcefile)
|
||||||
text = 'source: %s' % (relpath,)
|
text = 'source: %s' % (relpath,)
|
||||||
if is_in_pkg:
|
if is_in_pkg:
|
||||||
href = self.linker.get_lazyhref(sourcefile,
|
#href = self.linker.get_lazyhref(sourcefile,
|
||||||
self.get_anchor(func))
|
# self.get_anchor(func))
|
||||||
|
href = self.linker.get_lazyhref(sourcefile) #
|
||||||
csource = H.SourceSnippet(text, href, colored)
|
csource = H.SourceSnippet(text, href, colored)
|
||||||
cslinks = self.build_callsites(dotted_name)
|
cslinks = self.build_callsites(dotted_name)
|
||||||
snippet = H.FunctionDescription(localname, argdesc, docstring,
|
snippet = H.FunctionDescription(localname, argdesc, docstring,
|
||||||
|
|
@ -464,8 +465,8 @@ class ApiPageBuilder(AbstractPageBuilder):
|
||||||
if sourcefile[-1] in ['o', 'c']:
|
if sourcefile[-1] in ['o', 'c']:
|
||||||
sourcefile = sourcefile[:-1]
|
sourcefile = sourcefile[:-1]
|
||||||
sourcelink = H.div(H.a('view source',
|
sourcelink = H.div(H.a('view source',
|
||||||
href=self.linker.get_lazyhref(sourcefile,
|
href=self.linker.get_lazyhref(sourcefile) #, self.get_anchor(cls)
|
||||||
self.get_anchor(cls))))
|
))
|
||||||
|
|
||||||
snippet = H.ClassDescription(
|
snippet = H.ClassDescription(
|
||||||
# XXX bases HTML
|
# XXX bases HTML
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
import py
|
import py
|
||||||
from py.__.doc import confrest
|
from py.__.doc import confrest
|
||||||
from py.__.apigen import linker
|
from py.__.apigen import linker
|
||||||
from py.__.doc.conftest import get_apigenpath, get_docpath
|
|
||||||
|
|
||||||
here = py.magic.autopath().dirpath()
|
here = py.magic.autopath().dirpath()
|
||||||
|
|
||||||
|
|
@ -25,7 +24,7 @@ class LayoutPage(confrest.PyPage):
|
||||||
|
|
||||||
def get_relpath(self):
|
def get_relpath(self):
|
||||||
return linker.relpath(self.targetpath.strpath,
|
return linker.relpath(self.targetpath.strpath,
|
||||||
get_apigenpath().strpath) + '/'
|
self.project.apigenpath.strpath) + '/'
|
||||||
|
|
||||||
def set_content(self, contentel):
|
def set_content(self, contentel):
|
||||||
self.contentspace.append(contentel)
|
self.contentspace.append(contentel)
|
||||||
|
|
|
||||||
|
|
@ -8,14 +8,18 @@
|
||||||
import py
|
import py
|
||||||
from layout import LayoutPage
|
from layout import LayoutPage
|
||||||
|
|
||||||
class Project(py.__.doc.confrest.Project):
|
# XXX don't import from an internal py lib class
|
||||||
|
from py.__.doc import confrest
|
||||||
|
|
||||||
|
class Project(confrest.Project):
|
||||||
""" a full project
|
""" a full project
|
||||||
|
|
||||||
this takes care of storing information on the first pass, and building
|
this takes care of storing information on the first pass, and building
|
||||||
pages + indexes on the second
|
pages + indexes on the second
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, *args, **kwargs):
|
||||||
|
confrest.Project.__init__(self, *args, **kwargs)
|
||||||
self.content_items = {}
|
self.content_items = {}
|
||||||
|
|
||||||
def add_item(self, path, content):
|
def add_item(self, path, content):
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ from py.__.apigen.tracer.permastore import PermaDocStorage
|
||||||
import pickle
|
import pickle
|
||||||
|
|
||||||
from py.__.apigen.tracer.testing.runtest import cut_pyc
|
from py.__.apigen.tracer.testing.runtest import cut_pyc
|
||||||
from py.__.doc.conftest import genlinkchecks
|
|
||||||
from py.__.rest.rst import Rest, Paragraph
|
from py.__.rest.rst import Rest, Paragraph
|
||||||
from py.__.rest.transform import HTMLHandler
|
from py.__.rest.transform import HTMLHandler
|
||||||
# XXX: UUuuuuuuuuuuuuuuuuuuuuuuu, dangerous import
|
# XXX: UUuuuuuuuuuuuuuuuuuuuuuuu, dangerous import
|
||||||
|
|
@ -186,9 +185,11 @@ class TestRest(object):
|
||||||
py.test.skip('skipping rest generation because docutils is '
|
py.test.skip('skipping rest generation because docutils is '
|
||||||
'not installed (this is a partial skip, the rest '
|
'not installed (this is a partial skip, the rest '
|
||||||
'of the test was successful)')
|
'of the test was successful)')
|
||||||
for path in tempdir.listdir('*.txt'):
|
py.test.skip("partial skip: find a nice way to re-use pytest_restdoc's genlinkchecks")
|
||||||
for item, arg1, arg2, arg3 in genlinkchecks(path):
|
# XXX find a nice way check pytest_restdoc's genlinkchecks()
|
||||||
item(arg1, arg2, arg3)
|
#for path in tempdir.listdir('*.txt'):
|
||||||
|
# for item, arg1, arg2, arg3 in genlinkchecks(path):
|
||||||
|
# item(arg1, arg2, arg3)
|
||||||
|
|
||||||
def test_generation_simple_api(self):
|
def test_generation_simple_api(self):
|
||||||
ds = self.get_filled_docstorage()
|
ds = self.get_filled_docstorage()
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,8 @@ from py.__.apigen.tracer.tracer import Tracer
|
||||||
from py.__.apigen.layout import LayoutPage
|
from py.__.apigen.layout import LayoutPage
|
||||||
from py.__.apigen.project import Project
|
from py.__.apigen.project import Project
|
||||||
from py.__.test.web import webcheck
|
from py.__.test.web import webcheck
|
||||||
from py.__.apigen.conftest import option
|
|
||||||
from py.__.path.svn.testing.svntestbase import make_test_repo
|
from py.__.path.svn.testing.svntestbase import make_test_repo
|
||||||
|
|
||||||
py.test.skip("apigen needs work on py.test to work again")
|
|
||||||
|
|
||||||
def run_string_sequence_test(data, seq):
|
def run_string_sequence_test(data, seq):
|
||||||
currpos = -1
|
currpos = -1
|
||||||
for s in seq:
|
for s in seq:
|
||||||
|
|
@ -84,7 +81,7 @@ def _checkhtml(htmlstring):
|
||||||
if isinstance(htmlstring, unicode):
|
if isinstance(htmlstring, unicode):
|
||||||
htmlstring = htmlstring.encode('UTF-8', 'replace')
|
htmlstring = htmlstring.encode('UTF-8', 'replace')
|
||||||
assert isinstance(htmlstring, str)
|
assert isinstance(htmlstring, str)
|
||||||
if option.webcheck:
|
if py.test.config.option.webcheck:
|
||||||
webcheck.check_html(htmlstring)
|
webcheck.check_html(htmlstring)
|
||||||
else:
|
else:
|
||||||
py.test.skip("pass --webcheck to validate html produced in tests "
|
py.test.skip("pass --webcheck to validate html produced in tests "
|
||||||
|
|
@ -238,7 +235,8 @@ class TestApiPageBuilder(AbstractBuilderTest):
|
||||||
self.linker.replace_dirpath(self.base, False)
|
self.linker.replace_dirpath(self.base, False)
|
||||||
funchtml = self.base.join('api/main.SomeClass.html').read()
|
funchtml = self.base.join('api/main.SomeClass.html').read()
|
||||||
print funchtml
|
print funchtml
|
||||||
assert funchtml.find('href="../source/pkg/someclass.py.html#SomeClass"') > -1
|
#assert funchtml.find('href="../source/pkg/someclass.py.html#SomeClass"') > -1
|
||||||
|
assert funchtml.find('href="../source/pkg/someclass.py.html"') > -1
|
||||||
_checkhtml(funchtml)
|
_checkhtml(funchtml)
|
||||||
|
|
||||||
def test_build_namespace_pages(self):
|
def test_build_namespace_pages(self):
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ if __name__ == '__main__':
|
||||||
if apigendir.check():
|
if apigendir.check():
|
||||||
print apigendir, "exists, not re-generating - remove to trigger regeneration"
|
print apigendir, "exists, not re-generating - remove to trigger regeneration"
|
||||||
else:
|
else:
|
||||||
sysexec('%(env)s %(pytest)s --apigen=%(pypath)s/apigen/apigen.py py' % locals())
|
sysexec('%(env)s %(pytest)s py' % locals())
|
||||||
print
|
print
|
||||||
print "*" * 30, "static generation", "*" * 30
|
print "*" * 30, "static generation", "*" * 30
|
||||||
sysexec('%(env)s %(pytest)s --forcegen %(pypath)s/doc' % locals())
|
sysexec('%(env)s %(pytest)s --forcegen %(pypath)s/doc' % locals())
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
from py.__.test.testing import suptest
|
|
||||||
from py.__.test.testing.acceptance_test import AcceptBase
|
|
||||||
|
|
||||||
class TestPyLookup(AcceptBase):
|
pytest_plugins = "pytest_pytester"
|
||||||
def test_basic(self):
|
|
||||||
p = self.makepyfile(hello="def x(): pass")
|
class TestPyLookup:
|
||||||
result = self.runpybin("py.lookup", "pass")
|
def test_basic(self, testdir):
|
||||||
suptest.assert_lines_contain_lines(result.outlines,
|
p = testdir.makepyfile(hello="def x(): pass")
|
||||||
|
result = testdir.runpybin("py.lookup", "pass")
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
['%s:*def x(): pass' %(p.basename)]
|
['%s:*def x(): pass' %(p.basename)]
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_search_in_filename(self):
|
def test_search_in_filename(self, testdir):
|
||||||
p = self.makepyfile(hello="def x(): pass")
|
p = testdir.makepyfile(hello="def x(): pass")
|
||||||
result = self.runpybin("py.lookup", "hello")
|
result = testdir.runpybin("py.lookup", "hello")
|
||||||
suptest.assert_lines_contain_lines(result.outlines,
|
result.stdout.fnmatch_lines(
|
||||||
['*%s:*' %(p.basename)]
|
['*%s:*' %(p.basename)]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,18 @@
|
||||||
#pythonexecutables = ('python2.2', 'python2.3',)
|
dist_rsync_roots = ['.'] # XXX
|
||||||
#pythonexecutable = 'python2.2'
|
|
||||||
|
|
||||||
# in the future we want to be able to say here:
|
pytest_plugins = 'pytest_doctest', 'pytest_pytester', 'pytest_restdoc'
|
||||||
#def setup_module(extpy):
|
|
||||||
# mod = extpy.resolve()
|
|
||||||
# mod.module = 23
|
|
||||||
# directory = pypath.root.dirpath()
|
|
||||||
|
|
||||||
# default values for options (modified from cmdline)
|
|
||||||
verbose = 0
|
|
||||||
nocapture = False
|
|
||||||
collectonly = False
|
|
||||||
exitfirst = False
|
|
||||||
fulltrace = False
|
|
||||||
showlocals = False
|
|
||||||
nomagic = False
|
|
||||||
|
|
||||||
import py
|
import py
|
||||||
Option = py.test.config.Option
|
class PylibTestPlugin:
|
||||||
|
def pytest_addoption(self, parser):
|
||||||
option = py.test.config.addoptions("execnet options",
|
group = parser.addgroup("pylib", "py lib testing options")
|
||||||
Option('-S', '',
|
group.addoption('--sshhost',
|
||||||
action="store", dest="sshtarget", default=None,
|
action="store", dest="sshhost", default=None,
|
||||||
help=("target to run tests requiring ssh, e.g. "
|
help=("target to run tests requiring ssh, e.g. "
|
||||||
"user@codespeak.net")),
|
"user@codespeak.net"))
|
||||||
Option('', '--apigenpath',
|
group.addoption('--runslowtests',
|
||||||
action="store", dest="apigenpath",
|
|
||||||
default="../apigen",
|
|
||||||
type="string",
|
|
||||||
help="relative path to apigen doc output location (relative from py/)"),
|
|
||||||
Option('', '--docpath',
|
|
||||||
action='store', dest='docpath',
|
|
||||||
default="doc", type='string',
|
|
||||||
help="relative path to doc output location (relative from py/)"),
|
|
||||||
Option('', '--runslowtests',
|
|
||||||
action="store_true", dest="runslowtests", default=False,
|
action="store_true", dest="runslowtests", default=False,
|
||||||
help="run slow tests"),
|
help="run slow tests")
|
||||||
)
|
|
||||||
|
ConftestPlugin = PylibTestPlugin
|
||||||
|
|
||||||
dist_rsync_roots = ['.']
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import py
|
import py
|
||||||
from py.__.misc.rest import convert_rest_html, strip_html_header
|
from py.__.misc.rest import convert_rest_html, strip_html_header
|
||||||
from py.__.misc.difftime import worded_time
|
from py.__.misc.difftime import worded_time
|
||||||
from py.__.doc.conftest import get_apigenpath, get_docpath
|
|
||||||
from py.__.apigen.linker import relpath
|
from py.__.apigen.linker import relpath
|
||||||
|
|
||||||
html = py.xml.html
|
html = py.xml.html
|
||||||
|
|
@ -25,13 +24,13 @@ class Page(object):
|
||||||
self.fill()
|
self.fill()
|
||||||
|
|
||||||
def a_docref(self, name, relhtmlpath):
|
def a_docref(self, name, relhtmlpath):
|
||||||
docpath = self.project.get_docpath()
|
docpath = self.project.docpath
|
||||||
return html.a(name, class_="menu",
|
return html.a(name, class_="menu",
|
||||||
href=relpath(self.targetpath.strpath,
|
href=relpath(self.targetpath.strpath,
|
||||||
docpath.join(relhtmlpath).strpath))
|
docpath.join(relhtmlpath).strpath))
|
||||||
|
|
||||||
def a_apigenref(self, name, relhtmlpath):
|
def a_apigenref(self, name, relhtmlpath):
|
||||||
apipath = get_apigenpath()
|
apipath = self.project.apigenpath
|
||||||
return html.a(name, class_="menu",
|
return html.a(name, class_="menu",
|
||||||
href=relpath(self.targetpath.strpath,
|
href=relpath(self.targetpath.strpath,
|
||||||
apipath.join(relhtmlpath).strpath))
|
apipath.join(relhtmlpath).strpath))
|
||||||
|
|
@ -107,8 +106,6 @@ def getrealname(username):
|
||||||
|
|
||||||
class Project:
|
class Project:
|
||||||
mydir = py.magic.autopath().dirpath()
|
mydir = py.magic.autopath().dirpath()
|
||||||
# string for url, path for local file
|
|
||||||
stylesheet = mydir.join('style.css')
|
|
||||||
title = "py lib"
|
title = "py lib"
|
||||||
prefix_title = "" # we have a logo already containing "py lib"
|
prefix_title = "" # we have a logo already containing "py lib"
|
||||||
encoding = 'latin1'
|
encoding = 'latin1'
|
||||||
|
|
@ -119,31 +116,53 @@ class Project:
|
||||||
href="http://codespeak.net"))
|
href="http://codespeak.net"))
|
||||||
Page = PyPage
|
Page = PyPage
|
||||||
|
|
||||||
|
def __init__(self, sourcepath=None):
|
||||||
|
if sourcepath is None:
|
||||||
|
sourcepath = self.mydir
|
||||||
|
self.setpath(sourcepath)
|
||||||
|
|
||||||
|
def setpath(self, sourcepath, docpath=None,
|
||||||
|
apigenpath=None, stylesheet=None):
|
||||||
|
self.sourcepath = sourcepath
|
||||||
|
if docpath is None:
|
||||||
|
docpath = sourcepath
|
||||||
|
self.docpath = docpath
|
||||||
|
if apigenpath is None:
|
||||||
|
apigenpath = docpath
|
||||||
|
self.apigenpath = apigenpath
|
||||||
|
if stylesheet is None:
|
||||||
|
p = sourcepath.join("style.css")
|
||||||
|
if p.check():
|
||||||
|
self.stylesheet = p
|
||||||
|
else:
|
||||||
|
self.stylesheet = None
|
||||||
|
else:
|
||||||
|
p = py.path.local(stylesheet)
|
||||||
|
if p.check():
|
||||||
|
stylesheet = p
|
||||||
|
self.stylesheet = stylesheet
|
||||||
|
self.apigen_relpath = relpath(
|
||||||
|
self.docpath.strpath + '/', self.apigenpath.strpath + '/')
|
||||||
|
|
||||||
def get_content(self, txtpath, encoding):
|
def get_content(self, txtpath, encoding):
|
||||||
return unicode(txtpath.read(), encoding)
|
return unicode(txtpath.read(), encoding)
|
||||||
|
|
||||||
def get_docpath(self):
|
|
||||||
return get_docpath()
|
|
||||||
|
|
||||||
def get_htmloutputpath(self, txtpath):
|
def get_htmloutputpath(self, txtpath):
|
||||||
docpath = self.get_docpath()
|
reloutputpath = txtpath.new(ext='.html').relto(self.sourcepath)
|
||||||
reloutputpath = txtpath.new(ext='.html').relto(self.mydir)
|
return self.docpath.join(reloutputpath)
|
||||||
return docpath.join(reloutputpath)
|
|
||||||
|
|
||||||
def process(self, txtpath):
|
def process(self, txtpath):
|
||||||
encoding = self.encoding
|
encoding = self.encoding
|
||||||
content = self.get_content(txtpath, encoding)
|
content = self.get_content(txtpath, encoding)
|
||||||
docpath = self.get_docpath()
|
|
||||||
outputpath = self.get_htmloutputpath(txtpath)
|
outputpath = self.get_htmloutputpath(txtpath)
|
||||||
|
|
||||||
stylesheet = self.stylesheet
|
stylesheet = self.stylesheet
|
||||||
if isinstance(self.stylesheet, py.path.local):
|
if isinstance(stylesheet, py.path.local):
|
||||||
if not docpath.join(stylesheet.basename).check():
|
if not self.docpath.join(stylesheet.basename).check():
|
||||||
docpath.ensure(dir=True)
|
docpath.ensure(dir=True)
|
||||||
stylesheet.copy(docpath)
|
stylesheet.copy(docpath)
|
||||||
stylesheet = relpath(outputpath.strpath,
|
stylesheet = relpath(outputpath.strpath,
|
||||||
docpath.join(stylesheet.basename).strpath)
|
self.docpath.join(stylesheet.basename).strpath)
|
||||||
|
|
||||||
content = convert_rest_html(content, txtpath,
|
content = convert_rest_html(content, txtpath,
|
||||||
stylesheet=stylesheet, encoding=encoding)
|
stylesheet=stylesheet, encoding=encoding)
|
||||||
|
|
|
||||||
|
|
@ -1,322 +0,0 @@
|
||||||
from __future__ import generators
|
|
||||||
import py
|
|
||||||
from py.__.misc import rest
|
|
||||||
from py.__.apigen.linker import relpath
|
|
||||||
import os
|
|
||||||
|
|
||||||
pypkgdir = py.path.local(py.__file__).dirpath()
|
|
||||||
|
|
||||||
mypath = py.magic.autopath().dirpath()
|
|
||||||
|
|
||||||
TIMEOUT_URLOPEN = 5.0
|
|
||||||
|
|
||||||
Option = py.test.config.Option
|
|
||||||
option = py.test.config.addoptions("documentation check options",
|
|
||||||
Option('-R', '--checkremote',
|
|
||||||
action="store_true", dest="checkremote", default=False,
|
|
||||||
help="urlopen() remote links found in ReST text files.",
|
|
||||||
),
|
|
||||||
Option('', '--forcegen',
|
|
||||||
action="store_true", dest="forcegen", default=False,
|
|
||||||
help="force generation of html files even if they appear up-to-date"
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_apigenpath():
|
|
||||||
from py.__.conftest import option
|
|
||||||
path = os.environ.get('APIGENPATH')
|
|
||||||
if path is None:
|
|
||||||
path = option.apigenpath
|
|
||||||
return pypkgdir.join(path, abs=True)
|
|
||||||
|
|
||||||
def get_docpath():
|
|
||||||
from py.__.conftest import option
|
|
||||||
path = os.environ.get('DOCPATH')
|
|
||||||
if path is None:
|
|
||||||
path = option.docpath
|
|
||||||
return pypkgdir.join(path, abs=True)
|
|
||||||
|
|
||||||
def get_apigen_relpath():
|
|
||||||
return relpath(get_docpath().strpath + '/',
|
|
||||||
get_apigenpath().strpath + '/')
|
|
||||||
|
|
||||||
def deindent(s, sep='\n'):
|
|
||||||
leastspaces = -1
|
|
||||||
lines = s.split(sep)
|
|
||||||
for line in lines:
|
|
||||||
if not line.strip():
|
|
||||||
continue
|
|
||||||
spaces = len(line) - len(line.lstrip())
|
|
||||||
if leastspaces == -1 or spaces < leastspaces:
|
|
||||||
leastspaces = spaces
|
|
||||||
if leastspaces == -1:
|
|
||||||
return s
|
|
||||||
for i, line in py.builtin.enumerate(lines):
|
|
||||||
if not line.strip():
|
|
||||||
lines[i] = ''
|
|
||||||
else:
|
|
||||||
lines[i] = line[leastspaces:]
|
|
||||||
return sep.join(lines)
|
|
||||||
|
|
||||||
_initialized = False
|
|
||||||
def checkdocutils():
|
|
||||||
global _initialized
|
|
||||||
py.test.importorskip("docutils")
|
|
||||||
if not _initialized:
|
|
||||||
from py.__.rest import directive
|
|
||||||
directive.register_linkrole('api', resolve_linkrole)
|
|
||||||
directive.register_linkrole('source', resolve_linkrole)
|
|
||||||
_initialized = True
|
|
||||||
|
|
||||||
def restcheck(path):
|
|
||||||
localpath = path
|
|
||||||
if hasattr(path, 'localpath'):
|
|
||||||
localpath = path.localpath
|
|
||||||
checkdocutils()
|
|
||||||
import docutils.utils
|
|
||||||
|
|
||||||
try:
|
|
||||||
cur = localpath
|
|
||||||
for x in cur.parts(reverse=True):
|
|
||||||
confrest = x.dirpath('confrest.py')
|
|
||||||
if confrest.check(file=1):
|
|
||||||
confrest = confrest.pyimport()
|
|
||||||
project = confrest.Project()
|
|
||||||
_checkskip(path, project.get_htmloutputpath(path))
|
|
||||||
project.process(path)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# defer to default processor
|
|
||||||
_checkskip(path)
|
|
||||||
rest.process(path)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
raise
|
|
||||||
except docutils.utils.SystemMessage:
|
|
||||||
# we assume docutils printed info on stdout
|
|
||||||
py.test.fail("docutils processing failed, see captured stderr")
|
|
||||||
|
|
||||||
def _checkskip(lpath, htmlpath=None):
|
|
||||||
if not option.forcegen:
|
|
||||||
lpath = py.path.local(lpath)
|
|
||||||
if htmlpath is not None:
|
|
||||||
htmlpath = py.path.local(htmlpath)
|
|
||||||
if lpath.ext == '.txt':
|
|
||||||
htmlpath = htmlpath or lpath.new(ext='.html')
|
|
||||||
if htmlpath.check(file=1) and htmlpath.mtime() >= lpath.mtime():
|
|
||||||
py.test.skip("html file is up to date, use --forcegen to regenerate")
|
|
||||||
#return [] # no need to rebuild
|
|
||||||
|
|
||||||
class ReSTSyntaxTest(py.test.collect.Item):
|
|
||||||
def runtest(self):
|
|
||||||
mypath = self.fspath
|
|
||||||
restcheck(py.path.svnwc(mypath))
|
|
||||||
|
|
||||||
class DoctestText(py.test.collect.Item):
|
|
||||||
def runtest(self):
|
|
||||||
s = self._normalize_linesep()
|
|
||||||
l = []
|
|
||||||
prefix = '.. >>> '
|
|
||||||
mod = py.std.types.ModuleType(self.fspath.purebasename)
|
|
||||||
skipchunk = False
|
|
||||||
for line in deindent(s).split('\n'):
|
|
||||||
stripped = line.strip()
|
|
||||||
if skipchunk and line.startswith(skipchunk):
|
|
||||||
print "skipping", line
|
|
||||||
continue
|
|
||||||
skipchunk = False
|
|
||||||
if stripped.startswith(prefix):
|
|
||||||
try:
|
|
||||||
exec py.code.Source(stripped[len(prefix):]).compile() in \
|
|
||||||
mod.__dict__
|
|
||||||
except ValueError, e:
|
|
||||||
if e.args and e.args[0] == "skipchunk":
|
|
||||||
skipchunk = " " * (len(line) - len(line.lstrip()))
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
l.append(line)
|
|
||||||
docstring = "\n".join(l)
|
|
||||||
mod.__doc__ = docstring
|
|
||||||
failed, tot = py.compat.doctest.testmod(mod, verbose=1)
|
|
||||||
if failed:
|
|
||||||
py.test.fail("doctest %s: %s failed out of %s" %(
|
|
||||||
self.fspath, failed, tot))
|
|
||||||
|
|
||||||
def _normalize_linesep(self):
|
|
||||||
# XXX quite nasty... but it works (fixes win32 issues)
|
|
||||||
s = self.fspath.read()
|
|
||||||
linesep = '\n'
|
|
||||||
if '\r' in s:
|
|
||||||
if '\n' not in s:
|
|
||||||
linesep = '\r'
|
|
||||||
else:
|
|
||||||
linesep = '\r\n'
|
|
||||||
s = s.replace(linesep, '\n')
|
|
||||||
return s
|
|
||||||
|
|
||||||
class LinkCheckerMaker(py.test.collect.Collector):
|
|
||||||
def collect(self):
|
|
||||||
l = []
|
|
||||||
for call, tryfn, path, lineno in genlinkchecks(self.fspath):
|
|
||||||
name = "%s:%d" %(tryfn, lineno)
|
|
||||||
l.append(
|
|
||||||
CheckLink(name, parent=self, args=(tryfn, path, lineno), callobj=call)
|
|
||||||
)
|
|
||||||
return l
|
|
||||||
|
|
||||||
class CheckLink(py.test.collect.Function):
|
|
||||||
def repr_metainfo(self):
|
|
||||||
return self.ReprMetaInfo(fspath=self.fspath, lineno=self._args[2],
|
|
||||||
modpath="checklink: %s" % (self._args[0],))
|
|
||||||
def setup(self):
|
|
||||||
pass
|
|
||||||
def teardown(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class DocfileTests(py.test.collect.File):
|
|
||||||
DoctestText = DoctestText
|
|
||||||
ReSTSyntaxTest = ReSTSyntaxTest
|
|
||||||
LinkCheckerMaker = LinkCheckerMaker
|
|
||||||
|
|
||||||
def collect(self):
|
|
||||||
return [
|
|
||||||
self.ReSTSyntaxTest(self.fspath.basename, parent=self),
|
|
||||||
self.LinkCheckerMaker("checklinks", self),
|
|
||||||
self.DoctestText("doctest", self),
|
|
||||||
]
|
|
||||||
|
|
||||||
# generating functions + args as single tests
|
|
||||||
def genlinkchecks(path):
|
|
||||||
for lineno, line in py.builtin.enumerate(path.readlines()):
|
|
||||||
line = line.strip()
|
|
||||||
if line.startswith('.. _'):
|
|
||||||
if line.startswith('.. _`'):
|
|
||||||
delim = '`:'
|
|
||||||
else:
|
|
||||||
delim = ':'
|
|
||||||
l = line.split(delim, 1)
|
|
||||||
if len(l) != 2:
|
|
||||||
continue
|
|
||||||
tryfn = l[1].strip()
|
|
||||||
if tryfn.startswith('http:') or tryfn.startswith('https'):
|
|
||||||
if option.checkremote:
|
|
||||||
yield urlcheck, tryfn, path, lineno
|
|
||||||
elif tryfn.startswith('webcal:'):
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
i = tryfn.find('#')
|
|
||||||
if i != -1:
|
|
||||||
checkfn = tryfn[:i]
|
|
||||||
else:
|
|
||||||
checkfn = tryfn
|
|
||||||
if checkfn.strip() and (1 or checkfn.endswith('.html')):
|
|
||||||
yield localrefcheck, tryfn, path, lineno
|
|
||||||
|
|
||||||
def urlcheck(tryfn, path, lineno):
|
|
||||||
old = py.std.socket.getdefaulttimeout()
|
|
||||||
py.std.socket.setdefaulttimeout(TIMEOUT_URLOPEN)
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
print "trying remote", tryfn
|
|
||||||
py.std.urllib2.urlopen(tryfn)
|
|
||||||
finally:
|
|
||||||
py.std.socket.setdefaulttimeout(old)
|
|
||||||
except (py.std.urllib2.URLError, py.std.urllib2.HTTPError), e:
|
|
||||||
if e.code in (401, 403): # authorization required, forbidden
|
|
||||||
py.test.skip("%s: %s" %(tryfn, str(e)))
|
|
||||||
else:
|
|
||||||
py.test.fail("remote reference error %r in %s:%d\n%s" %(
|
|
||||||
tryfn, path.basename, lineno+1, e))
|
|
||||||
|
|
||||||
def localrefcheck(tryfn, path, lineno):
|
|
||||||
# assume it should be a file
|
|
||||||
i = tryfn.find('#')
|
|
||||||
if tryfn.startswith('javascript:'):
|
|
||||||
return # don't check JS refs
|
|
||||||
if i != -1:
|
|
||||||
anchor = tryfn[i+1:]
|
|
||||||
tryfn = tryfn[:i]
|
|
||||||
else:
|
|
||||||
anchor = ''
|
|
||||||
fn = path.dirpath(tryfn)
|
|
||||||
ishtml = fn.ext == '.html'
|
|
||||||
fn = ishtml and fn.new(ext='.txt') or fn
|
|
||||||
print "filename is", fn
|
|
||||||
if not fn.check(): # not ishtml or not fn.check():
|
|
||||||
if not py.path.local(tryfn).check(): # the html could be there
|
|
||||||
py.test.fail("reference error %r in %s:%d" %(
|
|
||||||
tryfn, path.basename, lineno+1))
|
|
||||||
if anchor:
|
|
||||||
source = unicode(fn.read(), 'latin1')
|
|
||||||
source = source.lower().replace('-', ' ') # aehem
|
|
||||||
|
|
||||||
anchor = anchor.replace('-', ' ')
|
|
||||||
match2 = ".. _`%s`:" % anchor
|
|
||||||
match3 = ".. _%s:" % anchor
|
|
||||||
candidates = (anchor, match2, match3)
|
|
||||||
print "candidates", repr(candidates)
|
|
||||||
for line in source.split('\n'):
|
|
||||||
line = line.strip()
|
|
||||||
if line in candidates:
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
py.test.fail("anchor reference error %s#%s in %s:%d" %(
|
|
||||||
tryfn, anchor, path.basename, lineno+1))
|
|
||||||
|
|
||||||
|
|
||||||
# ___________________________________________________________
|
|
||||||
#
|
|
||||||
# hooking into py.test Directory collector's chain ...
|
|
||||||
|
|
||||||
class DocDirectory(py.test.collect.Directory):
|
|
||||||
DocfileTests = DocfileTests
|
|
||||||
def collect(self):
|
|
||||||
results = super(DocDirectory, self).collect()
|
|
||||||
for x in self.fspath.listdir('*.txt', sort=True):
|
|
||||||
results.append(self.DocfileTests(x, parent=self))
|
|
||||||
return results
|
|
||||||
|
|
||||||
Directory = DocDirectory
|
|
||||||
|
|
||||||
def resolve_linkrole(name, text, check=True):
|
|
||||||
apigen_relpath = get_apigen_relpath()
|
|
||||||
if name == 'api':
|
|
||||||
if text == 'py':
|
|
||||||
return ('py', apigen_relpath + 'api/index.html')
|
|
||||||
else:
|
|
||||||
assert text.startswith('py.'), (
|
|
||||||
'api link "%s" does not point to the py package') % (text,)
|
|
||||||
dotted_name = text
|
|
||||||
if dotted_name.find('(') > -1:
|
|
||||||
dotted_name = dotted_name[:text.find('(')]
|
|
||||||
# remove pkg root
|
|
||||||
path = dotted_name.split('.')[1:]
|
|
||||||
dotted_name = '.'.join(path)
|
|
||||||
obj = py
|
|
||||||
if check:
|
|
||||||
for chunk in path:
|
|
||||||
try:
|
|
||||||
obj = getattr(obj, chunk)
|
|
||||||
except AttributeError:
|
|
||||||
raise AssertionError(
|
|
||||||
'problem with linkrole :api:`%s`: can not resolve '
|
|
||||||
'dotted name %s' % (text, dotted_name,))
|
|
||||||
return (text, apigen_relpath + 'api/%s.html' % (dotted_name,))
|
|
||||||
elif name == 'source':
|
|
||||||
assert text.startswith('py/'), ('source link "%s" does not point '
|
|
||||||
'to the py package') % (text,)
|
|
||||||
relpath = '/'.join(text.split('/')[1:])
|
|
||||||
if check:
|
|
||||||
pkgroot = py.__pkg__.getpath()
|
|
||||||
abspath = pkgroot.join(relpath)
|
|
||||||
assert pkgroot.join(relpath).check(), (
|
|
||||||
'problem with linkrole :source:`%s`: '
|
|
||||||
'path %s does not exist' % (text, relpath))
|
|
||||||
if relpath.endswith('/') or not relpath:
|
|
||||||
relpath += 'index.html'
|
|
||||||
else:
|
|
||||||
relpath += '.html'
|
|
||||||
return (text, apigen_relpath + 'source/%s' % (relpath,))
|
|
||||||
|
|
||||||
# legacy
|
|
||||||
ReSTChecker = DocfileTests
|
|
||||||
|
|
@ -16,8 +16,6 @@ py lib contact and communication
|
||||||
|
|
||||||
.. _`merlinux.eu`: http://merlinux.eu
|
.. _`merlinux.eu`: http://merlinux.eu
|
||||||
|
|
||||||
.. _`zope3`: http://zope3.zwiki.org/
|
|
||||||
.. _twisted: http://www.twistedmatrix.org
|
|
||||||
.. _future: future.html
|
.. _future: future.html
|
||||||
|
|
||||||
.. _`get an account`:
|
.. _`get an account`:
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,13 @@
|
||||||
import py
|
import py
|
||||||
failure_demo = py.magic.autopath().dirpath('failure_demo.py')
|
failure_demo = py.magic.autopath().dirpath('failure_demo.py')
|
||||||
|
|
||||||
from py.__.test.testing import suptest
|
pytest_plugins = "pytest_pytester"
|
||||||
from py.__.test import event
|
|
||||||
|
|
||||||
def test_failure_demo_fails_properly():
|
def test_failure_demo_fails_properly(testdir):
|
||||||
sorter = suptest.events_from_cmdline([failure_demo])
|
sorter = testdir.inline_run(failure_demo)
|
||||||
passed, skipped, failed = sorter.countoutcomes()
|
passed, skipped, failed = sorter.countoutcomes()
|
||||||
assert passed == 0
|
assert passed == 0
|
||||||
assert failed == 20, failed
|
assert failed == 20, failed
|
||||||
colreports = sorter.get(event.CollectionReport)
|
colreports = sorter.getnamed("collectionreport")
|
||||||
failed = len([x.failed for x in colreports])
|
failed = len([x.failed for x in colreports])
|
||||||
assert failed == 5
|
assert failed == 5
|
||||||
|
|
|
||||||
|
|
@ -105,10 +105,7 @@ be directly usable for such linux-filesystem glue code.
|
||||||
In other words, implementing a `memoryfs`_ or a `dictfs`_ would
|
In other words, implementing a `memoryfs`_ or a `dictfs`_ would
|
||||||
give you two things for free: a filesystem mountable at kernel level
|
give you two things for free: a filesystem mountable at kernel level
|
||||||
as well as a uniform "path" object allowing you to access your
|
as well as a uniform "path" object allowing you to access your
|
||||||
filesystem in convenient ways. (At some point it might
|
filesystem in convenient ways.
|
||||||
even become interesting to think about interfacing to
|
|
||||||
`reiserfs v4 features`_ at the Filesystem level but that
|
|
||||||
is a can of subsequent worms).
|
|
||||||
|
|
||||||
Also interesting to check out is Will McGugan's work on
|
Also interesting to check out is Will McGugan's work on
|
||||||
his `fs package`_.
|
his `fs package`_.
|
||||||
|
|
@ -128,7 +125,6 @@ is Matthew Scotts `dictproxy patch`_ which adds
|
||||||
.. _`dictfs`: http://codespeak.net/pipermail/py-dev/2005-January/000191.html
|
.. _`dictfs`: http://codespeak.net/pipermail/py-dev/2005-January/000191.html
|
||||||
.. _`pylufs`: http://codespeak.net/svn/user/arigo/hack/pylufs/
|
.. _`pylufs`: http://codespeak.net/svn/user/arigo/hack/pylufs/
|
||||||
.. _`pyfuse`: http://codespeak.net/svn/user/arigo/hack/pyfuse/
|
.. _`pyfuse`: http://codespeak.net/svn/user/arigo/hack/pyfuse/
|
||||||
.. _`reiserfs v4 features`: http://www.namesys.com/v4/v4.html
|
|
||||||
|
|
||||||
|
|
||||||
Integrate interactive completion
|
Integrate interactive completion
|
||||||
|
|
|
||||||
|
|
@ -270,3 +270,5 @@ Customizing execution of Functions
|
||||||
function with the given (usually empty set of) arguments.
|
function with the given (usually empty set of) arguments.
|
||||||
|
|
||||||
.. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev
|
.. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ solutions.
|
||||||
Some example usage of :api:`py.path.svnurl`::
|
Some example usage of :api:`py.path.svnurl`::
|
||||||
|
|
||||||
.. >>> import py
|
.. >>> import py
|
||||||
.. >>> if not py.test.config.option.checkremote: raise ValueError('skipchunk')
|
.. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
|
||||||
>>> url = py.path.svnurl('http://codespeak.net/svn/py')
|
>>> url = py.path.svnurl('http://codespeak.net/svn/py')
|
||||||
>>> info = url.info()
|
>>> info = url.info()
|
||||||
>>> info.kind
|
>>> info.kind
|
||||||
|
|
@ -64,7 +64,7 @@ Some example usage of :api:`py.path.svnurl`::
|
||||||
|
|
||||||
Example usage of :api:`py.path.svnwc`::
|
Example usage of :api:`py.path.svnwc`::
|
||||||
|
|
||||||
.. >>> if not py.test.config.option.checkremote: raise ValueError('skipchunk')
|
.. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
|
||||||
>>> temp = py.test.ensuretemp('py.path_documentation')
|
>>> temp = py.test.ensuretemp('py.path_documentation')
|
||||||
>>> wc = py.path.svnwc(temp.join('svnwc'))
|
>>> wc = py.path.svnwc(temp.join('svnwc'))
|
||||||
>>> wc.checkout('http://codespeak.net/svn/py/dist/py/path/local')
|
>>> wc.checkout('http://codespeak.net/svn/py/dist/py/path/local')
|
||||||
|
|
@ -132,6 +132,10 @@ don't have to exist, either)::
|
||||||
>>> sep = py.path.local.sep
|
>>> sep = py.path.local.sep
|
||||||
>>> p2.relto(p1).replace(sep, '/') # os-specific path sep in the string
|
>>> p2.relto(p1).replace(sep, '/') # os-specific path sep in the string
|
||||||
'baz/qux'
|
'baz/qux'
|
||||||
|
>>> p2.bestrelpath(p1)
|
||||||
|
'../..'
|
||||||
|
>>> p2.join(p2.bestrelpath(p1)) == p1
|
||||||
|
True
|
||||||
>>> p3 = p1 / 'baz/qux' # the / operator allows joining, too
|
>>> p3 = p1 / 'baz/qux' # the / operator allows joining, too
|
||||||
>>> p2 == p3
|
>>> p2 == p3
|
||||||
True
|
True
|
||||||
|
|
@ -178,7 +182,7 @@ Setting svn-properties
|
||||||
As an example of 'uncommon' methods, we'll show how to read and write
|
As an example of 'uncommon' methods, we'll show how to read and write
|
||||||
properties in an :api:`py.path.svnwc` instance::
|
properties in an :api:`py.path.svnwc` instance::
|
||||||
|
|
||||||
.. >>> if not py.test.config.option.checkremote: raise ValueError('skipchunk')
|
.. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
|
||||||
>>> wc.propget('foo')
|
>>> wc.propget('foo')
|
||||||
''
|
''
|
||||||
>>> wc.propset('foo', 'bar')
|
>>> wc.propset('foo', 'bar')
|
||||||
|
|
@ -196,7 +200,7 @@ SVN authentication
|
||||||
Some uncommon functionality can also be provided as extensions, such as SVN
|
Some uncommon functionality can also be provided as extensions, such as SVN
|
||||||
authentication::
|
authentication::
|
||||||
|
|
||||||
.. >>> if not py.test.config.option.checkremote: raise ValueError('skipchunk')
|
.. >>> if not py.test.config.option.urlcheck: raise ValueError('skipchunk')
|
||||||
>>> auth = py.path.SvnAuth('anonymous', 'user', cache_auth=False,
|
>>> auth = py.path.SvnAuth('anonymous', 'user', cache_auth=False,
|
||||||
... interactive=False)
|
... interactive=False)
|
||||||
>>> wc.auth = auth
|
>>> wc.auth = auth
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
|
||||||
|
pytest plugins
|
||||||
|
==================
|
||||||
|
|
||||||
|
specifying plugins for directories or test modules
|
||||||
|
---------------------------------------------------------
|
||||||
|
|
||||||
|
py.test loads and configures plugins at tool startup and whenever
|
||||||
|
it encounters new confest or test modules which
|
||||||
|
contain a ``pytest_plugins`` definition. At tool
|
||||||
|
startup the ``PYTEST_PLUGINS`` environment variable
|
||||||
|
is considered as well.
|
||||||
|
|
||||||
|
Example
|
||||||
|
++++++++++
|
||||||
|
|
||||||
|
If you create a ``conftest.py`` file with the following content::
|
||||||
|
|
||||||
|
pytest_plugins = "pytest_plugin1", MyLocalPluginClass
|
||||||
|
|
||||||
|
then test execution within that directory can make use
|
||||||
|
of the according instantiated plugins:
|
||||||
|
|
||||||
|
* the module ``pytest_plugin1`` will be imported and
|
||||||
|
and its contained `Plugin1`` class instantiated.
|
||||||
|
A plugin module can put its dependencies into
|
||||||
|
a "pytest_plugins" attribute at module level as well.
|
||||||
|
|
||||||
|
* the ``MyLocalPluginClass`` will be instantiated
|
||||||
|
and added to the pluginmanager.
|
||||||
|
|
||||||
|
|
||||||
|
Plugin methods
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
A Plugin class may implement the following attributes and methods:
|
||||||
|
|
||||||
|
* pytest_cmdlineoptions: a list of optparse-style py.test.config.Option objects
|
||||||
|
* pytest_configure(self, config): called after command line options have been parsed
|
||||||
|
* pytest_unconfigure(self, config): called before the test process quits
|
||||||
|
* pytest_event(self, event): called for each `pytest event`_
|
||||||
|
|
||||||
|
XXX reference APIcheck'ed full documentation
|
||||||
|
|
||||||
|
_`pytest event`:
|
||||||
|
|
||||||
|
Pytest Events
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
XXX Various reporting events.
|
||||||
|
|
||||||
|
Example plugins
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
XXX here are a few existing plugins:
|
||||||
|
|
||||||
|
* adding reporting facilities, e.g.
|
||||||
|
pytest_terminal: default reporter for writing info to terminals
|
||||||
|
pytest_resultlog: log test results in machine-readable form to a file
|
||||||
|
pytest_eventlog: log all internal pytest events to a file
|
||||||
|
pytest_xfail: "expected to fail" test marker
|
||||||
|
pytest_tmpdir: provide temporary directories to test functions
|
||||||
|
pytest_plugintester: generic apichecks, support for functional plugin tests
|
||||||
|
pytest_pytester: support for testing py.test runs
|
||||||
|
|
||||||
|
* extending test execution, e.g.
|
||||||
|
pytest_apigen: tracing values of function/method calls when running tests
|
||||||
|
|
@ -142,23 +142,18 @@ To make it easier to distinguish the generated tests it is possible to specify a
|
||||||
selecting/unselecting tests by keyword
|
selecting/unselecting tests by keyword
|
||||||
---------------------------------------------
|
---------------------------------------------
|
||||||
|
|
||||||
|
Pytest's keyword mechanism provides a powerful way to
|
||||||
|
group and selectively run tests in your test code base.
|
||||||
You can selectively run tests by specifiying a keyword
|
You can selectively run tests by specifiying a keyword
|
||||||
on the command line. Example::
|
on the command line. Examples:
|
||||||
|
|
||||||
py.test -k test_simple
|
py.test -k test_simple
|
||||||
|
py.test -k "-test_simple"
|
||||||
|
|
||||||
will run all tests that are found from the current directory
|
will run all tests matching (or not matching) the
|
||||||
and where the word "test_simple" equals the start of one part of the
|
"test_simple" keyword. Note that you need to quote
|
||||||
path leading up to the test item. Directory and file basenames as well
|
the keyword if "-" is recognized as an indicator
|
||||||
as function, class and function/method names each form a possibly
|
for a commandline option. Lastly, you may use
|
||||||
matching name. You can also unselect tests by preceding a keyword
|
|
||||||
with a dash::
|
|
||||||
|
|
||||||
py.test. -k "-test_simple"
|
|
||||||
|
|
||||||
will run all tests except where the word "test_simple" matches a tests keyword.
|
|
||||||
Note that you need to quote the keyword if the shell recognizes "-" as an intro
|
|
||||||
to a cmdline option. Lastly, you may use
|
|
||||||
|
|
||||||
py.test. -k "test_simple:"
|
py.test. -k "test_simple:"
|
||||||
|
|
||||||
|
|
@ -166,6 +161,15 @@ which will run all tests after the expression has *matched once*, i.e.
|
||||||
all tests that are seen after a test that matches the "test_simple"
|
all tests that are seen after a test that matches the "test_simple"
|
||||||
keyword.
|
keyword.
|
||||||
|
|
||||||
|
By default, all filename parts and
|
||||||
|
class/function names of a test function are put into the set
|
||||||
|
of keywords for a given test. You may specify additional
|
||||||
|
kewords like this::
|
||||||
|
|
||||||
|
@py.test.keywords("webtest")
|
||||||
|
def test_send_http():
|
||||||
|
...
|
||||||
|
|
||||||
testing with multiple python versions / executables
|
testing with multiple python versions / executables
|
||||||
---------------------------------------------------
|
---------------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,115 +0,0 @@
|
||||||
|
|
||||||
import py
|
|
||||||
from py.__.test import event
|
|
||||||
from py.__.test.testing import suptest
|
|
||||||
from py.__.doc import conftest as doc_conftest
|
|
||||||
|
|
||||||
|
|
||||||
class TestDoctest(suptest.InlineCollection):
|
|
||||||
def setup_method(self, method):
|
|
||||||
super(TestDoctest, self).setup_method(method)
|
|
||||||
p = py.path.local(doc_conftest.__file__)
|
|
||||||
if p.ext == ".pyc":
|
|
||||||
p = p.new(ext=".py")
|
|
||||||
p.copy(self.tmpdir.join("conftest.py"))
|
|
||||||
|
|
||||||
def test_doctest_extra_exec(self):
|
|
||||||
xtxt = self.maketxtfile(x="""
|
|
||||||
hello::
|
|
||||||
.. >>> raise ValueError
|
|
||||||
>>> None
|
|
||||||
""")
|
|
||||||
sorter = suptest.events_from_cmdline([xtxt])
|
|
||||||
passed, skipped, failed = sorter.countoutcomes()
|
|
||||||
assert failed == 1
|
|
||||||
|
|
||||||
def test_doctest_basic(self):
|
|
||||||
xtxt = self.maketxtfile(x="""
|
|
||||||
..
|
|
||||||
>>> from os.path import abspath
|
|
||||||
|
|
||||||
hello world
|
|
||||||
|
|
||||||
>>> assert abspath
|
|
||||||
>>> i=3
|
|
||||||
>>> print i
|
|
||||||
3
|
|
||||||
|
|
||||||
yes yes
|
|
||||||
|
|
||||||
>>> i
|
|
||||||
3
|
|
||||||
|
|
||||||
end
|
|
||||||
""")
|
|
||||||
sorter = suptest.events_from_cmdline([xtxt])
|
|
||||||
passed, skipped, failed = sorter.countoutcomes()
|
|
||||||
assert failed == 0
|
|
||||||
assert passed + skipped == 2
|
|
||||||
|
|
||||||
def test_doctest_eol(self):
|
|
||||||
ytxt = self.maketxtfile(y=".. >>> 1 + 1\r\n 2\r\n\r\n")
|
|
||||||
sorter = suptest.events_from_cmdline([ytxt])
|
|
||||||
passed, skipped, failed = sorter.countoutcomes()
|
|
||||||
assert failed == 0
|
|
||||||
assert passed + skipped == 2
|
|
||||||
|
|
||||||
def test_doctest_indentation(self):
|
|
||||||
footxt = self.maketxtfile(foo=
|
|
||||||
'..\n >>> print "foo\\n bar"\n foo\n bar\n')
|
|
||||||
sorter = suptest.events_from_cmdline([footxt])
|
|
||||||
passed, skipped, failed = sorter.countoutcomes()
|
|
||||||
assert failed == 0
|
|
||||||
assert skipped + passed == 2
|
|
||||||
|
|
||||||
def test_js_ignore(self):
|
|
||||||
xtxt = self.maketxtfile(xtxt="""
|
|
||||||
`blah`_
|
|
||||||
|
|
||||||
.. _`blah`: javascript:some_function()
|
|
||||||
""")
|
|
||||||
sorter = suptest.events_from_cmdline([xtxt])
|
|
||||||
passed, skipped, failed = sorter.countoutcomes()
|
|
||||||
assert failed == 0
|
|
||||||
assert skipped + passed == 3
|
|
||||||
|
|
||||||
def test_deindent():
|
|
||||||
deindent = doc_conftest.deindent
|
|
||||||
assert deindent('foo') == 'foo'
|
|
||||||
assert deindent('foo\n bar') == 'foo\n bar'
|
|
||||||
assert deindent(' foo\n bar\n') == 'foo\nbar\n'
|
|
||||||
assert deindent(' foo\n\n bar\n') == 'foo\n\nbar\n'
|
|
||||||
assert deindent(' foo\n bar\n') == 'foo\n bar\n'
|
|
||||||
assert deindent(' foo\n bar\n') == ' foo\nbar\n'
|
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_linkrole():
|
|
||||||
from py.__.doc.conftest import get_apigen_relpath
|
|
||||||
apigen_relpath = get_apigen_relpath()
|
|
||||||
from py.__.doc.conftest import resolve_linkrole
|
|
||||||
assert resolve_linkrole('api', 'py.foo.bar', False) == (
|
|
||||||
'py.foo.bar', apigen_relpath + 'api/foo.bar.html')
|
|
||||||
assert resolve_linkrole('api', 'py.foo.bar()', False) == (
|
|
||||||
'py.foo.bar()', apigen_relpath + 'api/foo.bar.html')
|
|
||||||
assert resolve_linkrole('api', 'py', False) == (
|
|
||||||
'py', apigen_relpath + 'api/index.html')
|
|
||||||
py.test.raises(AssertionError, 'resolve_linkrole("api", "foo.bar")')
|
|
||||||
assert resolve_linkrole('source', 'py/foo/bar.py', False) == (
|
|
||||||
'py/foo/bar.py', apigen_relpath + 'source/foo/bar.py.html')
|
|
||||||
assert resolve_linkrole('source', 'py/foo/', False) == (
|
|
||||||
'py/foo/', apigen_relpath + 'source/foo/index.html')
|
|
||||||
assert resolve_linkrole('source', 'py/', False) == (
|
|
||||||
'py/', apigen_relpath + 'source/index.html')
|
|
||||||
py.test.raises(AssertionError, 'resolve_linkrole("source", "/foo/bar/")')
|
|
||||||
|
|
||||||
def test_resolve_linkrole_check_api():
|
|
||||||
from py.__.doc.conftest import resolve_linkrole
|
|
||||||
assert resolve_linkrole('api', 'py.test.ensuretemp')
|
|
||||||
py.test.raises(AssertionError, "resolve_linkrole('api', 'py.foo.baz')")
|
|
||||||
|
|
||||||
def test_resolve_linkrole_check_source():
|
|
||||||
from py.__.doc.conftest import resolve_linkrole
|
|
||||||
assert resolve_linkrole('source', 'py/path/common.py')
|
|
||||||
py.test.raises(AssertionError,
|
|
||||||
"resolve_linkrole('source', 'py/foo/bar.py')")
|
|
||||||
|
|
||||||
|
|
@ -168,5 +168,4 @@ complete the probably request-specific serialization of
|
||||||
your Tags. Hum, it's probably harder to explain this than to
|
your Tags. Hum, it's probably harder to explain this than to
|
||||||
actually code it :-)
|
actually code it :-)
|
||||||
|
|
||||||
.. _Nevow: http://www.divmod.org/projects/nevow
|
|
||||||
.. _`py.test`: test.html
|
.. _`py.test`: test.html
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ from __future__ import generators
|
||||||
import os, sys, time, signal
|
import os, sys, time, signal
|
||||||
import py
|
import py
|
||||||
from py.__.execnet import gateway
|
from py.__.execnet import gateway
|
||||||
from py.__.conftest import option
|
|
||||||
mypath = py.magic.autopath()
|
mypath = py.magic.autopath()
|
||||||
|
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
|
|
@ -247,7 +246,10 @@ class BasicRemoteExecution:
|
||||||
channel.waitclose(TESTTIMEOUT)
|
channel.waitclose(TESTTIMEOUT)
|
||||||
assert l == [42]
|
assert l == [42]
|
||||||
|
|
||||||
def test_channel_callback_stays_active(self, earlyfree=True):
|
def test_channel_callback_stays_active(self):
|
||||||
|
self.check_channel_callback_stays_active(earlyfree=True)
|
||||||
|
|
||||||
|
def check_channel_callback_stays_active(self, earlyfree=True):
|
||||||
# with 'earlyfree==True', this tests the "sendonly" channel state.
|
# with 'earlyfree==True', this tests the "sendonly" channel state.
|
||||||
l = []
|
l = []
|
||||||
channel = self.gw.remote_exec(source='''
|
channel = self.gw.remote_exec(source='''
|
||||||
|
|
@ -278,7 +280,7 @@ class BasicRemoteExecution:
|
||||||
return subchannel
|
return subchannel
|
||||||
|
|
||||||
def test_channel_callback_remote_freed(self):
|
def test_channel_callback_remote_freed(self):
|
||||||
channel = self.test_channel_callback_stays_active(False)
|
channel = self.check_channel_callback_stays_active(earlyfree=False)
|
||||||
channel.waitclose(TESTTIMEOUT) # freed automatically at the end of producer()
|
channel.waitclose(TESTTIMEOUT) # freed automatically at the end of producer()
|
||||||
|
|
||||||
def test_channel_endmarker_callback(self):
|
def test_channel_endmarker_callback(self):
|
||||||
|
|
@ -568,16 +570,16 @@ class TestSocketGateway(SocketGatewaySetup, BasicRemoteExecution):
|
||||||
|
|
||||||
class TestSshGateway(BasicRemoteExecution):
|
class TestSshGateway(BasicRemoteExecution):
|
||||||
def setup_class(cls):
|
def setup_class(cls):
|
||||||
if option.sshtarget is None:
|
if py.test.config.option.sshhost is None:
|
||||||
py.test.skip("no known ssh target, use -S to set one")
|
py.test.skip("no known ssh target, use --sshhost to set one")
|
||||||
cls.gw = py.execnet.SshGateway(option.sshtarget)
|
cls.gw = py.execnet.SshGateway(py.test.config.option.sshhost)
|
||||||
|
|
||||||
def test_sshconfig_functional(self):
|
def test_sshconfig_functional(self):
|
||||||
tmpdir = py.test.ensuretemp("test_sshconfig")
|
tmpdir = py.test.ensuretemp("test_sshconfig")
|
||||||
ssh_config = tmpdir.join("ssh_config")
|
ssh_config = tmpdir.join("ssh_config")
|
||||||
ssh_config.write(
|
ssh_config.write(
|
||||||
"Host alias123\n"
|
"Host alias123\n"
|
||||||
" HostName %s\n" % (option.sshtarget,))
|
" HostName %s\n" % (py.test.config.option.sshhost,))
|
||||||
gw = py.execnet.SshGateway("alias123", ssh_config=ssh_config)
|
gw = py.execnet.SshGateway("alias123", ssh_config=ssh_config)
|
||||||
assert gw._cmd.find("-F") != -1
|
assert gw._cmd.find("-F") != -1
|
||||||
assert gw._cmd.find(str(ssh_config)) != -1
|
assert gw._cmd.find(str(ssh_config)) != -1
|
||||||
|
|
@ -585,7 +587,7 @@ class TestSshGateway(BasicRemoteExecution):
|
||||||
gw.exit()
|
gw.exit()
|
||||||
|
|
||||||
def test_sshaddress(self):
|
def test_sshaddress(self):
|
||||||
assert self.gw.remoteaddress == option.sshtarget
|
assert self.gw.remoteaddress == py.test.config.option.sshhost
|
||||||
|
|
||||||
def test_connexion_failes_on_non_existing_hosts(self):
|
def test_connexion_failes_on_non_existing_hosts(self):
|
||||||
py.test.raises(IOError,
|
py.test.raises(IOError,
|
||||||
|
|
|
||||||
|
|
@ -64,12 +64,6 @@ class Package(object):
|
||||||
def _resolve(self, extpyish):
|
def _resolve(self, extpyish):
|
||||||
""" resolve a combined filesystem/python extpy-ish path. """
|
""" resolve a combined filesystem/python extpy-ish path. """
|
||||||
fspath, modpath = extpyish
|
fspath, modpath = extpyish
|
||||||
if not fspath.endswith('.py'):
|
|
||||||
import py
|
|
||||||
e = py.path.local(self.implmodule.__file__)
|
|
||||||
e = e.dirpath(fspath, abs=True)
|
|
||||||
e = py.path.extpy(e, modpath)
|
|
||||||
return e.resolve()
|
|
||||||
assert fspath.startswith('./'), \
|
assert fspath.startswith('./'), \
|
||||||
"%r is not an implementation path (XXX)" % (extpyish,)
|
"%r is not an implementation path (XXX)" % (extpyish,)
|
||||||
implmodule = self._loadimpl(fspath[:-3])
|
implmodule = self._loadimpl(fspath[:-3])
|
||||||
|
|
@ -166,15 +160,18 @@ def setmodule(modpath, module):
|
||||||
sys.modules[modpath] = module
|
sys.modules[modpath] = module
|
||||||
|
|
||||||
# ---------------------------------------------------
|
# ---------------------------------------------------
|
||||||
# Virtual Module Object
|
# API Module Object
|
||||||
# ---------------------------------------------------
|
# ---------------------------------------------------
|
||||||
|
|
||||||
class Module(ModuleType):
|
class ApiModule(ModuleType):
|
||||||
def __init__(self, pkg, name):
|
def __init__(self, pkg, name):
|
||||||
self.__pkg__ = pkg
|
self.__pkg__ = pkg
|
||||||
self.__name__ = name
|
self.__name__ = name
|
||||||
self.__map__ = {}
|
self.__map__ = {}
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<ApiModule %r>' % (self.__name__,)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
if '*' in self.__map__:
|
if '*' in self.__map__:
|
||||||
extpy = self.__map__['*'][0], name
|
extpy = self.__map__['*'][0], name
|
||||||
|
|
@ -209,9 +206,6 @@ class Module(ModuleType):
|
||||||
except (AttributeError, TypeError):
|
except (AttributeError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return '<Module %r>' % (self.__name__, )
|
|
||||||
|
|
||||||
def getdict(self):
|
def getdict(self):
|
||||||
# force all the content of the module to be loaded when __dict__ is read
|
# force all the content of the module to be loaded when __dict__ is read
|
||||||
dictdescr = ModuleType.__dict__['__dict__']
|
dictdescr = ModuleType.__dict__['__dict__']
|
||||||
|
|
@ -254,7 +248,7 @@ def initpkg(pkgname, exportdefs, **kw):
|
||||||
previous = current
|
previous = current
|
||||||
current += '.' + name
|
current += '.' + name
|
||||||
if current not in seen:
|
if current not in seen:
|
||||||
seen[current] = mod = Module(pkg, current)
|
seen[current] = mod = ApiModule(pkg, current)
|
||||||
setattr(seen[previous], name, mod)
|
setattr(seen[previous], name, mod)
|
||||||
setmodule(current, mod)
|
setmodule(current, mod)
|
||||||
|
|
||||||
|
|
@ -272,3 +266,12 @@ def initpkg(pkgname, exportdefs, **kw):
|
||||||
for mod, pypart, extpy in deferred_imports:
|
for mod, pypart, extpy in deferred_imports:
|
||||||
setattr(mod, pypart, pkg._resolve(extpy))
|
setattr(mod, pypart, pkg._resolve(extpy))
|
||||||
|
|
||||||
|
autoimport(pkgname)
|
||||||
|
|
||||||
|
def autoimport(pkgname):
|
||||||
|
import py
|
||||||
|
ENVKEY = pkgname.upper() + "_AUTOIMPORT"
|
||||||
|
if ENVKEY in os.environ:
|
||||||
|
for impname in os.environ[ENVKEY].split(","):
|
||||||
|
py._com.pyplugins.notify("autoimport", impname)
|
||||||
|
__import__(impname)
|
||||||
|
|
|
||||||
|
|
@ -71,3 +71,11 @@ def strip_html_header(string, encoding='utf8'):
|
||||||
break
|
break
|
||||||
uni = match.group(1)
|
uni = match.group(1)
|
||||||
return uni
|
return uni
|
||||||
|
|
||||||
|
class Project: # used for confrest.py files
|
||||||
|
def __init__(self, sourcepath):
|
||||||
|
self.sourcepath = sourcepath
|
||||||
|
def process(self, path):
|
||||||
|
return process(path)
|
||||||
|
def get_htmloutputpath(self, path):
|
||||||
|
return path.new(ext='html')
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ class TestBuildcostAccess(BasicCacheAPITest):
|
||||||
|
|
||||||
|
|
||||||
class TestAging(BasicCacheAPITest):
|
class TestAging(BasicCacheAPITest):
|
||||||
maxsecs = 0.02
|
maxsecs = 0.10
|
||||||
cache = AgingCache(maxentries=128, maxseconds=maxsecs)
|
cache = AgingCache(maxentries=128, maxseconds=maxsecs)
|
||||||
|
|
||||||
def test_cache_eviction(self):
|
def test_cache_eviction(self):
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,233 @@
|
||||||
|
|
||||||
|
import py
|
||||||
|
import os
|
||||||
|
from py._com import PyPlugins, MultiCall
|
||||||
|
|
||||||
|
pytest_plugins = "xfail"
|
||||||
|
|
||||||
|
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
|
||||||
|
return 17
|
||||||
|
|
||||||
|
class P2:
|
||||||
|
def m(self, __call__, x):
|
||||||
|
assert __call__.currentmethod == self.m
|
||||||
|
assert __call__.args
|
||||||
|
assert __call__.results == []
|
||||||
|
assert __call__.methods
|
||||||
|
return 23
|
||||||
|
|
||||||
|
p1 = P1()
|
||||||
|
p2 = P2()
|
||||||
|
multicall = MultiCall([p1.m, p2.m], 23)
|
||||||
|
reslist = multicall.execute()
|
||||||
|
assert len(reslist) == 2
|
||||||
|
# ensure reversed order
|
||||||
|
assert reslist == [23, 17]
|
||||||
|
|
||||||
|
def test_optionalcallarg(self):
|
||||||
|
class P1:
|
||||||
|
def m(self, x):
|
||||||
|
return x
|
||||||
|
call = MultiCall([P1().m], 23)
|
||||||
|
assert call.execute() == [23]
|
||||||
|
assert call.execute(firstresult=True) == 23
|
||||||
|
|
||||||
|
def test_call_subexecute(self):
|
||||||
|
def m(__call__):
|
||||||
|
subresult = __call__.execute(firstresult=True)
|
||||||
|
return subresult + 1
|
||||||
|
|
||||||
|
def n():
|
||||||
|
return 1
|
||||||
|
|
||||||
|
call = MultiCall([n, m])
|
||||||
|
res = call.execute(firstresult=True)
|
||||||
|
assert res == 2
|
||||||
|
|
||||||
|
class TestPyPlugins:
|
||||||
|
def test_MultiCall(self):
|
||||||
|
plugins = PyPlugins()
|
||||||
|
assert hasattr(plugins, "MultiCall")
|
||||||
|
|
||||||
|
def test_register(self):
|
||||||
|
plugins = PyPlugins()
|
||||||
|
class MyPlugin:
|
||||||
|
pass
|
||||||
|
my = MyPlugin()
|
||||||
|
plugins.register(my)
|
||||||
|
assert plugins.getplugins() == [my]
|
||||||
|
my2 = MyPlugin()
|
||||||
|
plugins.register(my2)
|
||||||
|
assert plugins.getplugins() == [my, my2]
|
||||||
|
|
||||||
|
assert plugins.isregistered(my)
|
||||||
|
assert plugins.isregistered(my2)
|
||||||
|
plugins.unregister(my)
|
||||||
|
assert not plugins.isregistered(my)
|
||||||
|
assert plugins.getplugins() == [my2]
|
||||||
|
|
||||||
|
#@py.test.keywords(xfail=True)
|
||||||
|
def test_onregister(self):
|
||||||
|
py.test.skip("implement exitfirst plugin and "
|
||||||
|
"modify xfail plugin to override exitfirst behaviour?")
|
||||||
|
plugins = PyPlugins()
|
||||||
|
l = []
|
||||||
|
class MyApi:
|
||||||
|
def pyevent_plugin_registered(self, plugin):
|
||||||
|
l.append(plugin)
|
||||||
|
def pyevent_plugin_unregistered(self, plugin):
|
||||||
|
l.remove(plugin)
|
||||||
|
myapi = MyApi()
|
||||||
|
plugins.register(myapi)
|
||||||
|
assert len(l) == 1
|
||||||
|
assert l[0] is myapi
|
||||||
|
plugins.unregister(myapi)
|
||||||
|
assert not l
|
||||||
|
|
||||||
|
def test_call_methods(self):
|
||||||
|
plugins = PyPlugins()
|
||||||
|
class api1:
|
||||||
|
def m(self, __call__, x):
|
||||||
|
return x
|
||||||
|
class api2:
|
||||||
|
def m(self, __call__, x, y=33):
|
||||||
|
return y
|
||||||
|
plugins.register(api1())
|
||||||
|
plugins.register(api2())
|
||||||
|
res = plugins.call_firstresult("m", x=5)
|
||||||
|
assert plugins.call_firstresult("notexist") is None
|
||||||
|
|
||||||
|
assert res == 33
|
||||||
|
reslist = plugins.call_each("m", x=5)
|
||||||
|
assert len(reslist) == 2
|
||||||
|
assert 5 in reslist
|
||||||
|
assert 33 in reslist
|
||||||
|
assert plugins.call_each("notexist") == []
|
||||||
|
|
||||||
|
assert plugins.call_plugin(api1(), 'm', x=12) == 12
|
||||||
|
assert plugins.call_plugin(api2(), 't') is None
|
||||||
|
|
||||||
|
def test_call_none_is_no_result(self):
|
||||||
|
plugins = PyPlugins()
|
||||||
|
class api1:
|
||||||
|
def m(self):
|
||||||
|
return None
|
||||||
|
class api2:
|
||||||
|
def m(self, __call__):
|
||||||
|
return 41
|
||||||
|
plugins.register(api1())
|
||||||
|
plugins.register(api1())
|
||||||
|
plugins.register(api2())
|
||||||
|
assert plugins.call_firstresult('m') == 41
|
||||||
|
assert plugins.call_each('m') == [41]
|
||||||
|
|
||||||
|
def test_call_noneasresult(self):
|
||||||
|
plugins = PyPlugins()
|
||||||
|
class api1:
|
||||||
|
def m(self, __call__):
|
||||||
|
return __call__.NONEASRESULT
|
||||||
|
plugins.register(api1())
|
||||||
|
plugins.register(api1())
|
||||||
|
assert plugins.call_firstresult('m') is None
|
||||||
|
assert plugins.call_each('m') == [None, None]
|
||||||
|
|
||||||
|
def test_listattr(self):
|
||||||
|
plugins = PyPlugins()
|
||||||
|
class api1:
|
||||||
|
x = 42
|
||||||
|
class api2:
|
||||||
|
x = 41
|
||||||
|
plugins.register(api1())
|
||||||
|
plugins.register(api2())
|
||||||
|
l = list(plugins.listattr('x'))
|
||||||
|
l.sort()
|
||||||
|
assert l == [41, 42]
|
||||||
|
|
||||||
|
def test_notify_anonymous_ordered(self):
|
||||||
|
plugins = PyPlugins()
|
||||||
|
l = []
|
||||||
|
class api1:
|
||||||
|
def pyevent_hello(self):
|
||||||
|
l.append("hellospecific")
|
||||||
|
class api2:
|
||||||
|
def pyevent(self, name, *args):
|
||||||
|
if name == "hello":
|
||||||
|
l.append(name + "anonymous")
|
||||||
|
plugins.register(api1())
|
||||||
|
plugins.register(api2())
|
||||||
|
plugins.notify('hello')
|
||||||
|
assert l == ["hellospecific", "helloanonymous"]
|
||||||
|
|
||||||
|
def test_consider_env(self, monkeypatch):
|
||||||
|
# XXX write a helper for preserving os.environ
|
||||||
|
plugins = PyPlugins()
|
||||||
|
monkeypatch.setitem(os.environ, 'PYLIB', "unknownconsider_env")
|
||||||
|
py.test.raises(ImportError, "plugins.consider_env()")
|
||||||
|
|
||||||
|
def test_consider_module(self):
|
||||||
|
plugins = PyPlugins()
|
||||||
|
mod = py.std.new.module("temp")
|
||||||
|
mod.pylib = ["xxx nomod"]
|
||||||
|
excinfo = py.test.raises(ImportError, "plugins.consider_module(mod)")
|
||||||
|
mod.pylib = "os"
|
||||||
|
class Events(list):
|
||||||
|
def pyevent_importingmodule(self, mod):
|
||||||
|
self.append(mod)
|
||||||
|
l = Events()
|
||||||
|
plugins.register(l)
|
||||||
|
plugins.consider_module(mod)
|
||||||
|
assert len(l) == 1
|
||||||
|
assert l[0] == (mod.pylib)
|
||||||
|
|
||||||
|
def test_api_and_defaults():
|
||||||
|
assert isinstance(py._com.pyplugins, PyPlugins)
|
||||||
|
|
||||||
|
def test_subprocess_env():
|
||||||
|
# XXX write a helper for preserving os.environ
|
||||||
|
plugins = PyPlugins()
|
||||||
|
KEY = "PYLIB"
|
||||||
|
old = os.environ.get(KEY, None)
|
||||||
|
olddir = py.path.local(py.__file__).dirpath().dirpath().chdir()
|
||||||
|
try:
|
||||||
|
os.environ[KEY] = "unknownconsider_env"
|
||||||
|
excinfo = py.test.raises(py.process.cmdexec.Error, """
|
||||||
|
py.process.cmdexec("python -c 'import py'")
|
||||||
|
""")
|
||||||
|
assert str(excinfo.value).find("ImportError") != -1
|
||||||
|
assert str(excinfo.value).find("unknownconsider") != -1
|
||||||
|
finally:
|
||||||
|
olddir.chdir()
|
||||||
|
if old is None:
|
||||||
|
del os.environ[KEY]
|
||||||
|
else:
|
||||||
|
os.environ[KEY] = old
|
||||||
|
|
||||||
|
class TestPyPluginsEvents:
|
||||||
|
def test_pyevent_named_dispatch(self):
|
||||||
|
plugins = PyPlugins()
|
||||||
|
l = []
|
||||||
|
class A:
|
||||||
|
def pyevent_name(self, x):
|
||||||
|
l.append(x)
|
||||||
|
plugins.register(A())
|
||||||
|
plugins.notify("name", 13)
|
||||||
|
assert l == [13]
|
||||||
|
|
||||||
|
def test_pyevent_anonymous_dispatch(self):
|
||||||
|
plugins = PyPlugins()
|
||||||
|
l = []
|
||||||
|
class A:
|
||||||
|
def pyevent(self, name, *args, **kwargs):
|
||||||
|
if name == "name":
|
||||||
|
l.extend([args, kwargs])
|
||||||
|
|
||||||
|
plugins.register(A())
|
||||||
|
plugins.notify("name", 13, x=15)
|
||||||
|
assert l == [(13, ), {'x':15}]
|
||||||
|
|
||||||
|
|
@ -12,20 +12,20 @@ def checksubpackage(name):
|
||||||
assert getattr(obj, '__map__') == {}
|
assert getattr(obj, '__map__') == {}
|
||||||
|
|
||||||
def test_dir():
|
def test_dir():
|
||||||
from py.__.initpkg import Module
|
from py.__.initpkg import ApiModule
|
||||||
for name in dir(py):
|
for name in dir(py):
|
||||||
if name == 'magic': # greenlets don't work everywhere, we don't care here
|
if name == 'magic': # greenlets don't work everywhere, we don't care here
|
||||||
continue
|
continue
|
||||||
if not name.startswith('_'):
|
if not name.startswith('_'):
|
||||||
yield checksubpackage, name
|
yield checksubpackage, name
|
||||||
|
|
||||||
from py.initpkg import Module
|
from py.initpkg import ApiModule
|
||||||
glob = []
|
glob = []
|
||||||
class MyModule(Module):
|
class MyModule(ApiModule):
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
glob.append(self.__dict__)
|
glob.append(self.__dict__)
|
||||||
assert isinstance(glob[-1], (dict, type(None)))
|
assert isinstance(glob[-1], (dict, type(None)))
|
||||||
Module.__init__(self, *args)
|
ApiModule.__init__(self, *args)
|
||||||
|
|
||||||
def test_early__dict__access():
|
def test_early__dict__access():
|
||||||
mymod = MyModule("whatever", "myname")
|
mymod = MyModule("whatever", "myname")
|
||||||
|
|
@ -68,7 +68,10 @@ def test_importall():
|
||||||
base.join('execnet', 'script'),
|
base.join('execnet', 'script'),
|
||||||
base.join('compat', 'testing'),
|
base.join('compat', 'testing'),
|
||||||
)
|
)
|
||||||
for p in base.visit('*.py', lambda x: x.check(dotfile=0)):
|
def recurse(p):
|
||||||
|
return p.check(dotfile=0) and p.basename != "attic"
|
||||||
|
|
||||||
|
for p in base.visit('*.py', recurse):
|
||||||
if p.basename == '__init__.py':
|
if p.basename == '__init__.py':
|
||||||
continue
|
continue
|
||||||
relpath = p.new(ext='').relto(base)
|
relpath = p.new(ext='').relto(base)
|
||||||
|
|
@ -255,3 +258,8 @@ class TestRealModule:
|
||||||
# help(std.path)
|
# help(std.path)
|
||||||
# #assert False
|
# #assert False
|
||||||
|
|
||||||
|
|
||||||
|
def test_autoimport():
|
||||||
|
from py.initpkg import autoimport
|
||||||
|
py.std.os.environ['AUTOTEST_AUTOIMPORT'] = "nonexistmodule"
|
||||||
|
py.test.raises(ImportError, "autoimport('autotest')")
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,20 @@
|
||||||
import py
|
import py
|
||||||
from py.__.misc.warn import WarningBus
|
from py.__.misc.warn import WarningPlugin
|
||||||
mypath = py.magic.autopath()
|
mypath = py.magic.autopath()
|
||||||
|
|
||||||
class TestWarningBus:
|
class TestWarningPlugin:
|
||||||
def setup_method(self, method):
|
def setup_method(self, method):
|
||||||
self.wb = WarningBus()
|
self.bus = py._com.PyPlugins()
|
||||||
|
self.wb = WarningPlugin(self.bus)
|
||||||
|
self.bus.register(self)
|
||||||
self.warnings = []
|
self.warnings = []
|
||||||
self.wb.subscribe(self.warnings.append)
|
|
||||||
|
|
||||||
def test_basic(self):
|
def pyevent_WARNING(self, warning):
|
||||||
|
self.warnings.append(warning)
|
||||||
|
|
||||||
|
def test_event_generation(self):
|
||||||
self.wb.warn("hello")
|
self.wb.warn("hello")
|
||||||
assert len(self.warnings) == 1
|
assert len(self.warnings) == 1
|
||||||
self.wb.unsubscribe(self.warnings.append)
|
|
||||||
self.wb.warn("this")
|
|
||||||
assert len(self.warnings) == 1
|
|
||||||
w = self.warnings[0]
|
|
||||||
|
|
||||||
def test_location(self):
|
def test_location(self):
|
||||||
self.wb.warn("again")
|
self.wb.warn("again")
|
||||||
|
|
@ -27,19 +27,16 @@ class TestWarningBus:
|
||||||
assert str(warning) == warning.msg
|
assert str(warning) == warning.msg
|
||||||
|
|
||||||
def test_stacklevel(self):
|
def test_stacklevel(self):
|
||||||
l = []
|
|
||||||
self.wb.subscribe(l.append)
|
|
||||||
def f():
|
def f():
|
||||||
self.wb.warn("x", stacklevel=2)
|
self.wb.warn("x", stacklevel=2)
|
||||||
# 5
|
# 3
|
||||||
# 6
|
# 4
|
||||||
f()
|
f()
|
||||||
lno = self.test_stacklevel.im_func.func_code.co_firstlineno + 7
|
lno = self.test_stacklevel.im_func.func_code.co_firstlineno + 5
|
||||||
warning = l[0]
|
warning = self.warnings[0]
|
||||||
assert warning.lineno == lno
|
assert warning.lineno == lno
|
||||||
|
|
||||||
def test_forwarding_to_warnings_module(self):
|
def test_forwarding_to_warnings_module(self):
|
||||||
self.wb._setforwarding()
|
|
||||||
py.test.deprecated_call(self.wb.warn, "x")
|
py.test.deprecated_call(self.wb.warn, "x")
|
||||||
|
|
||||||
def test_apiwarn(self):
|
def test_apiwarn(self):
|
||||||
|
|
@ -47,7 +44,6 @@ class TestWarningBus:
|
||||||
warning = self.warnings[0]
|
warning = self.warnings[0]
|
||||||
assert warning.msg == "xxx (since version 3.0)"
|
assert warning.msg == "xxx (since version 3.0)"
|
||||||
|
|
||||||
def test_APIWARN():
|
def test_default():
|
||||||
from py.__.misc.warn import APIWARN
|
from py.__.misc.warn import APIWARN
|
||||||
wb = APIWARN.im_self
|
assert py._com.pyplugins.isregistered(APIWARN.im_self)
|
||||||
assert wb._forward in wb._eventbus._subscribers
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import py, sys
|
import py, sys
|
||||||
from py.__.test.event import EventBus
|
|
||||||
|
|
||||||
class Warning(py.std.exceptions.DeprecationWarning):
|
class Warning(py.std.exceptions.DeprecationWarning):
|
||||||
def __init__(self, msg, path, lineno):
|
def __init__(self, msg, path, lineno):
|
||||||
|
|
@ -11,19 +10,16 @@ class Warning(py.std.exceptions.DeprecationWarning):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.msg
|
return self.msg
|
||||||
|
|
||||||
class WarningBus(object):
|
# XXX probably only apiwarn() + py._com.pyplugins forwarding
|
||||||
def __init__(self):
|
# warn_explicit is actually needed
|
||||||
self._eventbus = EventBus()
|
|
||||||
|
|
||||||
def subscribe(self, callable):
|
class WarningPlugin(object):
|
||||||
self._eventbus.subscribe(callable)
|
def __init__(self, bus):
|
||||||
|
self.bus = bus
|
||||||
|
bus.register(self)
|
||||||
|
|
||||||
def unsubscribe(self, callable):
|
def pyevent_WARNING(self, warning):
|
||||||
self._eventbus.unsubscribe(callable)
|
# forward to python warning system
|
||||||
|
|
||||||
def _setforwarding(self):
|
|
||||||
self._eventbus.subscribe(self._forward)
|
|
||||||
def _forward(self, warning):
|
|
||||||
py.std.warnings.warn_explicit(warning, category=Warning,
|
py.std.warnings.warn_explicit(warning, category=Warning,
|
||||||
filename=str(warning.path),
|
filename=str(warning.path),
|
||||||
lineno=warning.lineno,
|
lineno=warning.lineno,
|
||||||
|
|
@ -66,9 +62,8 @@ class WarningBus(object):
|
||||||
filename = module
|
filename = module
|
||||||
path = py.path.local(filename)
|
path = py.path.local(filename)
|
||||||
warning = Warning(msg, path, lineno)
|
warning = Warning(msg, path, lineno)
|
||||||
self._eventbus.notify(warning)
|
self.bus.notify("WARNING", warning)
|
||||||
|
|
||||||
# singleton api warner for py lib
|
# singleton api warner for py lib
|
||||||
apiwarner = WarningBus()
|
apiwarner = WarningPlugin(py._com.pyplugins)
|
||||||
apiwarner._setforwarding()
|
|
||||||
APIWARN = apiwarner.apiwarn
|
APIWARN = apiwarner.apiwarn
|
||||||
|
|
|
||||||
|
|
@ -152,6 +152,30 @@ class PathBase(object):
|
||||||
return strself[len(strrelpath):]
|
return strself[len(strrelpath):]
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def bestrelpath(self, dest):
|
||||||
|
""" return relative path from self to dest
|
||||||
|
such that self.join(bestrelpath) == dest.
|
||||||
|
if not such path can be determined return dest.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
base = self.common(dest)
|
||||||
|
if not base: # can be the case on windows
|
||||||
|
return dest
|
||||||
|
self2base = self.relto(base)
|
||||||
|
reldest = dest.relto(base)
|
||||||
|
if self2base:
|
||||||
|
n = self2base.count(self.sep) + 1
|
||||||
|
else:
|
||||||
|
n = 0
|
||||||
|
l = ['..'] * n
|
||||||
|
if reldest:
|
||||||
|
l.append(reldest)
|
||||||
|
target = dest.sep.join(l)
|
||||||
|
return target
|
||||||
|
except AttributeError:
|
||||||
|
return dest
|
||||||
|
|
||||||
|
|
||||||
def parts(self, reverse=False):
|
def parts(self, reverse=False):
|
||||||
""" return a root-first list of all ancestor directories
|
""" return a root-first list of all ancestor directories
|
||||||
plus the path itself.
|
plus the path itself.
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ from py.path import SvnAuth
|
||||||
import svntestbase
|
import svntestbase
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
import time
|
import time
|
||||||
from py.__.conftest import option
|
|
||||||
|
|
||||||
def make_repo_auth(repo, userdata):
|
def make_repo_auth(repo, userdata):
|
||||||
""" write config to repo
|
""" write config to repo
|
||||||
|
|
@ -251,7 +250,7 @@ class TestSvnURLAuth(object):
|
||||||
class SvnAuthFunctionalTestBase(object):
|
class SvnAuthFunctionalTestBase(object):
|
||||||
def setup_class(cls):
|
def setup_class(cls):
|
||||||
svntestbase.getsvnbin()
|
svntestbase.getsvnbin()
|
||||||
if not option.runslowtests:
|
if not py.test.config.option.runslowtests:
|
||||||
py.test.skip('skipping slow functional tests - use --runslowtests '
|
py.test.skip('skipping slow functional tests - use --runslowtests '
|
||||||
'to override')
|
'to override')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ from py.__.path.svn.testing.svntestbase import CommonSvnTests, getrepowc, getsvn
|
||||||
from py.__.path.svn.wccommand import InfoSvnWCCommand, XMLWCStatus
|
from py.__.path.svn.wccommand import InfoSvnWCCommand, XMLWCStatus
|
||||||
from py.__.path.svn.wccommand import parse_wcinfotime
|
from py.__.path.svn.wccommand import parse_wcinfotime
|
||||||
from py.__.path.svn import svncommon
|
from py.__.path.svn import svncommon
|
||||||
from py.__.conftest import option
|
|
||||||
|
|
||||||
if sys.platform != 'win32':
|
if sys.platform != 'win32':
|
||||||
def normpath(p):
|
def normpath(p):
|
||||||
|
|
@ -157,7 +156,7 @@ class TestWCSvnCommandPath(CommonSvnTests):
|
||||||
self.root.revert(rec=1)
|
self.root.revert(rec=1)
|
||||||
|
|
||||||
def test_status_conflict(self):
|
def test_status_conflict(self):
|
||||||
if not option.runslowtests:
|
if not py.test.config.option.runslowtests:
|
||||||
py.test.skip('skipping slow unit tests - use --runslowtests '
|
py.test.skip('skipping slow unit tests - use --runslowtests '
|
||||||
'to override')
|
'to override')
|
||||||
wc = self.root
|
wc = self.root
|
||||||
|
|
@ -177,7 +176,7 @@ class TestWCSvnCommandPath(CommonSvnTests):
|
||||||
assert [x.basename for x in s.conflict] == ['conflictsamplefile']
|
assert [x.basename for x in s.conflict] == ['conflictsamplefile']
|
||||||
|
|
||||||
def test_status_external(self):
|
def test_status_external(self):
|
||||||
if not option.runslowtests:
|
if not py.test.config.option.runslowtests:
|
||||||
py.test.skip('skipping slow unit tests - use --runslowtests '
|
py.test.skip('skipping slow unit tests - use --runslowtests '
|
||||||
'to override')
|
'to override')
|
||||||
otherrepo, otherwc = getrepowc('externalrepo', 'externalwc')
|
otherrepo, otherwc = getrepowc('externalrepo', 'externalwc')
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,18 @@ class CommonPathTests(object):
|
||||||
assert self.root.check(notrelto=l)
|
assert self.root.check(notrelto=l)
|
||||||
assert not self.root.check(relto=l)
|
assert not self.root.check(relto=l)
|
||||||
|
|
||||||
|
def test_bestrelpath(self):
|
||||||
|
curdir = self.root
|
||||||
|
sep = curdir.sep
|
||||||
|
s = curdir.bestrelpath(curdir.join("hello", "world"))
|
||||||
|
assert s == "hello" + sep + "world"
|
||||||
|
|
||||||
|
s = curdir.bestrelpath(curdir.dirpath().join("sister"))
|
||||||
|
assert s == ".." + sep + "sister"
|
||||||
|
assert curdir.bestrelpath(curdir.dirpath()) == ".."
|
||||||
|
|
||||||
|
assert curdir.bestrelpath("hello") == "hello"
|
||||||
|
|
||||||
def test_relto_not_relative(self):
|
def test_relto_not_relative(self):
|
||||||
l1=self.root.join("bcde")
|
l1=self.root.join("bcde")
|
||||||
l2=self.root.join("b")
|
l2=self.root.join("b")
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from py.__.rest.rst import *
|
from py.__.rest.rst import *
|
||||||
from py.__.doc.conftest import restcheck
|
from py.__.misc.rest import process as restcheck
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
tempdir = py.test.ensuretemp('rest')
|
tempdir = py.test.ensuretemp('rest')
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,10 @@ def main(args=None):
|
||||||
args = py.std.sys.argv[1:]
|
args = py.std.sys.argv[1:]
|
||||||
config = py.test.config
|
config = py.test.config
|
||||||
config.parse(args)
|
config.parse(args)
|
||||||
|
config.pytestplugins.configure(config)
|
||||||
session = config.initsession()
|
session = config.initsession()
|
||||||
exitstatus = session.main()
|
exitstatus = session.main()
|
||||||
|
config.pytestplugins.unconfigure(config)
|
||||||
raise SystemExit(exitstatus)
|
raise SystemExit(exitstatus)
|
||||||
|
|
||||||
def warn_about_missing_assertion():
|
def warn_about_missing_assertion():
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,6 @@ class SetupState(object):
|
||||||
col = self.stack.pop()
|
col = self.stack.pop()
|
||||||
col.teardown()
|
col.teardown()
|
||||||
for col in needed_collectors[len(self.stack):]:
|
for col in needed_collectors[len(self.stack):]:
|
||||||
#print "setting up", col
|
|
||||||
col.setup()
|
col.setup()
|
||||||
self.stack.append(col)
|
self.stack.append(col)
|
||||||
|
|
||||||
|
|
@ -67,7 +66,7 @@ class ReprMetaInfo(object):
|
||||||
params = self.__dict__.copy()
|
params = self.__dict__.copy()
|
||||||
if self.fspath:
|
if self.fspath:
|
||||||
if basedir is not None:
|
if basedir is not None:
|
||||||
params['fspath'] = getrelpath(basedir, self.fspath)
|
params['fspath'] = basedir.bestrelpath(self.fspath)
|
||||||
if self.lineno is not None:
|
if self.lineno is not None:
|
||||||
params['lineno'] = self.lineno + 1
|
params['lineno'] = self.lineno + 1
|
||||||
|
|
||||||
|
|
@ -108,6 +107,7 @@ class Node(object):
|
||||||
self._config = config
|
self._config = config
|
||||||
self.fspath = getattr(parent, 'fspath', None)
|
self.fspath = getattr(parent, 'fspath', None)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# note to myself: Pickling is uh.
|
# note to myself: Pickling is uh.
|
||||||
#
|
#
|
||||||
|
|
@ -172,15 +172,17 @@ class Node(object):
|
||||||
setattr(self, attrname, res)
|
setattr(self, attrname, res)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def listchain(self):
|
def listchain(self, rootfirst=False):
|
||||||
""" return list of all parent collectors up to self. """
|
""" return list of all parent collectors up to self,
|
||||||
|
starting form root of collection tree. """
|
||||||
l = [self]
|
l = [self]
|
||||||
while 1:
|
while 1:
|
||||||
x = l[-1]
|
x = l[-1]
|
||||||
if x.parent is not None:
|
if x.parent is not None:
|
||||||
l.append(x.parent)
|
l.append(x.parent)
|
||||||
else:
|
else:
|
||||||
l.reverse()
|
if not rootfirst:
|
||||||
|
l.reverse()
|
||||||
return l
|
return l
|
||||||
|
|
||||||
def listnames(self):
|
def listnames(self):
|
||||||
|
|
@ -200,6 +202,53 @@ class Node(object):
|
||||||
cur = next
|
cur = next
|
||||||
return cur
|
return cur
|
||||||
|
|
||||||
|
|
||||||
|
def _getfsnode(self, path):
|
||||||
|
# this method is usually called from
|
||||||
|
# config.getfsnode() which returns a colitem
|
||||||
|
# from filename arguments
|
||||||
|
#
|
||||||
|
# pytest's collector tree does not neccessarily
|
||||||
|
# follow the filesystem and we thus need to do
|
||||||
|
# some special matching code here because
|
||||||
|
# _getitembynames() works by colitem names, not
|
||||||
|
# basenames.
|
||||||
|
if path == self.fspath:
|
||||||
|
return self
|
||||||
|
basenames = path.relto(self.fspath).split(path.sep)
|
||||||
|
cur = self
|
||||||
|
while basenames:
|
||||||
|
basename = basenames.pop(0)
|
||||||
|
assert basename
|
||||||
|
fspath = cur.fspath.join(basename)
|
||||||
|
colitems = cur._memocollect()
|
||||||
|
l = []
|
||||||
|
for colitem in colitems:
|
||||||
|
if colitem.fspath == fspath or colitem.name == basename:
|
||||||
|
l.append(colitem)
|
||||||
|
if not l:
|
||||||
|
msg = ("Collector %r does not provide %r colitem "
|
||||||
|
"existing colitems are: %s" %
|
||||||
|
(cur, fspath, colitems))
|
||||||
|
raise AssertionError(msg)
|
||||||
|
if basenames:
|
||||||
|
if len(l) > 1:
|
||||||
|
msg = ("Collector %r has more than one %r colitem "
|
||||||
|
"existing colitems are: %s" %
|
||||||
|
(cur, fspath, colitems))
|
||||||
|
raise AssertionError(msg)
|
||||||
|
cur = l[0]
|
||||||
|
else:
|
||||||
|
if len(l) > 1:
|
||||||
|
cur = l
|
||||||
|
else:
|
||||||
|
cur = l[0]
|
||||||
|
break
|
||||||
|
return cur
|
||||||
|
|
||||||
|
def readkeywords(self):
|
||||||
|
return dict([(x, True) for x in self._keywords()])
|
||||||
|
|
||||||
def _keywords(self):
|
def _keywords(self):
|
||||||
return [self.name]
|
return [self.name]
|
||||||
|
|
||||||
|
|
@ -284,7 +333,6 @@ class Node(object):
|
||||||
return repr
|
return repr
|
||||||
|
|
||||||
repr_failure = _repr_failure_py
|
repr_failure = _repr_failure_py
|
||||||
|
|
||||||
shortfailurerepr = "F"
|
shortfailurerepr = "F"
|
||||||
|
|
||||||
class Collector(Node):
|
class Collector(Node):
|
||||||
|
|
@ -298,7 +346,7 @@ class Collector(Node):
|
||||||
"""
|
"""
|
||||||
Directory = configproperty('Directory')
|
Directory = configproperty('Directory')
|
||||||
Module = configproperty('Module')
|
Module = configproperty('Module')
|
||||||
DoctestFile = configproperty('DoctestFile')
|
#DoctestFile = configproperty('DoctestFile')
|
||||||
|
|
||||||
def collect(self):
|
def collect(self):
|
||||||
""" returns a list of children (items and collectors)
|
""" returns a list of children (items and collectors)
|
||||||
|
|
@ -407,41 +455,45 @@ class Directory(FSCollector):
|
||||||
return l
|
return l
|
||||||
l = []
|
l = []
|
||||||
for path in self.fspath.listdir(sort=True):
|
for path in self.fspath.listdir(sort=True):
|
||||||
res = self.consider(path, usefilters=True)
|
res = self.consider(path)
|
||||||
if res is not None:
|
if res is not None:
|
||||||
l.append(res)
|
if isinstance(res, (list, tuple)):
|
||||||
|
l.extend(res)
|
||||||
|
else:
|
||||||
|
l.append(res)
|
||||||
return l
|
return l
|
||||||
|
|
||||||
def consider(self, path, usefilters=True):
|
def consider(self, path):
|
||||||
if path.check(file=1):
|
if path.check(file=1):
|
||||||
return self.consider_file(path, usefilters=usefilters)
|
return self.consider_file(path)
|
||||||
elif path.check(dir=1):
|
elif path.check(dir=1):
|
||||||
return self.consider_dir(path, usefilters=usefilters)
|
return self.consider_dir(path)
|
||||||
|
|
||||||
def consider_file(self, path, usefilters=True):
|
def consider_file(self, path):
|
||||||
ext = path.ext
|
res = self._config.pytestplugins.call_each(
|
||||||
pb = path.purebasename
|
'pytest_collect_file', path=path, parent=self)
|
||||||
if not usefilters or pb.startswith("test_") or pb.endswith("_test"):
|
l = []
|
||||||
if ext == ".py":
|
# throw out identical modules
|
||||||
return self.Module(path, parent=self)
|
for x in res:
|
||||||
elif ext == ".txt":
|
if x not in l:
|
||||||
return self.DoctestFile(path, parent=self)
|
l.append(x)
|
||||||
|
return l
|
||||||
|
|
||||||
def consider_dir(self, path, usefilters=True):
|
def consider_dir(self, path, usefilters=None):
|
||||||
if not usefilters or self.recfilter(path):
|
if usefilters is not None:
|
||||||
# not use self.Directory here as
|
APIWARN("0.99", "usefilters argument not needed")
|
||||||
# dir/conftest.py shall be able to
|
if not self.recfilter(path):
|
||||||
# define Directory(dir) already
|
# check if cmdline specified this dir or a subdir
|
||||||
Directory = self._config.getvalue('Directory', path)
|
for arg in self._config.args:
|
||||||
return Directory(path, parent=self)
|
if path == arg or arg.relto(path):
|
||||||
|
break
|
||||||
def collect_by_name(self, name):
|
else:
|
||||||
""" get a child with the given name. """
|
return
|
||||||
res = super(Directory, self).collect_by_name(name)
|
# not use self.Directory here as
|
||||||
if res is None:
|
# dir/conftest.py shall be able to
|
||||||
p = self.fspath.join(name)
|
# define Directory(dir) already
|
||||||
res = self.consider(p, usefilters=False)
|
Directory = self._config.getvalue('Directory', path)
|
||||||
return res
|
return Directory(path, parent=self)
|
||||||
|
|
||||||
from py.__.test.runner import basic_run_report, forked_run_report
|
from py.__.test.runner import basic_run_report, forked_run_report
|
||||||
class Item(Node):
|
class Item(Node):
|
||||||
|
|
@ -479,27 +531,6 @@ class Item(Node):
|
||||||
def runtest(self):
|
def runtest(self):
|
||||||
""" execute this test item."""
|
""" execute this test item."""
|
||||||
|
|
||||||
|
|
||||||
def getrelpath(curdir, dest):
|
|
||||||
try:
|
|
||||||
base = curdir.common(dest)
|
|
||||||
if not base: # can be the case on windows
|
|
||||||
return dest
|
|
||||||
curdir2base = curdir.relto(base)
|
|
||||||
reldest = dest.relto(base)
|
|
||||||
if curdir2base:
|
|
||||||
n = curdir2base.count(curdir.sep) + 1
|
|
||||||
else:
|
|
||||||
n = 0
|
|
||||||
l = ['..'] * n
|
|
||||||
if reldest:
|
|
||||||
l.append(reldest)
|
|
||||||
target = dest.sep.join(l)
|
|
||||||
return target
|
|
||||||
except AttributeError:
|
|
||||||
return dest
|
|
||||||
|
|
||||||
|
|
||||||
def warnoldcollect():
|
def warnoldcollect():
|
||||||
APIWARN("1.0",
|
APIWARN("1.0",
|
||||||
"implement collector.collect() instead of "
|
"implement collector.collect() instead of "
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,9 @@ from __future__ import generators
|
||||||
|
|
||||||
import py
|
import py
|
||||||
from conftesthandle import Conftest
|
from conftesthandle import Conftest
|
||||||
from py.__.test.defaultconftest import adddefaultoptions
|
|
||||||
|
|
||||||
optparse = py.compat.optparse
|
from py.__.test import parseopt
|
||||||
|
from py.__.misc.warn import APIWARN
|
||||||
|
|
||||||
# XXX move to Config class
|
# XXX move to Config class
|
||||||
basetemp = None
|
basetemp = None
|
||||||
|
|
@ -26,14 +26,26 @@ class CmdOptions(object):
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
""" central bus for dealing with configuration/initialization data. """
|
""" central bus for dealing with configuration/initialization data. """
|
||||||
Option = optparse.Option
|
Option = py.compat.optparse.Option # deprecated
|
||||||
_initialized = False
|
_initialized = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, pytestplugins=None):
|
||||||
self.option = CmdOptions()
|
self.option = CmdOptions()
|
||||||
self._parser = optparse.OptionParser(
|
self._parser = parseopt.Parser(
|
||||||
usage="usage: %prog [options] [query] [filenames of tests]")
|
usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
|
||||||
self._conftest = Conftest()
|
processopt=self._processopt,
|
||||||
|
)
|
||||||
|
if pytestplugins is None:
|
||||||
|
pytestplugins = py.test._PytestPlugins()
|
||||||
|
assert isinstance(pytestplugins, py.test._PytestPlugins)
|
||||||
|
self.bus = pytestplugins.pyplugins
|
||||||
|
self.pytestplugins = pytestplugins
|
||||||
|
self._conftest = Conftest(onimport=self.pytestplugins.consider_conftest)
|
||||||
|
|
||||||
|
def _processopt(self, opt):
|
||||||
|
if hasattr(opt, 'default') and opt.dest:
|
||||||
|
if not hasattr(self.option, opt.dest):
|
||||||
|
setattr(self.option, opt.dest, opt.default)
|
||||||
|
|
||||||
def parse(self, args):
|
def parse(self, args):
|
||||||
""" parse cmdline arguments into this config object.
|
""" parse cmdline arguments into this config object.
|
||||||
|
|
@ -42,15 +54,14 @@ class Config(object):
|
||||||
assert not self._initialized, (
|
assert not self._initialized, (
|
||||||
"can only parse cmdline args at most once per Config object")
|
"can only parse cmdline args at most once per Config object")
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
adddefaultoptions(self)
|
|
||||||
self._conftest.setinitial(args)
|
self._conftest.setinitial(args)
|
||||||
args = [str(x) for x in args]
|
self.pytestplugins.consider_env()
|
||||||
cmdlineoption, args = self._parser.parse_args(args)
|
self.pytestplugins.do_addoption(self._parser)
|
||||||
self.option.__dict__.update(vars(cmdlineoption))
|
args = self._parser.parse_setoption(args, self.option)
|
||||||
if not args:
|
if not args:
|
||||||
args.append(py.std.os.getcwd())
|
args.append(py.std.os.getcwd())
|
||||||
self.topdir = gettopdir(args)
|
self.topdir = gettopdir(args)
|
||||||
self.args = args
|
self.args = [py.path.local(x) for x in args]
|
||||||
|
|
||||||
# config objects are usually pickled across system
|
# config objects are usually pickled across system
|
||||||
# barriers but they contain filesystem paths.
|
# barriers but they contain filesystem paths.
|
||||||
|
|
@ -62,11 +73,15 @@ class Config(object):
|
||||||
self._repr = repr
|
self._repr = repr
|
||||||
|
|
||||||
def _initafterpickle(self, topdir):
|
def _initafterpickle(self, topdir):
|
||||||
self.__init__()
|
self.__init__(
|
||||||
|
#issue1
|
||||||
|
#pytestplugins=py.test._PytestPlugins(py._com.pyplugins)
|
||||||
|
)
|
||||||
self._initialized = True
|
self._initialized = True
|
||||||
self.topdir = py.path.local(topdir)
|
self.topdir = py.path.local(topdir)
|
||||||
self._mergerepr(self._repr)
|
self._mergerepr(self._repr)
|
||||||
del self._repr
|
del self._repr
|
||||||
|
self.pytestplugins.configure(config=self)
|
||||||
|
|
||||||
def _makerepr(self):
|
def _makerepr(self):
|
||||||
l = []
|
l = []
|
||||||
|
|
@ -87,6 +102,9 @@ class Config(object):
|
||||||
self.option = cmdlineopts
|
self.option = cmdlineopts
|
||||||
self._conftest.setinitial(self.args)
|
self._conftest.setinitial(self.args)
|
||||||
|
|
||||||
|
def getcolitems(self):
|
||||||
|
return [self.getfsnode(arg) for arg in self.args]
|
||||||
|
|
||||||
def getfsnode(self, path):
|
def getfsnode(self, path):
|
||||||
path = py.path.local(path)
|
path = py.path.local(path)
|
||||||
assert path.check(), "%s: path does not exist" %(path,)
|
assert path.check(), "%s: path does not exist" %(path,)
|
||||||
|
|
@ -96,8 +114,7 @@ class Config(object):
|
||||||
pkgpath = path.check(file=1) and path.dirpath() or path
|
pkgpath = path.check(file=1) and path.dirpath() or path
|
||||||
Dir = self._conftest.rget("Directory", pkgpath)
|
Dir = self._conftest.rget("Directory", pkgpath)
|
||||||
col = Dir(pkgpath, config=self)
|
col = Dir(pkgpath, config=self)
|
||||||
names = path.relto(col.fspath).split(path.sep)
|
return col._getfsnode(path)
|
||||||
return col._getitembynames(names)
|
|
||||||
|
|
||||||
def getvalue_pathlist(self, name, path=None):
|
def getvalue_pathlist(self, name, path=None):
|
||||||
""" return a matching value, which needs to be sequence
|
""" return a matching value, which needs to be sequence
|
||||||
|
|
@ -119,25 +136,15 @@ class Config(object):
|
||||||
""" add a named group of options to the current testing session.
|
""" add a named group of options to the current testing session.
|
||||||
This function gets invoked during testing session initialization.
|
This function gets invoked during testing session initialization.
|
||||||
"""
|
"""
|
||||||
for spec in specs:
|
APIWARN("1.0", "define plugins to add options", stacklevel=2)
|
||||||
for shortopt in spec._short_opts:
|
group = self._parser.addgroup(groupname)
|
||||||
if not shortopt.isupper():
|
|
||||||
raise ValueError(
|
|
||||||
"custom options must be capital letter "
|
|
||||||
"got %r" %(spec,)
|
|
||||||
)
|
|
||||||
return self._addoptions(groupname, *specs)
|
|
||||||
|
|
||||||
def _addoptions(self, groupname, *specs):
|
|
||||||
optgroup = optparse.OptionGroup(self._parser, groupname)
|
|
||||||
optgroup.add_options(specs)
|
|
||||||
self._parser.add_option_group(optgroup)
|
|
||||||
for opt in specs:
|
for opt in specs:
|
||||||
if hasattr(opt, 'default') and opt.dest:
|
group._addoption_instance(opt)
|
||||||
if not hasattr(self.option, opt.dest):
|
|
||||||
setattr(self.option, opt.dest, opt.default)
|
|
||||||
return self.option
|
return self.option
|
||||||
|
|
||||||
|
def addoption(self, *optnames, **attrs):
|
||||||
|
return self._parser.addoption(*optnames, **attrs)
|
||||||
|
|
||||||
def getvalue(self, name, path=None):
|
def getvalue(self, name, path=None):
|
||||||
""" return 'name' value looked up from the 'options'
|
""" return 'name' value looked up from the 'options'
|
||||||
and then from the first conftest file found up
|
and then from the first conftest file found up
|
||||||
|
|
@ -150,30 +157,21 @@ class Config(object):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
return self._conftest.rget(name, path)
|
return self._conftest.rget(name, path)
|
||||||
|
|
||||||
def initreporter(self, bus):
|
|
||||||
if self.option.collectonly:
|
|
||||||
from py.__.test.report.collectonly import Reporter
|
|
||||||
else:
|
|
||||||
from py.__.test.report.terminal import Reporter
|
|
||||||
rep = Reporter(self, bus=bus)
|
|
||||||
return rep
|
|
||||||
|
|
||||||
def initsession(self):
|
def initsession(self):
|
||||||
""" return an initialized session object. """
|
""" return an initialized session object. """
|
||||||
cls = self._getsessionclass()
|
cls = self._getestdirclass()
|
||||||
session = cls(self)
|
session = cls(self)
|
||||||
session.fixoptions()
|
session.fixoptions()
|
||||||
session.reporter = self.initreporter(session.bus)
|
|
||||||
return session
|
return session
|
||||||
|
|
||||||
def _getsessionclass(self):
|
def _getestdirclass(self):
|
||||||
""" return Session class determined from cmdline options
|
""" return Session class determined from cmdline options
|
||||||
and looked up in initial config modules.
|
and looked up in initial config modules.
|
||||||
"""
|
"""
|
||||||
if self.option.session is not None:
|
if self.option.session is not None:
|
||||||
return self._conftest.rget(self.option.session)
|
return self._conftest.rget(self.option.session)
|
||||||
else:
|
else:
|
||||||
name = self._getsessionname()
|
name = self._getestdirname()
|
||||||
try:
|
try:
|
||||||
return self._conftest.rget(name)
|
return self._conftest.rget(name)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|
@ -182,7 +180,7 @@ class Config(object):
|
||||||
mod = __import__(importpath, None, None, '__doc__')
|
mod = __import__(importpath, None, None, '__doc__')
|
||||||
return getattr(mod, name)
|
return getattr(mod, name)
|
||||||
|
|
||||||
def _getsessionname(self):
|
def _getestdirname(self):
|
||||||
""" return default session name as determined from options. """
|
""" return default session name as determined from options. """
|
||||||
if self.option.collectonly:
|
if self.option.collectonly:
|
||||||
name = 'Session'
|
name = 'Session'
|
||||||
|
|
@ -221,42 +219,10 @@ class Config(object):
|
||||||
raise ValueError("unknown io capturing: " + iocapture)
|
raise ValueError("unknown io capturing: " + iocapture)
|
||||||
|
|
||||||
|
|
||||||
def gettracedir(self):
|
|
||||||
""" return a tracedirectory or None, depending on --tracedir. """
|
|
||||||
if self.option.tracedir is not None:
|
|
||||||
return py.path.local(self.option.tracedir)
|
|
||||||
|
|
||||||
def maketrace(self, name, flush=True):
|
|
||||||
""" return a tracedirectory or None, depending on --tracedir. """
|
|
||||||
tracedir = self.gettracedir()
|
|
||||||
if tracedir is None:
|
|
||||||
return NullTracer()
|
|
||||||
tracedir.ensure(dir=1)
|
|
||||||
return Tracer(tracedir.join(name), flush=flush)
|
|
||||||
|
|
||||||
class Tracer(object):
|
|
||||||
file = None
|
|
||||||
def __init__(self, path, flush=True):
|
|
||||||
self.file = path.open(mode='w')
|
|
||||||
self.flush = flush
|
|
||||||
|
|
||||||
def __call__(self, *args):
|
|
||||||
time = round(py.std.time.time(), 3)
|
|
||||||
print >>self.file, time, " ".join(map(str, args))
|
|
||||||
if self.flush:
|
|
||||||
self.file.flush()
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.file.close()
|
|
||||||
|
|
||||||
class NullTracer:
|
|
||||||
def __call__(self, *args):
|
|
||||||
pass
|
|
||||||
def close(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
# this is the one per-process instance of py.test configuration
|
# this is the one per-process instance of py.test configuration
|
||||||
config_per_process = Config()
|
config_per_process = Config(
|
||||||
|
pytestplugins=py.test._PytestPlugins(py._com.pyplugins)
|
||||||
|
)
|
||||||
|
|
||||||
# default import paths for sessions
|
# default import paths for sessions
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,9 @@ class Conftest(object):
|
||||||
conftest.py files may result in added cmdline options.
|
conftest.py files may result in added cmdline options.
|
||||||
XXX
|
XXX
|
||||||
"""
|
"""
|
||||||
def __init__(self, path=None):
|
def __init__(self, path=None, onimport=None):
|
||||||
self._path2confmods = {}
|
self._path2confmods = {}
|
||||||
|
self._onimport = onimport
|
||||||
if path is not None:
|
if path is not None:
|
||||||
self.setinitial([path])
|
self.setinitial([path])
|
||||||
|
|
||||||
|
|
@ -37,11 +38,11 @@ class Conftest(object):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
dp = path.dirpath()
|
dp = path.dirpath()
|
||||||
if dp == path:
|
if dp == path:
|
||||||
return [importconfig(defaultconftestpath)]
|
return [self.importconftest(defaultconftestpath)]
|
||||||
clist = self.getconftestmodules(dp)
|
clist = self.getconftestmodules(dp)
|
||||||
conftestpath = path.join("conftest.py")
|
conftestpath = path.join("conftest.py")
|
||||||
if conftestpath.check(file=1):
|
if conftestpath.check(file=1):
|
||||||
clist.append(importconfig(conftestpath))
|
clist.append(self.importconftest(conftestpath))
|
||||||
self._path2confmods[path] = clist
|
self._path2confmods[path] = clist
|
||||||
# be defensive: avoid changes from caller side to
|
# be defensive: avoid changes from caller side to
|
||||||
# affect us by always returning a copy of the actual list
|
# affect us by always returning a copy of the actual list
|
||||||
|
|
@ -61,15 +62,17 @@ class Conftest(object):
|
||||||
continue
|
continue
|
||||||
raise KeyError, name
|
raise KeyError, name
|
||||||
|
|
||||||
def importconfig(configpath):
|
def importconftest(self, conftestpath):
|
||||||
# We could have used caching here, but it's redundant since
|
# Using caching here looks redundant since ultimately
|
||||||
# they're cached on path anyway, so we use it only when doing rget_path
|
# sys.modules caches already
|
||||||
assert configpath.check(), configpath
|
assert conftestpath.check(), conftestpath
|
||||||
if not configpath.dirpath('__init__.py').check(file=1):
|
if not conftestpath.dirpath('__init__.py').check(file=1):
|
||||||
# HACK: we don't want any "globally" imported conftest.py,
|
# HACK: we don't want any "globally" imported conftest.py,
|
||||||
# prone to conflicts and subtle problems
|
# prone to conflicts and subtle problems
|
||||||
modname = str(configpath).replace('.', configpath.sep)
|
modname = str(conftestpath).replace('.', conftestpath.sep)
|
||||||
mod = configpath.pyimport(modname=modname)
|
mod = conftestpath.pyimport(modname=modname)
|
||||||
else:
|
else:
|
||||||
mod = configpath.pyimport()
|
mod = conftestpath.pyimport()
|
||||||
return mod
|
if self._onimport:
|
||||||
|
self._onimport(mod)
|
||||||
|
return mod
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import py
|
import py
|
||||||
|
|
||||||
Module = py.test.collect.Module
|
Module = py.test.collect.Module
|
||||||
DoctestFile = py.test.collect.DoctestFile
|
#DoctestFile = py.test.collect.DoctestFile
|
||||||
Directory = py.test.collect.Directory
|
Directory = py.test.collect.Directory
|
||||||
Class = py.test.collect.Class
|
Class = py.test.collect.Class
|
||||||
Generator = py.test.collect.Generator
|
Generator = py.test.collect.Generator
|
||||||
|
|
@ -10,13 +10,16 @@ Instance = py.test.collect.Instance
|
||||||
|
|
||||||
conf_iocapture = "fd" # overridable from conftest.py
|
conf_iocapture = "fd" # overridable from conftest.py
|
||||||
|
|
||||||
|
# XXX resultlog should go, pypy's nightrun depends on it
|
||||||
|
pytest_plugins = "default terminal xfail tmpdir resultlog monkeypatch".split()
|
||||||
|
|
||||||
# ===================================================
|
# ===================================================
|
||||||
# Distributed testing specific options
|
# Distributed testing specific options
|
||||||
|
|
||||||
#dist_hosts: needs to be provided by user
|
#dist_hosts: needs to be provided by user
|
||||||
#dist_rsync_roots: might be provided by user, if not present or None,
|
#dist_rsync_roots: might be provided by user, if not present or None,
|
||||||
# whole pkgdir will be rsynced
|
# whole pkgdir will be rsynced
|
||||||
# XXX deprecated dist_remotepython = None
|
|
||||||
dist_taskspernode = 15
|
dist_taskspernode = 15
|
||||||
dist_boxed = False
|
dist_boxed = False
|
||||||
if hasattr(py.std.os, 'nice'):
|
if hasattr(py.std.os, 'nice'):
|
||||||
|
|
@ -25,97 +28,3 @@ else:
|
||||||
dist_nicelevel = 0
|
dist_nicelevel = 0
|
||||||
dist_rsync_ignore = []
|
dist_rsync_ignore = []
|
||||||
|
|
||||||
# ===================================================
|
|
||||||
|
|
||||||
def adddefaultoptions(config):
|
|
||||||
Option = config.Option
|
|
||||||
config._addoptions('general options',
|
|
||||||
Option('-v', '--verbose',
|
|
||||||
action="count", dest="verbose", default=0,
|
|
||||||
help="increase verbosity."),
|
|
||||||
Option('-x', '--exitfirst',
|
|
||||||
action="store_true", dest="exitfirst", default=False,
|
|
||||||
help="exit instantly on first error or failed test."),
|
|
||||||
Option('-s', '--nocapture',
|
|
||||||
action="store_true", dest="nocapture", default=False,
|
|
||||||
help="disable catching of sys.stdout/stderr output."),
|
|
||||||
Option('-k',
|
|
||||||
action="store", dest="keyword", default='',
|
|
||||||
help="only run test items matching the given "
|
|
||||||
"space separated keywords. precede a keyword with '-' to negate. "
|
|
||||||
"Terminate the expression with ':' to treat a match as a signal to run all subsequent tests. "
|
|
||||||
),
|
|
||||||
Option('-l', '--showlocals',
|
|
||||||
action="store_true", dest="showlocals", default=False,
|
|
||||||
help="show locals in tracebacks (disabled by default)."),
|
|
||||||
Option('--showskipsummary',
|
|
||||||
action="store_true", dest="showskipsummary", default=False,
|
|
||||||
help="always show summary of skipped tests"),
|
|
||||||
Option('', '--pdb',
|
|
||||||
action="store_true", dest="usepdb", default=False,
|
|
||||||
help="start pdb (the Python debugger) on errors."),
|
|
||||||
Option('', '--eventlog',
|
|
||||||
action="store", dest="eventlog", default=None,
|
|
||||||
help="write reporting events to given file."),
|
|
||||||
Option('', '--tracedir',
|
|
||||||
action="store", dest="tracedir", default=None,
|
|
||||||
help="write tracing information to the given directory."),
|
|
||||||
Option('', '--tb',
|
|
||||||
action="store", dest="tbstyle", default='long',
|
|
||||||
type="choice", choices=['long', 'short', 'no'],
|
|
||||||
help="traceback verboseness (long/short/no)."),
|
|
||||||
Option('', '--fulltrace',
|
|
||||||
action="store_true", dest="fulltrace", default=False,
|
|
||||||
help="don't cut any tracebacks (default is to cut)."),
|
|
||||||
Option('', '--nomagic',
|
|
||||||
action="store_true", dest="nomagic", default=False,
|
|
||||||
help="refrain from using magic as much as possible."),
|
|
||||||
Option('', '--collectonly',
|
|
||||||
action="store_true", dest="collectonly", default=False,
|
|
||||||
help="only collect tests, don't execute them."),
|
|
||||||
Option('', '--traceconfig',
|
|
||||||
action="store_true", dest="traceconfig", default=False,
|
|
||||||
help="trace considerations of conftest.py files."),
|
|
||||||
Option('-f', '--looponfailing',
|
|
||||||
action="store_true", dest="looponfailing", default=False,
|
|
||||||
help="loop on failing test set."),
|
|
||||||
Option('', '--exec',
|
|
||||||
action="store", dest="executable", default=None,
|
|
||||||
help="python executable to run the tests with."),
|
|
||||||
Option('-n', '--numprocesses', dest="numprocesses", default=0,
|
|
||||||
action="store", type="int",
|
|
||||||
help="number of local test processes."),
|
|
||||||
Option('', '--debug',
|
|
||||||
action="store_true", dest="debug", default=False,
|
|
||||||
help="turn on debugging information."),
|
|
||||||
)
|
|
||||||
|
|
||||||
config._addoptions('EXPERIMENTAL options',
|
|
||||||
Option('-d', '--dist',
|
|
||||||
action="store_true", dest="dist", default=False,
|
|
||||||
help="ad-hoc distribute tests across machines (requires conftest settings)"),
|
|
||||||
Option('-w', '--startserver',
|
|
||||||
action="store_true", dest="startserver", default=False,
|
|
||||||
help="starts local web server for displaying test progress.",
|
|
||||||
),
|
|
||||||
Option('-r', '--runbrowser',
|
|
||||||
action="store_true", dest="runbrowser", default=False,
|
|
||||||
help="run browser (implies --startserver)."
|
|
||||||
),
|
|
||||||
Option('', '--boxed',
|
|
||||||
action="store_true", dest="boxed", default=False,
|
|
||||||
help="box each test run in a separate process"),
|
|
||||||
Option('', '--rest',
|
|
||||||
action='store_true', dest="restreport", default=False,
|
|
||||||
help="restructured text output reporting."),
|
|
||||||
Option('', '--apigen',
|
|
||||||
action="store", dest="apigen",
|
|
||||||
help="generate api documentation while testing (requires "
|
|
||||||
"argument pointing to a script)."),
|
|
||||||
Option('', '--session',
|
|
||||||
action="store", dest="session", default=None,
|
|
||||||
help="lookup given sessioname in conftest.py files and use it."),
|
|
||||||
Option('--resultlog', action="store",
|
|
||||||
default=None, dest="resultlog",
|
|
||||||
help="path for machine-readable result log")
|
|
||||||
)
|
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,10 @@ import py
|
||||||
from py.__.test import event
|
from py.__.test import event
|
||||||
import py.__.test.custompdb
|
import py.__.test.custompdb
|
||||||
from py.__.test.dsession.hostmanage import HostManager
|
from py.__.test.dsession.hostmanage import HostManager
|
||||||
Item = (py.test.collect.Item, py.test.collect.Item)
|
Item = py.test.collect.Item
|
||||||
Collector = (py.test.collect.Collector, py.test.collect.Collector)
|
Collector = py.test.collect.Collector
|
||||||
from py.__.test.runner import basic_run_report, basic_collect_report
|
from py.__.test.runner import basic_run_report, basic_collect_report
|
||||||
from py.__.test.session import Session
|
from py.__.test.session import Session
|
||||||
from py.__.test.runner import OutcomeRepr
|
|
||||||
from py.__.test import outcome
|
from py.__.test import outcome
|
||||||
|
|
||||||
import Queue
|
import Queue
|
||||||
|
|
@ -42,7 +41,6 @@ class DSession(Session):
|
||||||
self.host2pending = {}
|
self.host2pending = {}
|
||||||
self.item2host = {}
|
self.item2host = {}
|
||||||
self._testsfailed = False
|
self._testsfailed = False
|
||||||
self.trace = config.maketrace("dsession.log")
|
|
||||||
|
|
||||||
def fixoptions(self):
|
def fixoptions(self):
|
||||||
""" check, fix and determine conflicting options. """
|
""" check, fix and determine conflicting options. """
|
||||||
|
|
@ -79,11 +77,13 @@ class DSession(Session):
|
||||||
|
|
||||||
def main(self, colitems=None):
|
def main(self, colitems=None):
|
||||||
colitems = self.getinitialitems(colitems)
|
colitems = self.getinitialitems(colitems)
|
||||||
self.bus.notify(event.TestrunStart())
|
#self.bus.notify(event.TestrunStart())
|
||||||
|
self.sessionstarts()
|
||||||
self.setup_hosts()
|
self.setup_hosts()
|
||||||
exitstatus = self.loop(colitems)
|
exitstatus = self.loop(colitems)
|
||||||
self.bus.notify(event.TestrunFinish(exitstatus=exitstatus))
|
#self.bus.notify(event.TestrunFinish(exitstatus=exitstatus))
|
||||||
self.teardown_hosts()
|
self.teardown_hosts()
|
||||||
|
self.sessionfinishes()
|
||||||
return exitstatus
|
return exitstatus
|
||||||
|
|
||||||
def loop_once(self, loopstate):
|
def loop_once(self, loopstate):
|
||||||
|
|
@ -96,28 +96,34 @@ class DSession(Session):
|
||||||
# we use a timeout here so that control-C gets through
|
# we use a timeout here so that control-C gets through
|
||||||
while 1:
|
while 1:
|
||||||
try:
|
try:
|
||||||
ev = self.queue.get(timeout=2.0)
|
eventcall = self.queue.get(timeout=2.0)
|
||||||
break
|
break
|
||||||
except Queue.Empty:
|
except Queue.Empty:
|
||||||
continue
|
continue
|
||||||
loopstate.dowork = True
|
loopstate.dowork = True
|
||||||
self.bus.notify(ev)
|
|
||||||
if isinstance(ev, event.ItemTestReport):
|
eventname, args, kwargs = eventcall
|
||||||
|
self.bus.notify(eventname, *args, **kwargs)
|
||||||
|
if args:
|
||||||
|
ev, = args
|
||||||
|
else:
|
||||||
|
ev = None
|
||||||
|
if eventname == "itemtestreport":
|
||||||
self.removeitem(ev.colitem)
|
self.removeitem(ev.colitem)
|
||||||
if ev.failed:
|
if ev.failed:
|
||||||
loopstate.testsfailed = True
|
loopstate.testsfailed = True
|
||||||
elif isinstance(ev, event.CollectionReport):
|
elif eventname == "collectionreport":
|
||||||
if ev.passed:
|
if ev.passed:
|
||||||
colitems.extend(ev.result)
|
colitems.extend(ev.result)
|
||||||
elif isinstance(ev, event.HostUp):
|
elif eventname == "hostup":
|
||||||
self.addhost(ev.host)
|
self.addhost(ev.host)
|
||||||
elif isinstance(ev, event.HostDown):
|
elif eventname == "hostdown":
|
||||||
pending = self.removehost(ev.host)
|
pending = self.removehost(ev.host)
|
||||||
if pending:
|
if pending:
|
||||||
crashitem = pending.pop(0)
|
crashitem = pending.pop(0)
|
||||||
self.handle_crashitem(crashitem, ev.host)
|
self.handle_crashitem(crashitem, ev.host)
|
||||||
colitems.extend(pending)
|
colitems.extend(pending)
|
||||||
elif isinstance(ev, event.RescheduleItems):
|
elif eventname == "rescheduleitems":
|
||||||
colitems.extend(ev.items)
|
colitems.extend(ev.items)
|
||||||
loopstate.dowork = False # avoid busywait
|
loopstate.dowork = False # avoid busywait
|
||||||
|
|
||||||
|
|
@ -132,9 +138,10 @@ class DSession(Session):
|
||||||
def loop_once_shutdown(self, loopstate):
|
def loop_once_shutdown(self, loopstate):
|
||||||
# once we are in shutdown mode we dont send
|
# once we are in shutdown mode we dont send
|
||||||
# events other than HostDown upstream
|
# events other than HostDown upstream
|
||||||
ev = self.queue.get()
|
eventname, args, kwargs = self.queue.get()
|
||||||
if isinstance(ev, event.HostDown):
|
if eventname == "hostdown":
|
||||||
self.bus.notify(ev)
|
ev, = args
|
||||||
|
self.bus.notify("hostdown", ev)
|
||||||
self.removehost(ev.host)
|
self.removehost(ev.host)
|
||||||
if not self.host2pending:
|
if not self.host2pending:
|
||||||
# finished
|
# finished
|
||||||
|
|
@ -155,7 +162,7 @@ class DSession(Session):
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
exitstatus = outcome.EXIT_INTERRUPTED
|
exitstatus = outcome.EXIT_INTERRUPTED
|
||||||
except:
|
except:
|
||||||
self.bus.notify(event.InternalException())
|
self.bus.notify("internalerror", event.InternalException())
|
||||||
exitstatus = outcome.EXIT_INTERNALERROR
|
exitstatus = outcome.EXIT_INTERNALERROR
|
||||||
if exitstatus == 0 and self._testsfailed:
|
if exitstatus == 0 and self._testsfailed:
|
||||||
exitstatus = outcome.EXIT_TESTSFAILED
|
exitstatus = outcome.EXIT_TESTSFAILED
|
||||||
|
|
@ -173,7 +180,6 @@ class DSession(Session):
|
||||||
pending = self.host2pending.pop(host)
|
pending = self.host2pending.pop(host)
|
||||||
for item in pending:
|
for item in pending:
|
||||||
del self.item2host[item]
|
del self.item2host[item]
|
||||||
self.trace("removehost %s, pending=%r" %(host.hostid, pending))
|
|
||||||
return pending
|
return pending
|
||||||
|
|
||||||
def triggertesting(self, colitems):
|
def triggertesting(self, colitems):
|
||||||
|
|
@ -183,11 +189,13 @@ class DSession(Session):
|
||||||
if isinstance(next, Item):
|
if isinstance(next, Item):
|
||||||
senditems.append(next)
|
senditems.append(next)
|
||||||
else:
|
else:
|
||||||
ev = basic_collect_report(next)
|
self.bus.notify("collectionstart", event.CollectionStart(next))
|
||||||
self.bus.notify(event.CollectionStart(next))
|
self.queueevent("collectionreport", basic_collect_report(next))
|
||||||
self.queue.put(ev)
|
|
||||||
self.senditems(senditems)
|
self.senditems(senditems)
|
||||||
|
|
||||||
|
def queueevent(self, eventname, *args, **kwargs):
|
||||||
|
self.queue.put((eventname, args, kwargs))
|
||||||
|
|
||||||
def senditems(self, tosend):
|
def senditems(self, tosend):
|
||||||
if not tosend:
|
if not tosend:
|
||||||
return
|
return
|
||||||
|
|
@ -196,38 +204,36 @@ class DSession(Session):
|
||||||
if room > 0:
|
if room > 0:
|
||||||
sending = tosend[:room]
|
sending = tosend[:room]
|
||||||
host.node.sendlist(sending)
|
host.node.sendlist(sending)
|
||||||
self.trace("sent to host %s: %r" %(host.hostid, sending))
|
|
||||||
for item in sending:
|
for item in sending:
|
||||||
#assert item not in self.item2host, (
|
#assert item not in self.item2host, (
|
||||||
# "sending same item %r to multiple hosts "
|
# "sending same item %r to multiple hosts "
|
||||||
# "not implemented" %(item,))
|
# "not implemented" %(item,))
|
||||||
self.item2host[item] = host
|
self.item2host[item] = host
|
||||||
self.bus.notify(event.ItemStart(item, host))
|
self.bus.notify("itemstart", event.ItemStart(item, host))
|
||||||
pending.extend(sending)
|
pending.extend(sending)
|
||||||
tosend[:] = tosend[room:] # update inplace
|
tosend[:] = tosend[room:] # update inplace
|
||||||
if not tosend:
|
if not tosend:
|
||||||
break
|
break
|
||||||
if tosend:
|
if tosend:
|
||||||
# we have some left, give it to the main loop
|
# we have some left, give it to the main loop
|
||||||
self.queue.put(event.RescheduleItems(tosend))
|
self.queueevent("rescheduleitems", event.RescheduleItems(tosend))
|
||||||
|
|
||||||
def removeitem(self, item):
|
def removeitem(self, item):
|
||||||
if item not in self.item2host:
|
if item not in self.item2host:
|
||||||
raise AssertionError(item, self.item2host)
|
raise AssertionError(item, self.item2host)
|
||||||
host = self.item2host.pop(item)
|
host = self.item2host.pop(item)
|
||||||
self.host2pending[host].remove(item)
|
self.host2pending[host].remove(item)
|
||||||
self.trace("removed %r, host=%r" %(item,host.hostid))
|
#self.config.bus.notify("removeitem", item, host.hostid)
|
||||||
|
|
||||||
def handle_crashitem(self, item, host):
|
def handle_crashitem(self, item, host):
|
||||||
longrepr = "%r CRASHED THE HOST %r" %(item, host)
|
longrepr = "!!! Host %r crashed during running of test %r" %(host, item)
|
||||||
outcome = OutcomeRepr(when="execute", shortrepr="c", longrepr=longrepr)
|
rep = event.ItemTestReport(item, when="???", excinfo=longrepr)
|
||||||
rep = event.ItemTestReport(item, failed=outcome)
|
self.bus.notify("itemtestreport", rep)
|
||||||
self.bus.notify(rep)
|
|
||||||
|
|
||||||
def setup_hosts(self):
|
def setup_hosts(self):
|
||||||
""" setup any neccessary resources ahead of the test run. """
|
""" setup any neccessary resources ahead of the test run. """
|
||||||
self.hm = HostManager(self)
|
self.hm = HostManager(self)
|
||||||
self.hm.setup_hosts(notify=self.queue.put)
|
self.hm.setup_hosts(putevent=self.queue.put)
|
||||||
|
|
||||||
def teardown_hosts(self):
|
def teardown_hosts(self):
|
||||||
""" teardown any resources after a test run. """
|
""" teardown any resources after a test run. """
|
||||||
|
|
|
||||||
|
|
@ -148,7 +148,7 @@ class HostManager(object):
|
||||||
def prepare_gateways(self):
|
def prepare_gateways(self):
|
||||||
for host in self.hosts:
|
for host in self.hosts:
|
||||||
host.initgateway()
|
host.initgateway()
|
||||||
self.session.bus.notify(event.HostGatewayReady(host, self.roots))
|
self.session.bus.notify("hostgatewayready", event.HostGatewayReady(host, self.roots))
|
||||||
|
|
||||||
def init_rsync(self):
|
def init_rsync(self):
|
||||||
self.prepare_gateways()
|
self.prepare_gateways()
|
||||||
|
|
@ -164,16 +164,14 @@ class HostManager(object):
|
||||||
for host in self.hosts:
|
for host in self.hosts:
|
||||||
rsync.add_target_host(host, destrelpath)
|
rsync.add_target_host(host, destrelpath)
|
||||||
rsync.send(raises=False)
|
rsync.send(raises=False)
|
||||||
self.session.bus.notify(event.RsyncFinished())
|
self.session.bus.notify("rsyncfinished", event.RsyncFinished())
|
||||||
|
|
||||||
def setup_hosts(self, notify=None):
|
def setup_hosts(self, putevent):
|
||||||
if notify is None:
|
|
||||||
notify = self.session.bus.notify
|
|
||||||
self.init_rsync()
|
self.init_rsync()
|
||||||
for host in self.hosts:
|
for host in self.hosts:
|
||||||
host.node = MasterNode(host,
|
host.node = MasterNode(host,
|
||||||
self.session.config,
|
self.session.config,
|
||||||
notify)
|
putevent)
|
||||||
|
|
||||||
#
|
#
|
||||||
# helpers
|
# helpers
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,19 @@ from py.__.test.dsession.mypickle import PickleChannel
|
||||||
class MasterNode(object):
|
class MasterNode(object):
|
||||||
ENDMARK = -1
|
ENDMARK = -1
|
||||||
|
|
||||||
def __init__(self, host, config, notify):
|
def __init__(self, host, config, putevent):
|
||||||
self.host = host
|
self.host = host
|
||||||
self.config = config
|
self.config = config
|
||||||
self.notify = notify
|
self.putevent = putevent
|
||||||
self.channel = install_slave(host, config)
|
self.channel = install_slave(host, config)
|
||||||
self.channel.setcallback(self.callback, endmarker=self.ENDMARK)
|
self.channel.setcallback(self.callback, endmarker=self.ENDMARK)
|
||||||
self._down = False
|
self._down = False
|
||||||
|
|
||||||
def callback(self, ev):
|
def notify(self, eventname, *args, **kwargs):
|
||||||
""" this gets called for each item we receive from
|
self.putevent((eventname, args, kwargs))
|
||||||
|
|
||||||
|
def callback(self, eventcall):
|
||||||
|
""" this gets called for each object we receive from
|
||||||
the other side and if the channel closes.
|
the other side and if the channel closes.
|
||||||
|
|
||||||
Note that the callback runs in the receiver
|
Note that the callback runs in the receiver
|
||||||
|
|
@ -25,24 +28,27 @@ class MasterNode(object):
|
||||||
avoid raising exceptions or doing heavy work.
|
avoid raising exceptions or doing heavy work.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
if ev == self.ENDMARK:
|
if eventcall == self.ENDMARK:
|
||||||
err = self.channel._getremoteerror()
|
err = self.channel._getremoteerror()
|
||||||
if not self._down:
|
if not self._down:
|
||||||
if not err:
|
if not err:
|
||||||
err = "TERMINATED"
|
err = "TERMINATED"
|
||||||
self.notify(event.HostDown(self.host, err))
|
self.notify("hostdown", event.HostDown(self.host, err))
|
||||||
return
|
return
|
||||||
if ev is None:
|
elif eventcall is None:
|
||||||
self._down = True
|
self._down = True
|
||||||
self.notify(event.HostDown(self.host, None))
|
self.notify("hostdown", event.HostDown(self.host, None))
|
||||||
return
|
return
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
excinfo = py.code.ExceptionInfo()
|
excinfo = py.code.ExceptionInfo()
|
||||||
print "!" * 20, excinfo
|
print "!" * 20, excinfo
|
||||||
ev = event.InternalException(excinfo)
|
self.notify("internalerror", event.InternalException(excinfo))
|
||||||
self.notify(ev)
|
else:
|
||||||
|
# XXX we need to have the proper event name
|
||||||
|
eventname, args, kwargs = eventcall
|
||||||
|
self.notify(eventname, *args, **kwargs)
|
||||||
|
|
||||||
def send(self, item):
|
def send(self, item):
|
||||||
assert item is not None
|
assert item is not None
|
||||||
|
|
@ -101,24 +107,22 @@ class SlaveNode(object):
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
host = getattr(self, 'host', '<uninitialized>')
|
host = getattr(self, 'host', '<uninitialized>')
|
||||||
return "<%s host=%s>" %(self.__class__.__name__, host.hostid)
|
return "<%s host=%s>" %(self.__class__.__name__, host)
|
||||||
|
|
||||||
|
def sendevent(self, eventname, *args, **kwargs):
|
||||||
|
self.channel.send((eventname, args, kwargs))
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
from py.__.test.dsession.hostmanage import makehostup
|
from py.__.test.dsession.hostmanage import makehostup
|
||||||
channel = self.channel
|
channel = self.channel
|
||||||
self.host = host = channel.receive()
|
self.host = host = channel.receive()
|
||||||
channel.send(makehostup(host))
|
self.sendevent("hostup", makehostup(host))
|
||||||
self.trace = self.config.maketrace(host.hostid)
|
|
||||||
self.trace("initialized")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while 1:
|
while 1:
|
||||||
task = channel.receive()
|
task = channel.receive()
|
||||||
self.trace("received", task)
|
self.config.bus.notify("masterslave_receivedtask", task)
|
||||||
|
|
||||||
if task is None: # shutdown
|
if task is None: # shutdown
|
||||||
channel.send(None)
|
self.channel.send(None)
|
||||||
self.trace("shutting down, send None to", channel)
|
|
||||||
break
|
break
|
||||||
if isinstance(task, list):
|
if isinstance(task, list):
|
||||||
for item in task:
|
for item in task:
|
||||||
|
|
@ -128,15 +132,10 @@ class SlaveNode(object):
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
rep = event.InternalException()
|
self.sendevent("internalerror", event.InternalException())
|
||||||
self.trace("sending back internal exception report, breaking loop")
|
|
||||||
channel.send(rep)
|
|
||||||
raise
|
raise
|
||||||
else:
|
|
||||||
self.trace("normal shutdown")
|
|
||||||
|
|
||||||
def runtest(self, item):
|
def runtest(self, item):
|
||||||
runner = item._getrunner()
|
runner = item._getrunner()
|
||||||
testrep = runner(item)
|
testrep = runner(item)
|
||||||
self.channel.send(testrep)
|
self.sendevent("itemtestreport", testrep)
|
||||||
self.trace("sent back testreport", testrep)
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
from py.__.test.testing.suptest import InlineCollection
|
|
||||||
from py.__.test.dsession.dsession import DSession, LoopState
|
from py.__.test.dsession.dsession import DSession, LoopState
|
||||||
from py.__.test.dsession.hostmanage import Host, makehostup
|
from py.__.test.dsession.hostmanage import Host, makehostup
|
||||||
from py.__.test.runner import basic_collect_report
|
from py.__.test.runner import basic_collect_report
|
||||||
|
|
@ -24,17 +23,17 @@ def dumpqueue(queue):
|
||||||
while queue.qsize():
|
while queue.qsize():
|
||||||
print queue.get()
|
print queue.get()
|
||||||
|
|
||||||
class TestDSession(InlineCollection):
|
class TestDSession:
|
||||||
def test_fixoptions(self):
|
def test_fixoptions(self, testdir):
|
||||||
config = self.parseconfig("--exec=xxx")
|
config = testdir.parseconfig("--exec=xxx")
|
||||||
config.initsession().fixoptions()
|
config.initsession().fixoptions()
|
||||||
assert config.option.numprocesses == 1
|
assert config.option.numprocesses == 1
|
||||||
config = self.parseconfig("--exec=xxx", '-n3')
|
config = testdir.parseconfig("--exec=xxx", '-n3')
|
||||||
config.initsession().fixoptions()
|
config.initsession().fixoptions()
|
||||||
assert config.option.numprocesses == 3
|
assert config.option.numprocesses == 3
|
||||||
|
|
||||||
def test_add_remove_host(self):
|
def test_add_remove_host(self, testdir):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
rep = run(item)
|
rep = run(item)
|
||||||
session = DSession(item._config)
|
session = DSession(item._config)
|
||||||
host = Host("localhost")
|
host = Host("localhost")
|
||||||
|
|
@ -48,8 +47,8 @@ class TestDSession(InlineCollection):
|
||||||
assert item not in session.item2host
|
assert item not in session.item2host
|
||||||
py.test.raises(Exception, "session.removehost(host)")
|
py.test.raises(Exception, "session.removehost(host)")
|
||||||
|
|
||||||
def test_senditems_removeitems(self):
|
def test_senditems_removeitems(self, testdir):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
rep = run(item)
|
rep = run(item)
|
||||||
session = DSession(item._config)
|
session = DSession(item._config)
|
||||||
host = Host("localhost")
|
host = Host("localhost")
|
||||||
|
|
@ -62,19 +61,20 @@ class TestDSession(InlineCollection):
|
||||||
assert not session.host2pending[host]
|
assert not session.host2pending[host]
|
||||||
assert not session.item2host
|
assert not session.item2host
|
||||||
|
|
||||||
def test_triggertesting_collect(self):
|
def test_triggertesting_collect(self, testdir):
|
||||||
modcol = self.getmodulecol("""
|
modcol = testdir.getmodulecol("""
|
||||||
def test_func():
|
def test_func():
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
session = DSession(modcol._config)
|
session = DSession(modcol._config)
|
||||||
session.triggertesting([modcol])
|
session.triggertesting([modcol])
|
||||||
rep = session.queue.get(block=False)
|
name, args, kwargs = session.queue.get(block=False)
|
||||||
assert isinstance(rep, event.CollectionReport)
|
assert name == 'collectionreport'
|
||||||
|
rep, = args
|
||||||
assert len(rep.result) == 1
|
assert len(rep.result) == 1
|
||||||
|
|
||||||
def test_triggertesting_item(self):
|
def test_triggertesting_item(self, testdir):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
session = DSession(item._config)
|
session = DSession(item._config)
|
||||||
host1 = Host("localhost")
|
host1 = Host("localhost")
|
||||||
host1.node = MockNode()
|
host1.node = MockNode()
|
||||||
|
|
@ -89,51 +89,52 @@ class TestDSession(InlineCollection):
|
||||||
assert host2_sent == [item] * session.MAXITEMSPERHOST
|
assert host2_sent == [item] * session.MAXITEMSPERHOST
|
||||||
assert session.host2pending[host1] == host1_sent
|
assert session.host2pending[host1] == host1_sent
|
||||||
assert session.host2pending[host2] == host2_sent
|
assert session.host2pending[host2] == host2_sent
|
||||||
ev = session.queue.get(block=False)
|
name, args, kwargs = session.queue.get(block=False)
|
||||||
assert isinstance(ev, event.RescheduleItems)
|
assert name == "rescheduleitems"
|
||||||
|
ev, = args
|
||||||
assert ev.items == [item]
|
assert ev.items == [item]
|
||||||
|
|
||||||
def test_keyboardinterrupt(self):
|
def test_keyboardinterrupt(self, testdir):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
session = DSession(item._config)
|
session = DSession(item._config)
|
||||||
def raise_(timeout=None): raise KeyboardInterrupt()
|
def raise_(timeout=None): raise KeyboardInterrupt()
|
||||||
session.queue.get = raise_
|
session.queue.get = raise_
|
||||||
exitstatus = session.loop([])
|
exitstatus = session.loop([])
|
||||||
assert exitstatus == outcome.EXIT_INTERRUPTED
|
assert exitstatus == outcome.EXIT_INTERRUPTED
|
||||||
|
|
||||||
def test_internalerror(self):
|
def test_internalerror(self, testdir):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
session = DSession(item._config)
|
session = DSession(item._config)
|
||||||
def raise_(): raise ValueError()
|
def raise_(): raise ValueError()
|
||||||
session.queue.get = raise_
|
session.queue.get = raise_
|
||||||
exitstatus = session.loop([])
|
exitstatus = session.loop([])
|
||||||
assert exitstatus == outcome.EXIT_INTERNALERROR
|
assert exitstatus == outcome.EXIT_INTERNALERROR
|
||||||
|
|
||||||
def test_rescheduleevent(self):
|
def test_rescheduleevent(self, testdir):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
session = DSession(item._config)
|
session = DSession(item._config)
|
||||||
host1 = Host("localhost")
|
host1 = Host("localhost")
|
||||||
host1.node = MockNode()
|
host1.node = MockNode()
|
||||||
session.addhost(host1)
|
session.addhost(host1)
|
||||||
ev = event.RescheduleItems([item])
|
ev = event.RescheduleItems([item])
|
||||||
loopstate = LoopState([])
|
loopstate = LoopState([])
|
||||||
session.queue.put(ev)
|
session.queueevent("rescheduleitems", ev)
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
# check that RescheduleEvents are not immediately
|
# check that RescheduleEvents are not immediately
|
||||||
# rescheduled if there are no hosts
|
# rescheduled if there are no hosts
|
||||||
assert loopstate.dowork == False
|
assert loopstate.dowork == False
|
||||||
session.queue.put(event.NOP())
|
session.queueevent("anonymous", event.NOP())
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
session.queue.put(event.NOP())
|
session.queueevent("anonymous", event.NOP())
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
assert host1.node.sent == [[item]]
|
assert host1.node.sent == [[item]]
|
||||||
session.queue.put(run(item))
|
session.queueevent("itemtestreport", run(item))
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
assert loopstate.shuttingdown
|
assert loopstate.shuttingdown
|
||||||
assert not loopstate.testsfailed
|
assert not loopstate.testsfailed
|
||||||
|
|
||||||
def test_no_hosts_remaining_for_tests(self):
|
def test_no_hosts_remaining_for_tests(self, testdir):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
# setup a session with one host
|
# setup a session with one host
|
||||||
session = DSession(item._config)
|
session = DSession(item._config)
|
||||||
host1 = Host("localhost")
|
host1 = Host("localhost")
|
||||||
|
|
@ -142,7 +143,7 @@ class TestDSession(InlineCollection):
|
||||||
|
|
||||||
# setup a HostDown event
|
# setup a HostDown event
|
||||||
ev = event.HostDown(host1, None)
|
ev = event.HostDown(host1, None)
|
||||||
session.queue.put(ev)
|
session.queueevent("hostdown", ev)
|
||||||
|
|
||||||
loopstate = LoopState([item])
|
loopstate = LoopState([item])
|
||||||
loopstate.dowork = False
|
loopstate.dowork = False
|
||||||
|
|
@ -150,8 +151,8 @@ class TestDSession(InlineCollection):
|
||||||
dumpqueue(session.queue)
|
dumpqueue(session.queue)
|
||||||
assert loopstate.exitstatus == outcome.EXIT_NOHOSTS
|
assert loopstate.exitstatus == outcome.EXIT_NOHOSTS
|
||||||
|
|
||||||
def test_hostdown_causes_reschedule_pending(self):
|
def test_hostdown_causes_reschedule_pending(self, testdir, EventRecorder):
|
||||||
modcol = self.getmodulecol("""
|
modcol = testdir.getmodulecol("""
|
||||||
def test_crash():
|
def test_crash():
|
||||||
assert 0
|
assert 0
|
||||||
def test_fail():
|
def test_fail():
|
||||||
|
|
@ -172,41 +173,39 @@ class TestDSession(InlineCollection):
|
||||||
session.senditems([item1, item2])
|
session.senditems([item1, item2])
|
||||||
host = session.item2host[item1]
|
host = session.item2host[item1]
|
||||||
ev = event.HostDown(host, None)
|
ev = event.HostDown(host, None)
|
||||||
session.queue.put(ev)
|
session.queueevent("hostdown", ev)
|
||||||
|
evrec = EventRecorder(session.bus)
|
||||||
events = [] ; session.bus.subscribe(events.append)
|
|
||||||
loopstate = LoopState([])
|
loopstate = LoopState([])
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
|
|
||||||
assert loopstate.colitems == [item2] # do not reschedule crash item
|
assert loopstate.colitems == [item2] # do not reschedule crash item
|
||||||
testrep = [x for x in events if isinstance(x, event.ItemTestReport)][0]
|
testrep = evrec.getfirstnamed("itemtestreport")
|
||||||
assert testrep.failed
|
assert testrep.failed
|
||||||
assert testrep.colitem == item1
|
assert testrep.colitem == item1
|
||||||
assert str(testrep.outcome.longrepr).find("CRASHED") != -1
|
assert str(testrep.longrepr).find("crashed") != -1
|
||||||
assert str(testrep.outcome.longrepr).find(host.hostname) != -1
|
assert str(testrep.longrepr).find(host.hostname) != -1
|
||||||
|
|
||||||
def test_hostup_adds_to_available(self):
|
def test_hostup_adds_to_available(self, testdir):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
# setup a session with two hosts
|
# setup a session with two hosts
|
||||||
session = DSession(item._config)
|
session = DSession(item._config)
|
||||||
host1 = Host("localhost")
|
host1 = Host("localhost")
|
||||||
hostup = makehostup(host1)
|
hostup = makehostup(host1)
|
||||||
session.queue.put(hostup)
|
session.queueevent("hostup", hostup)
|
||||||
loopstate = LoopState([item])
|
loopstate = LoopState([item])
|
||||||
loopstate.dowork = False
|
loopstate.dowork = False
|
||||||
assert len(session.host2pending) == 0
|
assert len(session.host2pending) == 0
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
assert len(session.host2pending) == 1
|
assert len(session.host2pending) == 1
|
||||||
|
|
||||||
def test_event_propagation(self):
|
def test_event_propagation(self, testdir, EventRecorder):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
session = DSession(item._config)
|
session = DSession(item._config)
|
||||||
|
|
||||||
ev = event.NOP()
|
evrec = EventRecorder(session.bus)
|
||||||
events = [] ; session.bus.subscribe(events.append)
|
session.queueevent("NOPevent", 42)
|
||||||
session.queue.put(ev)
|
|
||||||
session.loop_once(LoopState([]))
|
session.loop_once(LoopState([]))
|
||||||
assert events[0] == ev
|
assert evrec.getfirstnamed('NOPevent')
|
||||||
|
|
||||||
def runthrough(self, item):
|
def runthrough(self, item):
|
||||||
session = DSession(item._config)
|
session = DSession(item._config)
|
||||||
|
|
@ -215,31 +214,31 @@ class TestDSession(InlineCollection):
|
||||||
session.addhost(host1)
|
session.addhost(host1)
|
||||||
loopstate = LoopState([item])
|
loopstate = LoopState([item])
|
||||||
|
|
||||||
session.queue.put(event.NOP())
|
session.queueevent("NOP")
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
|
|
||||||
assert host1.node.sent == [[item]]
|
assert host1.node.sent == [[item]]
|
||||||
ev = run(item)
|
ev = run(item)
|
||||||
session.queue.put(ev)
|
session.queueevent("itemtestreport", ev)
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
assert loopstate.shuttingdown
|
assert loopstate.shuttingdown
|
||||||
session.queue.put(event.HostDown(host1, None))
|
session.queueevent("hostdown", event.HostDown(host1, None))
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
dumpqueue(session.queue)
|
dumpqueue(session.queue)
|
||||||
return session, loopstate.exitstatus
|
return session, loopstate.exitstatus
|
||||||
|
|
||||||
def test_exit_completed_tests_ok(self):
|
def test_exit_completed_tests_ok(self, testdir):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
session, exitstatus = self.runthrough(item)
|
session, exitstatus = self.runthrough(item)
|
||||||
assert exitstatus == outcome.EXIT_OK
|
assert exitstatus == outcome.EXIT_OK
|
||||||
|
|
||||||
def test_exit_completed_tests_fail(self):
|
def test_exit_completed_tests_fail(self, testdir):
|
||||||
item = self.getitem("def test_func(): 0/0")
|
item = testdir.getitem("def test_func(): 0/0")
|
||||||
session, exitstatus = self.runthrough(item)
|
session, exitstatus = self.runthrough(item)
|
||||||
assert exitstatus == outcome.EXIT_TESTSFAILED
|
assert exitstatus == outcome.EXIT_TESTSFAILED
|
||||||
|
|
||||||
def test_exit_on_first_failing(self):
|
def test_exit_on_first_failing(self, testdir):
|
||||||
modcol = self.getmodulecol("""
|
modcol = testdir.getmodulecol("""
|
||||||
def test_fail():
|
def test_fail():
|
||||||
assert 0
|
assert 0
|
||||||
def test_pass():
|
def test_pass():
|
||||||
|
|
@ -258,33 +257,32 @@ class TestDSession(InlineCollection):
|
||||||
# run tests ourselves and produce reports
|
# run tests ourselves and produce reports
|
||||||
ev1 = run(items[0])
|
ev1 = run(items[0])
|
||||||
ev2 = run(items[1])
|
ev2 = run(items[1])
|
||||||
session.queue.put(ev1) # a failing one
|
session.queueevent("itemtestreport", ev1) # a failing one
|
||||||
session.queue.put(ev2)
|
session.queueevent("itemtestreport", ev2)
|
||||||
# now call the loop
|
# now call the loop
|
||||||
loopstate = LoopState(items)
|
loopstate = LoopState(items)
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
assert loopstate.testsfailed
|
assert loopstate.testsfailed
|
||||||
assert loopstate.shuttingdown
|
assert loopstate.shuttingdown
|
||||||
|
|
||||||
def test_shuttingdown_filters_events(self):
|
def test_shuttingdown_filters_events(self, testdir, EventRecorder):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
session = DSession(item._config)
|
session = DSession(item._config)
|
||||||
host = Host("localhost")
|
host = Host("localhost")
|
||||||
session.addhost(host)
|
session.addhost(host)
|
||||||
loopstate = LoopState([])
|
loopstate = LoopState([])
|
||||||
loopstate.shuttingdown = True
|
loopstate.shuttingdown = True
|
||||||
l = []
|
evrec = EventRecorder(session.bus)
|
||||||
session.bus.subscribe(l.append)
|
session.queueevent("itemtestreport", run(item))
|
||||||
session.queue.put(run(item))
|
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
assert not l
|
assert not evrec.getfirstnamed("hostdown")
|
||||||
ev = event.HostDown(host)
|
ev = event.HostDown(host)
|
||||||
session.queue.put(ev)
|
session.queueevent("hostdown", ev)
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
assert l == [ev]
|
assert evrec.getfirstnamed('hostdown') == ev
|
||||||
|
|
||||||
def test_filteritems(self):
|
def test_filteritems(self, testdir, EventRecorder):
|
||||||
modcol = self.getmodulecol("""
|
modcol = testdir.getmodulecol("""
|
||||||
def test_fail():
|
def test_fail():
|
||||||
assert 0
|
assert 0
|
||||||
def test_pass():
|
def test_pass():
|
||||||
|
|
@ -296,42 +294,42 @@ class TestDSession(InlineCollection):
|
||||||
dsel = session.filteritems([modcol])
|
dsel = session.filteritems([modcol])
|
||||||
assert dsel == [modcol]
|
assert dsel == [modcol]
|
||||||
items = modcol.collect()
|
items = modcol.collect()
|
||||||
events = [] ; session.bus.subscribe(events.append)
|
evrec = EventRecorder(session.bus)
|
||||||
remaining = session.filteritems(items)
|
remaining = session.filteritems(items)
|
||||||
assert remaining == []
|
assert remaining == []
|
||||||
|
|
||||||
ev = events[-1]
|
evname, ev = evrec.events[-1]
|
||||||
assert isinstance(ev, event.Deselected)
|
assert evname == "deselected"
|
||||||
assert ev.items == items
|
assert ev.items == items
|
||||||
|
|
||||||
modcol._config.option.keyword = "test_fail"
|
modcol._config.option.keyword = "test_fail"
|
||||||
remaining = session.filteritems(items)
|
remaining = session.filteritems(items)
|
||||||
assert remaining == [items[0]]
|
assert remaining == [items[0]]
|
||||||
|
|
||||||
ev = events[-1]
|
evname, ev = evrec.events[-1]
|
||||||
assert isinstance(ev, event.Deselected)
|
assert evname == "deselected"
|
||||||
assert ev.items == [items[1]]
|
assert ev.items == [items[1]]
|
||||||
|
|
||||||
def test_hostdown_shutdown_after_completion(self):
|
def test_hostdown_shutdown_after_completion(self, testdir):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
session = DSession(item._config)
|
session = DSession(item._config)
|
||||||
|
|
||||||
host = Host("localhost")
|
host = Host("localhost")
|
||||||
host.node = MockNode()
|
host.node = MockNode()
|
||||||
session.addhost(host)
|
session.addhost(host)
|
||||||
session.senditems([item])
|
session.senditems([item])
|
||||||
session.queue.put(run(item))
|
session.queueevent("itemtestreport", run(item))
|
||||||
loopstate = LoopState([])
|
loopstate = LoopState([])
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
assert host.node._shutdown is True
|
assert host.node._shutdown is True
|
||||||
assert loopstate.exitstatus is None, "loop did not wait for hostdown"
|
assert loopstate.exitstatus is None, "loop did not wait for hostdown"
|
||||||
assert loopstate.shuttingdown
|
assert loopstate.shuttingdown
|
||||||
session.queue.put(event.HostDown(host, None))
|
session.queueevent("hostdown", event.HostDown(host, None))
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
assert loopstate.exitstatus == 0
|
assert loopstate.exitstatus == 0
|
||||||
|
|
||||||
def test_nopending_but_collection_remains(self):
|
def test_nopending_but_collection_remains(self, testdir):
|
||||||
modcol = self.getmodulecol("""
|
modcol = testdir.getmodulecol("""
|
||||||
def test_fail():
|
def test_fail():
|
||||||
assert 0
|
assert 0
|
||||||
def test_pass():
|
def test_pass():
|
||||||
|
|
@ -347,10 +345,10 @@ class TestDSession(InlineCollection):
|
||||||
session.senditems([item1])
|
session.senditems([item1])
|
||||||
# host2pending will become empty when the loop sees
|
# host2pending will become empty when the loop sees
|
||||||
# the report
|
# the report
|
||||||
session.queue.put(run(item1))
|
session.queueevent("itemtestreport", run(item1))
|
||||||
|
|
||||||
# but we have a collection pending
|
# but we have a collection pending
|
||||||
session.queue.put(colreport)
|
session.queueevent("collectionreport", colreport)
|
||||||
|
|
||||||
loopstate = LoopState([])
|
loopstate = LoopState([])
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
|
|
|
||||||
|
|
@ -3,74 +3,48 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import py
|
import py
|
||||||
from py.__.test import event
|
|
||||||
from py.__.test.dsession.dsession import DSession
|
from py.__.test.dsession.dsession import DSession
|
||||||
from py.__.test.dsession.hostmanage import HostManager, Host
|
from py.__.test.dsession.hostmanage import HostManager, Host
|
||||||
from py.__.test.testing import suptest
|
from test_masterslave import EventQueue
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
def eventreader(session):
|
|
||||||
queue = py.std.Queue.Queue()
|
|
||||||
session.bus.subscribe(queue.put)
|
|
||||||
def readevent(eventtype=event.ItemTestReport, timeout=2.0):
|
|
||||||
events = []
|
|
||||||
while 1:
|
|
||||||
try:
|
|
||||||
ev = queue.get(timeout=timeout)
|
|
||||||
except py.std.Queue.Empty:
|
|
||||||
print "seen events", events
|
|
||||||
raise IOError("did not see %r events" % (eventtype))
|
|
||||||
else:
|
|
||||||
if isinstance(ev, eventtype):
|
|
||||||
#print "other events seen", events
|
|
||||||
return ev
|
|
||||||
events.append(ev)
|
|
||||||
return readevent
|
|
||||||
|
|
||||||
class TestAsyncFunctional(suptest.InlineCollection):
|
class TestAsyncFunctional:
|
||||||
def test_dist_no_disthost(self):
|
def test_dist_no_disthost(self, testdir):
|
||||||
config = self.parseconfig(self.tmpdir, '-d')
|
config = testdir.parseconfig(testdir.tmpdir, '-d')
|
||||||
py.test.raises(SystemExit, "config.initsession()")
|
py.test.raises(SystemExit, "config.initsession()")
|
||||||
|
|
||||||
def test_session_eventlog_dist(self):
|
def test_conftest_options(self, testdir):
|
||||||
self.makepyfile(conftest="dist_hosts=['localhost']\n")
|
testdir.makepyfile(conftest="""
|
||||||
eventlog = self.tmpdir.join("mylog")
|
|
||||||
config = self.parseconfig(self.tmpdir, '-d', '--eventlog=%s' % eventlog)
|
|
||||||
session = config.initsession()
|
|
||||||
session.bus.notify(event.TestrunStart())
|
|
||||||
s = eventlog.read()
|
|
||||||
assert s.find("TestrunStart") != -1
|
|
||||||
|
|
||||||
def test_conftest_options(self):
|
|
||||||
self.makepyfile(conftest="""
|
|
||||||
print "importing conftest"
|
print "importing conftest"
|
||||||
import py
|
import py
|
||||||
Option = py.test.config.Option
|
Option = py.test.config.Option
|
||||||
option = py.test.config.addoptions("someopt",
|
option = py.test.config.addoptions("someopt",
|
||||||
Option('', '--forcegen', action="store_true", dest="forcegen", default=False))
|
Option('--someopt', action="store_true", dest="someopt", default=False))
|
||||||
""")
|
""",
|
||||||
self.makepyfile(__init__="#")
|
)
|
||||||
p1 = self.makepyfile(test_one="""
|
p1 = testdir.makepyfile("""
|
||||||
def test_1():
|
def test_1():
|
||||||
import py, conftest
|
import py, conftest
|
||||||
print "test_1: py.test.config.option.forcegen", py.test.config.option.forcegen
|
print "test_1: py.test.config.option.someopt", py.test.config.option.someopt
|
||||||
print "test_1: conftest", conftest
|
print "test_1: conftest", conftest
|
||||||
print "test_1: conftest.option.forcegen", conftest.option.forcegen
|
print "test_1: conftest.option.someopt", conftest.option.someopt
|
||||||
assert conftest.option.forcegen
|
assert conftest.option.someopt
|
||||||
""")
|
""", __init__="#")
|
||||||
print p1
|
print p1
|
||||||
config = self.parseconfig('-n1', p1, '--forcegen')
|
config = py.test.config._reparse(['-n1', p1, '--someopt'])
|
||||||
dsession = DSession(config)
|
dsession = DSession(config)
|
||||||
readevent = eventreader(dsession)
|
eq = EventQueue(config.bus)
|
||||||
dsession.main()
|
dsession.main()
|
||||||
ev = readevent(event.ItemTestReport)
|
ev, = eq.geteventargs("itemtestreport")
|
||||||
if not ev.passed:
|
if not ev.passed:
|
||||||
print ev.outcome.longrepr
|
print ev
|
||||||
assert 0
|
assert 0
|
||||||
|
|
||||||
def test_dist_some_tests(self):
|
def test_dist_some_tests(self, testdir):
|
||||||
self.makepyfile(conftest="dist_hosts=['localhost']\n")
|
testdir.makepyfile(conftest="dist_hosts=['localhost']\n")
|
||||||
p1 = self.makepyfile(test_one="""
|
p1 = testdir.makepyfile(test_one="""
|
||||||
def test_1():
|
def test_1():
|
||||||
pass
|
pass
|
||||||
def test_x():
|
def test_x():
|
||||||
|
|
@ -79,22 +53,22 @@ class TestAsyncFunctional(suptest.InlineCollection):
|
||||||
def test_fail():
|
def test_fail():
|
||||||
assert 0
|
assert 0
|
||||||
""")
|
""")
|
||||||
config = self.parseconfig('-d', p1)
|
config = testdir.parseconfig('-d', p1)
|
||||||
dsession = DSession(config)
|
dsession = DSession(config)
|
||||||
readevent = eventreader(dsession)
|
eq = EventQueue(config.bus)
|
||||||
dsession.main([config.getfsnode(p1)])
|
dsession.main([config.getfsnode(p1)])
|
||||||
ev = readevent(event.ItemTestReport)
|
ev, = eq.geteventargs("itemtestreport")
|
||||||
assert ev.passed
|
assert ev.passed
|
||||||
ev = readevent(event.ItemTestReport)
|
ev, = eq.geteventargs("itemtestreport")
|
||||||
assert ev.skipped
|
assert ev.skipped
|
||||||
ev = readevent(event.ItemTestReport)
|
ev, = eq.geteventargs("itemtestreport")
|
||||||
assert ev.failed
|
assert ev.failed
|
||||||
# see that the host is really down
|
# see that the host is really down
|
||||||
ev = readevent(event.HostDown)
|
ev, = eq.geteventargs("hostdown")
|
||||||
assert ev.host.hostname == "localhost"
|
assert ev.host.hostname == "localhost"
|
||||||
ev = readevent(event.TestrunFinish)
|
ev, = eq.geteventargs("testrunfinish")
|
||||||
|
|
||||||
def test_distribution_rsync_roots_example(self):
|
def test_distribution_rsync_roots_example(self, testdir):
|
||||||
py.test.skip("testing for root rsync needs rework")
|
py.test.skip("testing for root rsync needs rework")
|
||||||
destdir = py.test.ensuretemp("example_dist_destdir")
|
destdir = py.test.ensuretemp("example_dist_destdir")
|
||||||
subdir = "sub_example_dist"
|
subdir = "sub_example_dist"
|
||||||
|
|
@ -124,28 +98,26 @@ class TestAsyncFunctional(suptest.InlineCollection):
|
||||||
assert config.topdir == tmpdir
|
assert config.topdir == tmpdir
|
||||||
assert not tmpdir.join("__init__.py").check()
|
assert not tmpdir.join("__init__.py").check()
|
||||||
dist = DSession(config)
|
dist = DSession(config)
|
||||||
sorter = suptest.events_from_session(dist)
|
sorter = testdir.inline_runsession(dist)
|
||||||
testevents = sorter.get(event.ItemTestReport)
|
testevents = sorter.getnamed('itemtestreport')
|
||||||
assert len([x for x in testevents if x.passed]) == 2
|
assert len([x for x in testevents if x.passed]) == 2
|
||||||
assert len([x for x in testevents if x.failed]) == 3
|
assert len([x for x in testevents if x.failed]) == 3
|
||||||
assert len([x for x in testevents if x.skipped]) == 0
|
assert len([x for x in testevents if x.skipped]) == 0
|
||||||
|
|
||||||
def test_nice_level(self):
|
def test_nice_level(self, testdir):
|
||||||
""" Tests if nice level behaviour is ok """
|
""" Tests if nice level behaviour is ok """
|
||||||
if not hasattr(os, 'nice'):
|
if not hasattr(os, 'nice'):
|
||||||
py.test.skip("no os.nice() available")
|
py.test.skip("no os.nice() available")
|
||||||
self.makepyfile(conftest="""
|
testdir.makepyfile(conftest="""
|
||||||
dist_hosts=['localhost']
|
dist_hosts=['localhost']
|
||||||
dist_nicelevel = 10
|
dist_nicelevel = 10
|
||||||
""")
|
""")
|
||||||
p1 = self.makepyfile(test_nice="""
|
p1 = testdir.makepyfile("""
|
||||||
def test_nice():
|
def test_nice():
|
||||||
import os
|
import os
|
||||||
assert os.nice(0) == 10
|
assert os.nice(0) == 10
|
||||||
""")
|
""")
|
||||||
config = self.parseconfig('-d', p1)
|
evrec = testdir.inline_run('-d', p1)
|
||||||
session = config.initsession()
|
ev = evrec.getreport('test_nice')
|
||||||
events = suptest.events_from_session(session)
|
|
||||||
ev = events.getreport('test_nice')
|
|
||||||
assert ev.passed
|
assert ev.passed
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,23 +3,15 @@
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import py
|
import py
|
||||||
from py.__.test.testing import suptest
|
|
||||||
from py.__.test.dsession.hostmanage import HostRSync, Host, HostManager, gethosts
|
from py.__.test.dsession.hostmanage import HostRSync, Host, HostManager, gethosts
|
||||||
from py.__.test.dsession.hostmanage import sethomedir, gethomedir, getpath_relto_home
|
from py.__.test.dsession.hostmanage import sethomedir, gethomedir, getpath_relto_home
|
||||||
from py.__.test import event
|
from py.__.test import event
|
||||||
|
|
||||||
class TmpWithSourceDest(suptest.FileCreation):
|
class TestHost:
|
||||||
def setup_method(self, method):
|
def _gethostinfo(self, testdir, relpath=""):
|
||||||
super(TmpWithSourceDest, self).setup_method(method)
|
exampledir = testdir.mkdir("gethostinfo")
|
||||||
self.source = self.tmpdir.mkdir("source")
|
|
||||||
self.dest = self.tmpdir.mkdir("dest")
|
|
||||||
|
|
||||||
class TestHost(suptest.FileCreation):
|
|
||||||
def _gethostinfo(self, relpath=""):
|
|
||||||
exampledir = self.tmpdir.join("gethostinfo")
|
|
||||||
if relpath:
|
if relpath:
|
||||||
exampledir = exampledir.join(relpath)
|
exampledir = exampledir.join(relpath)
|
||||||
assert not exampledir.check()
|
|
||||||
hostinfo = Host("localhost:%s" % exampledir)
|
hostinfo = Host("localhost:%s" % exampledir)
|
||||||
return hostinfo
|
return hostinfo
|
||||||
|
|
||||||
|
|
@ -62,8 +54,8 @@ class TestHost(suptest.FileCreation):
|
||||||
py.test.raises((py.process.cmdexec.Error, IOError, EOFError),
|
py.test.raises((py.process.cmdexec.Error, IOError, EOFError),
|
||||||
host.initgateway)
|
host.initgateway)
|
||||||
|
|
||||||
def test_remote_has_homedir_as_currentdir(self):
|
def test_remote_has_homedir_as_currentdir(self, testdir):
|
||||||
host = self._gethostinfo()
|
host = self._gethostinfo(testdir)
|
||||||
old = py.path.local.get_temproot().chdir()
|
old = py.path.local.get_temproot().chdir()
|
||||||
try:
|
try:
|
||||||
host.initgateway()
|
host.initgateway()
|
||||||
|
|
@ -104,10 +96,10 @@ class TestHost(suptest.FileCreation):
|
||||||
assert l[0] == host.python
|
assert l[0] == host.python
|
||||||
|
|
||||||
def test_initgateway_ssh_and_remotepath(self):
|
def test_initgateway_ssh_and_remotepath(self):
|
||||||
from py.__.conftest import option
|
hostspec = py.test.config.option.sshhost
|
||||||
if not option.sshtarget:
|
if not hostspec:
|
||||||
py.test.skip("no known ssh target, use -S to set one")
|
py.test.skip("no known ssh target, use -S to set one")
|
||||||
host = Host("%s" % (option.sshtarget, ))
|
host = Host("%s" % (hostspec))
|
||||||
# this test should be careful to not write/rsync anything
|
# this test should be careful to not write/rsync anything
|
||||||
# as the remotepath is the default location
|
# as the remotepath is the default location
|
||||||
# and may be used in the real world
|
# and may be used in the real world
|
||||||
|
|
@ -125,18 +117,23 @@ class TestHost(suptest.FileCreation):
|
||||||
res = channel.receive()
|
res = channel.receive()
|
||||||
assert res == host.gw_remotepath
|
assert res == host.gw_remotepath
|
||||||
|
|
||||||
class TestSyncing(TmpWithSourceDest):
|
def pytest_pyfuncarg_source(pyfuncitem):
|
||||||
def _gethostinfo(self):
|
return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source")
|
||||||
hostinfo = Host("localhost:%s" % self.dest)
|
def pytest_pyfuncarg_dest(pyfuncitem):
|
||||||
|
return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("dest")
|
||||||
|
|
||||||
|
class TestSyncing:
|
||||||
|
def _gethostinfo(self, dest):
|
||||||
|
hostinfo = Host("localhost:%s" % dest)
|
||||||
return hostinfo
|
return hostinfo
|
||||||
|
|
||||||
def test_hrsync_filter(self):
|
def test_hrsync_filter(self, source, dest):
|
||||||
self.source.ensure("dir", "file.txt")
|
source.ensure("dir", "file.txt")
|
||||||
self.source.ensure(".svn", "entries")
|
source.ensure(".svn", "entries")
|
||||||
self.source.ensure(".somedotfile", "moreentries")
|
source.ensure(".somedotfile", "moreentries")
|
||||||
self.source.ensure("somedir", "editfile~")
|
source.ensure("somedir", "editfile~")
|
||||||
syncer = HostRSync(self.source)
|
syncer = HostRSync(source)
|
||||||
l = list(self.source.visit(rec=syncer.filter,
|
l = list(source.visit(rec=syncer.filter,
|
||||||
fil=syncer.filter))
|
fil=syncer.filter))
|
||||||
assert len(l) == 3
|
assert len(l) == 3
|
||||||
basenames = [x.basename for x in l]
|
basenames = [x.basename for x in l]
|
||||||
|
|
@ -144,10 +141,10 @@ class TestSyncing(TmpWithSourceDest):
|
||||||
assert 'file.txt' in basenames
|
assert 'file.txt' in basenames
|
||||||
assert 'somedir' in basenames
|
assert 'somedir' in basenames
|
||||||
|
|
||||||
def test_hrsync_localhost_inplace(self):
|
def test_hrsync_localhost_inplace(self, source, dest):
|
||||||
h1 = Host("localhost")
|
h1 = Host("localhost")
|
||||||
events = []
|
events = []
|
||||||
rsync = HostRSync(self.source)
|
rsync = HostRSync(source)
|
||||||
h1.initgateway()
|
h1.initgateway()
|
||||||
rsync.add_target_host(h1, notify=events.append)
|
rsync.add_target_host(h1, notify=events.append)
|
||||||
assert events
|
assert events
|
||||||
|
|
@ -161,24 +158,24 @@ class TestSyncing(TmpWithSourceDest):
|
||||||
if isinstance(x, event.HostRSyncRootReady)]
|
if isinstance(x, event.HostRSyncRootReady)]
|
||||||
assert len(l) == 1
|
assert len(l) == 1
|
||||||
ev = l[0]
|
ev = l[0]
|
||||||
assert ev.root == self.source
|
assert ev.root == source
|
||||||
assert ev.host == h1
|
assert ev.host == h1
|
||||||
|
|
||||||
def test_hrsync_one_host(self):
|
def test_hrsync_one_host(self, source, dest):
|
||||||
h1 = self._gethostinfo()
|
h1 = self._gethostinfo(dest)
|
||||||
finished = []
|
finished = []
|
||||||
rsync = HostRSync(self.source)
|
rsync = HostRSync(source)
|
||||||
h1.initgateway()
|
h1.initgateway()
|
||||||
rsync.add_target_host(h1)
|
rsync.add_target_host(h1)
|
||||||
self.source.join("hello.py").write("world")
|
source.join("hello.py").write("world")
|
||||||
rsync.send()
|
rsync.send()
|
||||||
assert self.dest.join("hello.py").check()
|
assert dest.join("hello.py").check()
|
||||||
|
|
||||||
def test_hrsync_same_host_twice(self):
|
def test_hrsync_same_host_twice(self, source, dest):
|
||||||
h1 = self._gethostinfo()
|
h1 = self._gethostinfo(dest)
|
||||||
h2 = self._gethostinfo()
|
h2 = self._gethostinfo(dest)
|
||||||
finished = []
|
finished = []
|
||||||
rsync = HostRSync(self.source)
|
rsync = HostRSync(source)
|
||||||
l = []
|
l = []
|
||||||
h1.initgateway()
|
h1.initgateway()
|
||||||
h2.initgateway()
|
h2.initgateway()
|
||||||
|
|
@ -187,87 +184,87 @@ class TestSyncing(TmpWithSourceDest):
|
||||||
res2 = rsync.add_target_host(h2)
|
res2 = rsync.add_target_host(h2)
|
||||||
assert not res2
|
assert not res2
|
||||||
|
|
||||||
class TestHostManager(TmpWithSourceDest):
|
class TestHostManager:
|
||||||
def gethostmanager(self, dist_hosts, dist_rsync_roots=None):
|
def gethostmanager(self, source, dist_hosts, dist_rsync_roots=None):
|
||||||
l = ["dist_hosts = %r" % dist_hosts]
|
l = ["dist_hosts = %r" % dist_hosts]
|
||||||
if dist_rsync_roots:
|
if dist_rsync_roots:
|
||||||
l.append("dist_rsync_roots = %r" % dist_rsync_roots)
|
l.append("dist_rsync_roots = %r" % dist_rsync_roots)
|
||||||
self.source.join("conftest.py").write("\n".join(l))
|
source.join("conftest.py").write("\n".join(l))
|
||||||
config = py.test.config._reparse([self.source])
|
config = py.test.config._reparse([source])
|
||||||
assert config.topdir == self.source
|
assert config.topdir == source
|
||||||
session = config.initsession()
|
session = config.initsession()
|
||||||
hm = HostManager(session)
|
hm = HostManager(session)
|
||||||
assert hm.hosts
|
assert hm.hosts
|
||||||
return hm
|
return hm
|
||||||
|
|
||||||
def test_hostmanager_custom_hosts(self):
|
def test_hostmanager_custom_hosts(self, source, dest):
|
||||||
session = py.test.config._reparse([self.source]).initsession()
|
session = py.test.config._reparse([source]).initsession()
|
||||||
hm = HostManager(session, hosts=[1,2,3])
|
hm = HostManager(session, hosts=[1,2,3])
|
||||||
assert hm.hosts == [1,2,3]
|
assert hm.hosts == [1,2,3]
|
||||||
|
|
||||||
def test_hostmanager_init_rsync_topdir(self):
|
def test_hostmanager_init_rsync_topdir(self, source, dest):
|
||||||
dir2 = self.source.ensure("dir1", "dir2", dir=1)
|
dir2 = source.ensure("dir1", "dir2", dir=1)
|
||||||
dir2.ensure("hello")
|
dir2.ensure("hello")
|
||||||
hm = self.gethostmanager(
|
hm = self.gethostmanager(source,
|
||||||
dist_hosts = ["localhost:%s" % self.dest]
|
dist_hosts = ["localhost:%s" % dest]
|
||||||
)
|
)
|
||||||
assert hm.session.config.topdir == self.source
|
assert hm.session.config.topdir == source
|
||||||
hm.init_rsync()
|
hm.init_rsync()
|
||||||
dest = self.dest.join(self.source.basename)
|
dest = dest.join(source.basename)
|
||||||
assert dest.join("dir1").check()
|
assert dest.join("dir1").check()
|
||||||
assert dest.join("dir1", "dir2").check()
|
assert dest.join("dir1", "dir2").check()
|
||||||
assert dest.join("dir1", "dir2", 'hello').check()
|
assert dest.join("dir1", "dir2", 'hello').check()
|
||||||
|
|
||||||
def test_hostmanager_init_rsync_topdir_explicit(self):
|
def test_hostmanager_init_rsync_topdir_explicit(self, source, dest):
|
||||||
dir2 = self.source.ensure("dir1", "dir2", dir=1)
|
dir2 = source.ensure("dir1", "dir2", dir=1)
|
||||||
dir2.ensure("hello")
|
dir2.ensure("hello")
|
||||||
hm = self.gethostmanager(
|
hm = self.gethostmanager(source,
|
||||||
dist_hosts = ["localhost:%s" % self.dest],
|
dist_hosts = ["localhost:%s" % dest],
|
||||||
dist_rsync_roots = [str(self.source)]
|
dist_rsync_roots = [str(source)]
|
||||||
)
|
)
|
||||||
assert hm.session.config.topdir == self.source
|
assert hm.session.config.topdir == source
|
||||||
hm.init_rsync()
|
hm.init_rsync()
|
||||||
dest = self.dest.join(self.source.basename)
|
dest = dest.join(source.basename)
|
||||||
assert dest.join("dir1").check()
|
assert dest.join("dir1").check()
|
||||||
assert dest.join("dir1", "dir2").check()
|
assert dest.join("dir1", "dir2").check()
|
||||||
assert dest.join("dir1", "dir2", 'hello').check()
|
assert dest.join("dir1", "dir2", 'hello').check()
|
||||||
|
|
||||||
def test_hostmanager_init_rsync_roots(self):
|
def test_hostmanager_init_rsync_roots(self, source, dest):
|
||||||
dir2 = self.source.ensure("dir1", "dir2", dir=1)
|
dir2 = source.ensure("dir1", "dir2", dir=1)
|
||||||
self.source.ensure("dir1", "somefile", dir=1)
|
source.ensure("dir1", "somefile", dir=1)
|
||||||
dir2.ensure("hello")
|
dir2.ensure("hello")
|
||||||
self.source.ensure("bogusdir", "file")
|
source.ensure("bogusdir", "file")
|
||||||
self.source.join("conftest.py").write(py.code.Source("""
|
source.join("conftest.py").write(py.code.Source("""
|
||||||
dist_rsync_roots = ['dir1/dir2']
|
dist_rsync_roots = ['dir1/dir2']
|
||||||
"""))
|
"""))
|
||||||
session = py.test.config._reparse([self.source]).initsession()
|
session = py.test.config._reparse([source]).initsession()
|
||||||
hm = HostManager(session,
|
hm = HostManager(session,
|
||||||
hosts=[Host("localhost:" + str(self.dest))])
|
hosts=[Host("localhost:" + str(dest))])
|
||||||
hm.init_rsync()
|
hm.init_rsync()
|
||||||
assert self.dest.join("dir2").check()
|
assert dest.join("dir2").check()
|
||||||
assert not self.dest.join("dir1").check()
|
assert not dest.join("dir1").check()
|
||||||
assert not self.dest.join("bogus").check()
|
assert not dest.join("bogus").check()
|
||||||
|
|
||||||
def test_hostmanager_rsync_ignore(self):
|
def test_hostmanager_rsync_ignore(self, source, dest):
|
||||||
dir2 = self.source.ensure("dir1", "dir2", dir=1)
|
dir2 = source.ensure("dir1", "dir2", dir=1)
|
||||||
dir5 = self.source.ensure("dir5", "dir6", "bogus")
|
dir5 = source.ensure("dir5", "dir6", "bogus")
|
||||||
dirf = self.source.ensure("dir5", "file")
|
dirf = source.ensure("dir5", "file")
|
||||||
dir2.ensure("hello")
|
dir2.ensure("hello")
|
||||||
self.source.join("conftest.py").write(py.code.Source("""
|
source.join("conftest.py").write(py.code.Source("""
|
||||||
dist_rsync_ignore = ['dir1/dir2', 'dir5/dir6']
|
dist_rsync_ignore = ['dir1/dir2', 'dir5/dir6']
|
||||||
"""))
|
"""))
|
||||||
session = py.test.config._reparse([self.source]).initsession()
|
session = py.test.config._reparse([source]).initsession()
|
||||||
hm = HostManager(session,
|
hm = HostManager(session,
|
||||||
hosts=[Host("localhost:" + str(self.dest))])
|
hosts=[Host("localhost:" + str(dest))])
|
||||||
hm.init_rsync()
|
hm.init_rsync()
|
||||||
assert self.dest.join("dir1").check()
|
assert dest.join("dir1").check()
|
||||||
assert not self.dest.join("dir1", "dir2").check()
|
assert not dest.join("dir1", "dir2").check()
|
||||||
assert self.dest.join("dir5","file").check()
|
assert dest.join("dir5","file").check()
|
||||||
assert not self.dest.join("dir6").check()
|
assert not dest.join("dir6").check()
|
||||||
|
|
||||||
def test_hostmanage_optimise_localhost(self):
|
def test_hostmanage_optimise_localhost(self, source, dest):
|
||||||
hosts = [Host("localhost") for i in range(3)]
|
hosts = [Host("localhost") for i in range(3)]
|
||||||
session = py.test.config._reparse([self.source]).initsession()
|
session = py.test.config._reparse([source]).initsession()
|
||||||
hm = HostManager(session, hosts=hosts)
|
hm = HostManager(session, hosts=hosts)
|
||||||
hm.init_rsync()
|
hm.init_rsync()
|
||||||
for host in hosts:
|
for host in hosts:
|
||||||
|
|
@ -275,28 +272,31 @@ class TestHostManager(TmpWithSourceDest):
|
||||||
assert host.gw_remotepath is None
|
assert host.gw_remotepath is None
|
||||||
assert not host.relpath
|
assert not host.relpath
|
||||||
|
|
||||||
def test_hostmanage_setup_hosts(self):
|
def test_hostmanage_setup_hosts(self, source):
|
||||||
hosts = [Host("localhost") for i in range(3)]
|
hosts = [Host("localhost") for i in range(3)]
|
||||||
session = py.test.config._reparse([self.source]).initsession()
|
session = py.test.config._reparse([source]).initsession()
|
||||||
hm = HostManager(session, hosts=hosts)
|
hm = HostManager(session, hosts=hosts)
|
||||||
queue = py.std.Queue.Queue()
|
queue = py.std.Queue.Queue()
|
||||||
hm.setup_hosts(notify=queue.put)
|
hm.setup_hosts(putevent=queue.put)
|
||||||
for host in hm.hosts:
|
for host in hm.hosts:
|
||||||
ev = queue.get(timeout=2.0)
|
eventcall = queue.get(timeout=2.0)
|
||||||
assert isinstance(ev, event.HostUp)
|
name, args, kwargs = eventcall
|
||||||
|
assert name == "hostup"
|
||||||
for host in hm.hosts:
|
for host in hm.hosts:
|
||||||
host.node.shutdown()
|
host.node.shutdown()
|
||||||
for host in hm.hosts:
|
for host in hm.hosts:
|
||||||
ev = queue.get(timeout=2.0)
|
eventcall = queue.get(timeout=2.0)
|
||||||
assert isinstance(ev, event.HostDown)
|
name, args, kwargs = eventcall
|
||||||
|
print name, args, kwargs
|
||||||
|
assert name == "hostdown"
|
||||||
|
|
||||||
def XXXtest_ssh_rsync_samehost_twice(self):
|
def XXXtest_ssh_rsync_samehost_twice(self):
|
||||||
#XXX we have no easy way to have a temp directory remotely!
|
#XXX we have no easy way to have a temp directory remotely!
|
||||||
option = py.test.config.option
|
option = py.test.config.option
|
||||||
if option.sshtarget is None:
|
if option.sshhost is None:
|
||||||
py.test.skip("no known ssh target, use -S to set one")
|
py.test.skip("no known ssh target, use -S to set one")
|
||||||
host1 = Host("%s" % (option.sshtarget, ))
|
host1 = Host("%s" % (option.sshhost, ))
|
||||||
host2 = Host("%s" % (option.sshtarget, ))
|
host2 = Host("%s" % (option.sshhost, ))
|
||||||
hm = HostManager(config, hosts=[host1, host2])
|
hm = HostManager(config, hosts=[host1, host2])
|
||||||
events = []
|
events = []
|
||||||
hm.init_rsync(events.append)
|
hm.init_rsync(events.append)
|
||||||
|
|
|
||||||
|
|
@ -2,99 +2,116 @@
|
||||||
import py
|
import py
|
||||||
from py.__.test.dsession.masterslave import MasterNode
|
from py.__.test.dsession.masterslave import MasterNode
|
||||||
from py.__.test.dsession.hostmanage import Host
|
from py.__.test.dsession.hostmanage import Host
|
||||||
from py.__.test import event
|
|
||||||
from py.__.test.testing import suptest
|
|
||||||
|
|
||||||
class TestMasterSlaveConnection(suptest.InlineCollection):
|
class EventQueue:
|
||||||
def getevent(self, eventtype=event.ItemTestReport, timeout=2.0):
|
def __init__(self, bus, queue=None):
|
||||||
|
if queue is None:
|
||||||
|
queue = py.std.Queue.Queue()
|
||||||
|
self.queue = queue
|
||||||
|
bus.register(self)
|
||||||
|
|
||||||
|
def pyevent(self, eventname, *args, **kwargs):
|
||||||
|
self.queue.put((eventname, args, kwargs))
|
||||||
|
|
||||||
|
def geteventargs(self, eventname, timeout=2.0):
|
||||||
events = []
|
events = []
|
||||||
while 1:
|
while 1:
|
||||||
try:
|
try:
|
||||||
ev = self.queue.get(timeout=timeout)
|
eventcall = self.queue.get(timeout=timeout)
|
||||||
except py.std.Queue.Empty:
|
except py.std.Queue.Empty:
|
||||||
print "node channel", self.node.channel
|
#print "node channel", self.node.channel
|
||||||
print "remoteerror", self.node.channel._getremoteerror()
|
#print "remoteerror", self.node.channel._getremoteerror()
|
||||||
print "seen events", events
|
print "seen events", events
|
||||||
raise IOError("did not see %r events" % (eventtype))
|
raise IOError("did not see %r events" % (eventname))
|
||||||
else:
|
else:
|
||||||
if isinstance(ev, eventtype):
|
name, args, kwargs = eventcall
|
||||||
return ev
|
assert isinstance(name, str)
|
||||||
events.append(ev)
|
if name == eventname:
|
||||||
|
return args
|
||||||
|
events.append(name)
|
||||||
|
|
||||||
def setup_method(self, method):
|
class MySetup:
|
||||||
super(TestMasterSlaveConnection, self).setup_method(method)
|
def __init__(self, pyfuncitem):
|
||||||
self.makepyfile(__init__="")
|
self.pyfuncitem = pyfuncitem
|
||||||
self.config = self.parseconfig(self.tmpdir)
|
|
||||||
|
def geteventargs(self, eventname, timeout=2.0):
|
||||||
|
eq = EventQueue(self.config.bus, self.queue)
|
||||||
|
return eq.geteventargs(eventname, timeout=timeout)
|
||||||
|
|
||||||
|
def makenode(self, config=None):
|
||||||
|
if config is None:
|
||||||
|
config = py.test.config._reparse([])
|
||||||
|
self.config = config
|
||||||
self.queue = py.std.Queue.Queue()
|
self.queue = py.std.Queue.Queue()
|
||||||
self.host = Host("localhost")
|
self.host = Host("localhost")
|
||||||
self.host.initgateway()
|
self.host.initgateway()
|
||||||
self.node = MasterNode(self.host, self.config, self.queue.put)
|
self.node = MasterNode(self.host, self.config, putevent=self.queue.put)
|
||||||
assert not self.node.channel.isclosed()
|
assert not self.node.channel.isclosed()
|
||||||
|
return self.node
|
||||||
|
|
||||||
def getitem(self, source):
|
def finalize(self):
|
||||||
kw = {"test_" + self.tmpdir.basename: py.code.Source(source).strip()}
|
if hasattr(self, 'host'):
|
||||||
path = self.makepyfile(**kw)
|
print "exiting:", self.host.gw
|
||||||
fscol = self.config.getfsnode(path)
|
self.host.gw.exit()
|
||||||
return fscol.collect_by_name("test_func")
|
|
||||||
|
|
||||||
def getitems(self, source):
|
def pytest_pyfuncarg_mysetup(pyfuncitem):
|
||||||
kw = {"test_" + self.tmpdir.basename: py.code.Source(source).strip()}
|
mysetup = MySetup(pyfuncitem)
|
||||||
path = self.makepyfile(**kw)
|
pyfuncitem.addfinalizer(mysetup.finalize)
|
||||||
fscol = self.config.getfsnode(path)
|
return mysetup
|
||||||
return fscol.collect()
|
|
||||||
|
|
||||||
def teardown_method(self, method):
|
class TestMasterSlaveConnection:
|
||||||
print "at teardown:", self.node.channel
|
|
||||||
#if not self.node.channel.isclosed():
|
|
||||||
# self.node.shutdown()
|
|
||||||
self.host.gw.exit()
|
|
||||||
|
|
||||||
def test_crash_invalid_item(self):
|
def test_crash_invalid_item(self, mysetup):
|
||||||
self.node.send(123) # invalid item
|
node = mysetup.makenode()
|
||||||
ev = self.getevent(event.HostDown)
|
node.send(123) # invalid item
|
||||||
assert ev.host == self.host
|
ev, = mysetup.geteventargs("hostdown")
|
||||||
|
assert ev.host == mysetup.host
|
||||||
assert str(ev.error).find("AttributeError") != -1
|
assert str(ev.error).find("AttributeError") != -1
|
||||||
|
|
||||||
def test_crash_killed(self):
|
def test_crash_killed(self, testdir, mysetup):
|
||||||
if not hasattr(py.std.os, 'kill'):
|
if not hasattr(py.std.os, 'kill'):
|
||||||
py.test.skip("no os.kill")
|
py.test.skip("no os.kill")
|
||||||
item = self.getitem("""
|
item = testdir.getitem("""
|
||||||
def test_func():
|
def test_func():
|
||||||
import os
|
import os
|
||||||
os.kill(os.getpid(), 15)
|
os.kill(os.getpid(), 15)
|
||||||
""")
|
""")
|
||||||
self.node.send(item)
|
node = mysetup.makenode(item._config)
|
||||||
ev = self.getevent(event.HostDown)
|
node.send(item)
|
||||||
assert ev.host == self.host
|
ev, = mysetup.geteventargs("hostdown")
|
||||||
|
assert ev.host == mysetup.host
|
||||||
assert str(ev.error).find("TERMINATED") != -1
|
assert str(ev.error).find("TERMINATED") != -1
|
||||||
|
|
||||||
def test_node_down(self):
|
def test_node_down(self, mysetup):
|
||||||
self.node.shutdown()
|
node = mysetup.makenode()
|
||||||
ev = self.getevent(event.HostDown)
|
node.shutdown()
|
||||||
assert ev.host == self.host
|
ev, = mysetup.geteventargs("hostdown")
|
||||||
|
assert ev.host == mysetup.host
|
||||||
assert not ev.error
|
assert not ev.error
|
||||||
self.node.callback(self.node.ENDMARK)
|
node.callback(node.ENDMARK)
|
||||||
excinfo = py.test.raises(IOError,
|
excinfo = py.test.raises(IOError,
|
||||||
"self.getevent(event.HostDown, timeout=0.01)")
|
"mysetup.geteventargs('hostdown', timeout=0.01)")
|
||||||
|
|
||||||
def test_send_on_closed_channel(self):
|
def test_send_on_closed_channel(self, testdir, mysetup):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
self.node.channel.close()
|
node = mysetup.makenode(item._config)
|
||||||
py.test.raises(IOError, "self.node.send(item)")
|
node.channel.close()
|
||||||
#ev = self.getevent(event.InternalException)
|
py.test.raises(IOError, "node.send(item)")
|
||||||
|
#ev = self.geteventargs(event.InternalException)
|
||||||
#assert ev.excinfo.errisinstance(IOError)
|
#assert ev.excinfo.errisinstance(IOError)
|
||||||
|
|
||||||
def test_send_one(self):
|
def test_send_one(self, testdir, mysetup):
|
||||||
item = self.getitem("def test_func(): pass")
|
item = testdir.getitem("def test_func(): pass")
|
||||||
self.node.send(item)
|
node = mysetup.makenode(item._config)
|
||||||
ev = self.getevent()
|
node.send(item)
|
||||||
|
ev, = mysetup.geteventargs("itemtestreport")
|
||||||
assert ev.passed
|
assert ev.passed
|
||||||
assert ev.colitem == item
|
assert ev.colitem == item
|
||||||
#assert event.item == item
|
#assert event.item == item
|
||||||
#assert event.item is not item
|
#assert event.item is not item
|
||||||
|
|
||||||
def test_send_some(self):
|
def test_send_some(self, testdir, mysetup):
|
||||||
items = self.getitems("""
|
items = testdir.getitems("""
|
||||||
def test_pass():
|
def test_pass():
|
||||||
pass
|
pass
|
||||||
def test_fail():
|
def test_fail():
|
||||||
|
|
@ -103,13 +120,14 @@ class TestMasterSlaveConnection(suptest.InlineCollection):
|
||||||
import py
|
import py
|
||||||
py.test.skip("x")
|
py.test.skip("x")
|
||||||
""")
|
""")
|
||||||
|
node = mysetup.makenode(items[0]._config)
|
||||||
for item in items:
|
for item in items:
|
||||||
self.node.send(item)
|
node.send(item)
|
||||||
for outcome in "passed failed skipped".split():
|
for outcome in "passed failed skipped".split():
|
||||||
ev = self.getevent()
|
ev, = mysetup.geteventargs("itemtestreport")
|
||||||
assert getattr(ev, outcome)
|
assert getattr(ev, outcome)
|
||||||
|
|
||||||
self.node.sendlist(items)
|
node.sendlist(items)
|
||||||
for outcome in "passed failed skipped".split():
|
for outcome in "passed failed skipped".split():
|
||||||
ev = self.getevent()
|
ev, = mysetup.geteventargs("itemtestreport")
|
||||||
assert getattr(ev, outcome)
|
assert getattr(ev, outcome)
|
||||||
|
|
|
||||||
|
|
@ -6,24 +6,6 @@ import py
|
||||||
import time
|
import time
|
||||||
from py.__.test.outcome import Skipped
|
from py.__.test.outcome import Skipped
|
||||||
|
|
||||||
class EventBus(object):
|
|
||||||
""" General Event Bus for distributing events. """
|
|
||||||
def __init__(self):
|
|
||||||
self._subscribers = []
|
|
||||||
|
|
||||||
def subscribe(self, callback):
|
|
||||||
""" subscribe given callback to bus events. """
|
|
||||||
self._subscribers.append(callback)
|
|
||||||
|
|
||||||
def unsubscribe(self, callback):
|
|
||||||
""" unsubscribe given callback from bus events. """
|
|
||||||
self._subscribers.remove(callback)
|
|
||||||
|
|
||||||
def notify(self, event):
|
|
||||||
for subscriber in self._subscribers:
|
|
||||||
subscriber(event)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseEvent(object):
|
class BaseEvent(object):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
l = ["%s=%s" %(key, value)
|
l = ["%s=%s" %(key, value)
|
||||||
|
|
@ -75,16 +57,8 @@ class Deselected(BaseEvent):
|
||||||
|
|
||||||
|
|
||||||
class BaseReport(BaseEvent):
|
class BaseReport(BaseEvent):
|
||||||
failed = passed = skipped = None
|
|
||||||
def __init__(self, colitem, **kwargs):
|
|
||||||
self.colitem = colitem
|
|
||||||
assert len(kwargs) == 1, kwargs
|
|
||||||
name, value = kwargs.items()[0]
|
|
||||||
setattr(self, name, True)
|
|
||||||
self.outcome = value
|
|
||||||
|
|
||||||
def toterminal(self, out):
|
def toterminal(self, out):
|
||||||
longrepr = self.outcome.longrepr
|
longrepr = self.longrepr
|
||||||
if hasattr(longrepr, 'toterminal'):
|
if hasattr(longrepr, 'toterminal'):
|
||||||
longrepr.toterminal(out)
|
longrepr.toterminal(out)
|
||||||
else:
|
else:
|
||||||
|
|
@ -92,6 +66,34 @@ class BaseReport(BaseEvent):
|
||||||
|
|
||||||
class ItemTestReport(BaseReport):
|
class ItemTestReport(BaseReport):
|
||||||
""" Test Execution Report. """
|
""" Test Execution Report. """
|
||||||
|
failed = passed = skipped = False
|
||||||
|
|
||||||
|
def __init__(self, colitem, excinfo=None, when=None, outerr=None):
|
||||||
|
self.colitem = colitem
|
||||||
|
self.keywords = colitem and colitem.readkeywords()
|
||||||
|
if not excinfo:
|
||||||
|
self.passed = True
|
||||||
|
self.shortrepr = "."
|
||||||
|
else:
|
||||||
|
self.when = when
|
||||||
|
if not isinstance(excinfo, py.code.ExceptionInfo):
|
||||||
|
self.failed = True
|
||||||
|
shortrepr = "?"
|
||||||
|
longrepr = excinfo
|
||||||
|
elif excinfo.errisinstance(Skipped):
|
||||||
|
self.skipped = True
|
||||||
|
shortrepr = "s"
|
||||||
|
longrepr = self.colitem._repr_failure_py(excinfo, outerr)
|
||||||
|
else:
|
||||||
|
self.failed = True
|
||||||
|
shortrepr = self.colitem.shortfailurerepr
|
||||||
|
if self.when == "execute":
|
||||||
|
longrepr = self.colitem.repr_failure(excinfo, outerr)
|
||||||
|
else: # exception in setup or teardown
|
||||||
|
longrepr = self.colitem._repr_failure_py(excinfo, outerr)
|
||||||
|
shortrepr = shortrepr.lower()
|
||||||
|
self.shortrepr = shortrepr
|
||||||
|
self.longrepr = longrepr
|
||||||
|
|
||||||
class CollectionStart(BaseEvent):
|
class CollectionStart(BaseEvent):
|
||||||
def __init__(self, collector):
|
def __init__(self, collector):
|
||||||
|
|
@ -99,9 +101,29 @@ class CollectionStart(BaseEvent):
|
||||||
|
|
||||||
class CollectionReport(BaseReport):
|
class CollectionReport(BaseReport):
|
||||||
""" Collection Report. """
|
""" Collection Report. """
|
||||||
def __init__(self, colitem, result, **kwargs):
|
skipped = failed = passed = False
|
||||||
super(CollectionReport, self).__init__(colitem, **kwargs)
|
|
||||||
self.result = result
|
def __init__(self, colitem, result, excinfo=None, when=None, outerr=None):
|
||||||
|
self.colitem = colitem
|
||||||
|
if not excinfo:
|
||||||
|
self.passed = True
|
||||||
|
self.result = result
|
||||||
|
else:
|
||||||
|
self.when = when
|
||||||
|
self.outerr = outerr
|
||||||
|
self.longrepr = self.colitem._repr_failure_py(excinfo, outerr)
|
||||||
|
if excinfo.errisinstance(Skipped):
|
||||||
|
self.skipped = True
|
||||||
|
self.reason = str(excinfo.value)
|
||||||
|
else:
|
||||||
|
self.failed = True
|
||||||
|
|
||||||
|
def toterminal(self, out):
|
||||||
|
longrepr = self.longrepr
|
||||||
|
if hasattr(longrepr, 'toterminal'):
|
||||||
|
longrepr.toterminal(out)
|
||||||
|
else:
|
||||||
|
out.line(str(longrepr))
|
||||||
|
|
||||||
class LooponfailingInfo(BaseEvent):
|
class LooponfailingInfo(BaseEvent):
|
||||||
def __init__(self, failreports, rootdirs):
|
def __init__(self, failreports, rootdirs):
|
||||||
|
|
@ -151,3 +173,12 @@ class HostRSyncRootReady(BaseEvent):
|
||||||
self.host = host
|
self.host = host
|
||||||
self.root = root
|
self.root = root
|
||||||
|
|
||||||
|
|
||||||
|
# make all eventclasses available on BaseEvent so that
|
||||||
|
# consumers of events can easily filter by
|
||||||
|
# 'isinstance(event, event.Name)' checks
|
||||||
|
|
||||||
|
for name, cls in vars().items():
|
||||||
|
if hasattr(cls, '__bases__') and issubclass(cls, BaseEvent):
|
||||||
|
setattr(BaseEvent, name, cls)
|
||||||
|
#
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ import py
|
||||||
from py.__.test.session import Session
|
from py.__.test.session import Session
|
||||||
from py.__.test.outcome import Failed, Passed, Skipped
|
from py.__.test.outcome import Failed, Passed, Skipped
|
||||||
from py.__.test.dsession.mypickle import PickleChannel
|
from py.__.test.dsession.mypickle import PickleChannel
|
||||||
from py.__.test.report.terminal import TerminalReporter
|
|
||||||
from py.__.test import event
|
from py.__.test import event
|
||||||
from py.__.test.looponfail import util
|
from py.__.test.looponfail import util
|
||||||
|
|
||||||
|
|
@ -83,8 +82,8 @@ class RemoteControl(object):
|
||||||
from py.__.test.looponfail.remote import slave_runsession
|
from py.__.test.looponfail.remote import slave_runsession
|
||||||
from py.__.test.dsession import masterslave
|
from py.__.test.dsession import masterslave
|
||||||
config = masterslave.receive_and_send_pickled_config(channel)
|
config = masterslave.receive_and_send_pickled_config(channel)
|
||||||
width, hasmarkup = channel.receive()
|
fullwidth, hasmarkup = channel.receive()
|
||||||
slave_runsession(channel, config, width, hasmarkup)
|
slave_runsession(channel, config, fullwidth, hasmarkup)
|
||||||
""", stdout=out, stderr=out)
|
""", stdout=out, stderr=out)
|
||||||
channel = PickleChannel(channel)
|
channel = PickleChannel(channel)
|
||||||
masterslave.send_and_receive_pickled_config(
|
masterslave.send_and_receive_pickled_config(
|
||||||
|
|
@ -117,7 +116,7 @@ class RemoteControl(object):
|
||||||
finally:
|
finally:
|
||||||
self.ensure_teardown()
|
self.ensure_teardown()
|
||||||
|
|
||||||
def slave_runsession(channel, config, width, hasmarkup):
|
def slave_runsession(channel, config, fullwidth, hasmarkup):
|
||||||
""" we run this on the other side. """
|
""" we run this on the other side. """
|
||||||
if config.option.debug:
|
if config.option.debug:
|
||||||
def DEBUG(*args):
|
def DEBUG(*args):
|
||||||
|
|
@ -134,8 +133,10 @@ def slave_runsession(channel, config, width, hasmarkup):
|
||||||
|
|
||||||
DEBUG("SLAVE: initsession()")
|
DEBUG("SLAVE: initsession()")
|
||||||
session = config.initsession()
|
session = config.initsession()
|
||||||
session.reporter._tw.hasmarkup = hasmarkup
|
# XXX configure the reporter object's terminal writer more directly
|
||||||
session.reporter._tw.fullwidth = width
|
# XXX and write a test for this remote-terminal setting logic
|
||||||
|
config.pytest_terminal_hasmarkup = hasmarkup
|
||||||
|
config.pytest_terminal_fullwidth = fullwidth
|
||||||
if trails:
|
if trails:
|
||||||
colitems = []
|
colitems = []
|
||||||
for trail in trails:
|
for trail in trails:
|
||||||
|
|
@ -148,19 +149,18 @@ def slave_runsession(channel, config, width, hasmarkup):
|
||||||
else:
|
else:
|
||||||
colitems = None
|
colitems = None
|
||||||
session.shouldclose = channel.isclosed
|
session.shouldclose = channel.isclosed
|
||||||
#def sendevent(ev):
|
|
||||||
# channel.send(ev)
|
class Failures(list):
|
||||||
#session.bus.subscribe(sendevent)
|
def pyevent_itemtestreport(self, ev):
|
||||||
failreports = []
|
|
||||||
def recordfailures(ev):
|
|
||||||
if isinstance(ev, event.BaseReport):
|
|
||||||
if ev.failed:
|
if ev.failed:
|
||||||
failreports.append(ev)
|
self.append(ev)
|
||||||
session.bus.subscribe(recordfailures)
|
pyevent_collectionreport = pyevent_itemtestreport
|
||||||
|
|
||||||
|
failreports = Failures()
|
||||||
|
session.bus.register(failreports)
|
||||||
|
|
||||||
DEBUG("SLAVE: starting session.main()")
|
DEBUG("SLAVE: starting session.main()")
|
||||||
session.main(colitems)
|
session.main(colitems)
|
||||||
session.bus.unsubscribe(recordfailures)
|
ev = event.LooponfailingInfo(list(failreports), [config.topdir])
|
||||||
ev = event.LooponfailingInfo(failreports, [config.topdir])
|
session.bus.notify("looponfailinginfo", ev)
|
||||||
session.bus.notify(ev)
|
|
||||||
channel.send([x.colitem._totrail() for x in failreports if x.failed])
|
channel.send([x.colitem._totrail() for x in failreports if x.failed])
|
||||||
|
|
|
||||||
|
|
@ -1,33 +1,16 @@
|
||||||
import py
|
import py
|
||||||
from py.__.test.testing import suptest
|
|
||||||
from py.__.test.looponfail.remote import LooponfailingSession, LoopState, RemoteControl
|
from py.__.test.looponfail.remote import LooponfailingSession, LoopState, RemoteControl
|
||||||
from py.__.test import event
|
|
||||||
|
|
||||||
def getevent(l, evtype):
|
class TestRemoteControl:
|
||||||
result = getevents(l, evtype)
|
def test_nofailures(self, testdir):
|
||||||
if not result:
|
item = testdir.getitem("def test_func(): pass\n")
|
||||||
raise ValueError("event %r not found in %r" %(evtype, l))
|
|
||||||
return result[0]
|
|
||||||
|
|
||||||
def getevents(l, evtype):
|
|
||||||
result = []
|
|
||||||
for ev in l:
|
|
||||||
if isinstance(ev, evtype):
|
|
||||||
result.append(ev)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
class TestRemoteControl(suptest.InlineCollection):
|
|
||||||
def test_nofailures(self):
|
|
||||||
item = self.getitem("def test_func(): pass\n")
|
|
||||||
events = []
|
|
||||||
control = RemoteControl(item._config)
|
control = RemoteControl(item._config)
|
||||||
control.setup()
|
control.setup()
|
||||||
failures = control.runsession()
|
failures = control.runsession()
|
||||||
assert not failures
|
assert not failures
|
||||||
|
|
||||||
def test_failures(self):
|
def test_failures_somewhere(self, testdir):
|
||||||
item = self.getitem("def test_func(): assert 0\n")
|
item = testdir.getitem("def test_func(): assert 0\n")
|
||||||
control = RemoteControl(item._config)
|
control = RemoteControl(item._config)
|
||||||
control.setup()
|
control.setup()
|
||||||
failures = control.runsession()
|
failures = control.runsession()
|
||||||
|
|
@ -38,8 +21,8 @@ class TestRemoteControl(suptest.InlineCollection):
|
||||||
failures = control.runsession(failures)
|
failures = control.runsession(failures)
|
||||||
assert not failures
|
assert not failures
|
||||||
|
|
||||||
def test_failure_change(self):
|
def test_failure_change(self, testdir):
|
||||||
modcol = self.getitem("""
|
modcol = testdir.getitem("""
|
||||||
def test_func():
|
def test_func():
|
||||||
assert 0
|
assert 0
|
||||||
""")
|
""")
|
||||||
|
|
@ -62,9 +45,9 @@ class TestRemoteControl(suptest.InlineCollection):
|
||||||
assert failures
|
assert failures
|
||||||
assert str(failures).find("test_new") != -1
|
assert str(failures).find("test_new") != -1
|
||||||
|
|
||||||
class TestLooponFailing(suptest.InlineCollection):
|
class TestLooponFailing:
|
||||||
def test_looponfailing_from_fail_to_ok(self):
|
def test_looponfailing_from_fail_to_ok(self, testdir):
|
||||||
modcol = self.getmodulecol("""
|
modcol = testdir.getmodulecol("""
|
||||||
def test_one():
|
def test_one():
|
||||||
x = 0
|
x = 0
|
||||||
assert x == 1
|
assert x == 1
|
||||||
|
|
@ -88,8 +71,8 @@ class TestLooponFailing(suptest.InlineCollection):
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
assert not loopstate.colitems
|
assert not loopstate.colitems
|
||||||
|
|
||||||
def test_looponfailing_from_one_to_two_tests(self):
|
def test_looponfailing_from_one_to_two_tests(self, testdir):
|
||||||
modcol = self.getmodulecol("""
|
modcol = testdir.getmodulecol("""
|
||||||
def test_one():
|
def test_one():
|
||||||
assert 0
|
assert 0
|
||||||
""")
|
""")
|
||||||
|
|
@ -113,8 +96,8 @@ class TestLooponFailing(suptest.InlineCollection):
|
||||||
session.loop_once(loopstate)
|
session.loop_once(loopstate)
|
||||||
assert len(loopstate.colitems) == 1
|
assert len(loopstate.colitems) == 1
|
||||||
|
|
||||||
def test_looponfailing_removed_test(self):
|
def test_looponfailing_removed_test(self, testdir):
|
||||||
modcol = self.getmodulecol("""
|
modcol = testdir.getmodulecol("""
|
||||||
def test_one():
|
def test_one():
|
||||||
assert 0
|
assert 0
|
||||||
def test_two():
|
def test_two():
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
import py
|
import py
|
||||||
from py.__.test.looponfail.util import StatRecorder, EventRecorder
|
from py.__.test.looponfail.util import StatRecorder
|
||||||
from py.__.test import event
|
|
||||||
|
|
||||||
def test_filechange():
|
def test_filechange(tmpdir):
|
||||||
tmp = py.test.ensuretemp("test_filechange")
|
tmp = tmpdir
|
||||||
hello = tmp.ensure("hello.py")
|
hello = tmp.ensure("hello.py")
|
||||||
sd = StatRecorder([tmp])
|
sd = StatRecorder([tmp])
|
||||||
changed = sd.check()
|
changed = sd.check()
|
||||||
|
|
@ -35,8 +34,8 @@ def test_filechange():
|
||||||
changed = sd.check()
|
changed = sd.check()
|
||||||
assert changed
|
assert changed
|
||||||
|
|
||||||
def test_pycremoval():
|
def test_pycremoval(tmpdir):
|
||||||
tmp = py.test.ensuretemp("test_pycremoval")
|
tmp = tmpdir
|
||||||
hello = tmp.ensure("hello.py")
|
hello = tmp.ensure("hello.py")
|
||||||
sd = StatRecorder([tmp])
|
sd = StatRecorder([tmp])
|
||||||
changed = sd.check()
|
changed = sd.check()
|
||||||
|
|
@ -52,8 +51,8 @@ def test_pycremoval():
|
||||||
assert not pycfile.check()
|
assert not pycfile.check()
|
||||||
|
|
||||||
|
|
||||||
def test_waitonchange():
|
def test_waitonchange(tmpdir):
|
||||||
tmp = py.test.ensuretemp("test_waitonchange")
|
tmp = tmpdir
|
||||||
sd = StatRecorder([tmp])
|
sd = StatRecorder([tmp])
|
||||||
|
|
||||||
wp = py._thread.WorkerPool(1)
|
wp = py._thread.WorkerPool(1)
|
||||||
|
|
@ -63,28 +62,3 @@ def test_waitonchange():
|
||||||
reply.get(timeout=0.5)
|
reply.get(timeout=0.5)
|
||||||
wp.shutdown()
|
wp.shutdown()
|
||||||
|
|
||||||
def test_eventrecorder():
|
|
||||||
bus = event.EventBus()
|
|
||||||
recorder = EventRecorder(bus)
|
|
||||||
bus.notify(event.NOP())
|
|
||||||
assert recorder.events
|
|
||||||
assert not recorder.getfailures()
|
|
||||||
rep = event.ItemTestReport(None, failed=True)
|
|
||||||
bus.notify(rep)
|
|
||||||
failures = recorder.getfailures()
|
|
||||||
assert failures == [rep]
|
|
||||||
recorder.clear()
|
|
||||||
assert not recorder.events
|
|
||||||
assert not recorder.getfailures()
|
|
||||||
recorder.unsubscribe()
|
|
||||||
bus.notify(rep)
|
|
||||||
assert not recorder.events
|
|
||||||
assert not recorder.getfailures()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,19 +52,3 @@ class StatRecorder:
|
||||||
self.statcache = newstat
|
self.statcache = newstat
|
||||||
return changed
|
return changed
|
||||||
|
|
||||||
|
|
||||||
class EventRecorder(object):
|
|
||||||
def __init__(self, bus):
|
|
||||||
self.events = []
|
|
||||||
self.bus = bus
|
|
||||||
self.bus.subscribe(self.events.append)
|
|
||||||
|
|
||||||
def getfailures(self):
|
|
||||||
return [ev for ev in self.events
|
|
||||||
if isinstance(ev, event.BaseReport) and \
|
|
||||||
ev.failed]
|
|
||||||
def clear(self):
|
|
||||||
self.events[:] = []
|
|
||||||
|
|
||||||
def unsubscribe(self):
|
|
||||||
self.bus.unsubscribe(self.events.append)
|
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,13 @@ def deprecated_call(func, *args, **kwargs):
|
||||||
raise AssertionError("%r did not produce DeprecationWarning" %(func,))
|
raise AssertionError("%r did not produce DeprecationWarning" %(func,))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
class keywords:
|
||||||
|
""" decorator for setting function attributes. """
|
||||||
|
def __init__(self, **kw):
|
||||||
|
self.kw = kw
|
||||||
|
def __call__(self, func):
|
||||||
|
func.func_dict.update(self.kw)
|
||||||
|
return func
|
||||||
|
|
||||||
# exitcodes for the command line
|
# exitcodes for the command line
|
||||||
EXIT_OK = 0
|
EXIT_OK = 0
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
"""
|
||||||
|
thin wrapper around Python's optparse.py
|
||||||
|
adding some extra checks and ways to systematically
|
||||||
|
have Environment variables provide default values
|
||||||
|
for options. basic usage:
|
||||||
|
|
||||||
|
>>> parser = Parser()
|
||||||
|
>>> parser.addoption("--hello", action="store_true", dest="hello")
|
||||||
|
>>> option, args = parser.parse(['--hello'])
|
||||||
|
>>> option.hello
|
||||||
|
True
|
||||||
|
>>> args
|
||||||
|
[]
|
||||||
|
|
||||||
|
"""
|
||||||
|
import py
|
||||||
|
from py.compat import optparse
|
||||||
|
|
||||||
|
class Parser:
|
||||||
|
""" Parser for command line arguments. """
|
||||||
|
|
||||||
|
def __init__(self, usage=None, processopt=None):
|
||||||
|
self._anonymous = OptionGroup("misc", parser=self)
|
||||||
|
self._groups = [self._anonymous]
|
||||||
|
self._processopt = processopt
|
||||||
|
self._usage = usage
|
||||||
|
|
||||||
|
def processoption(self, option):
|
||||||
|
if self._processopt:
|
||||||
|
if option.dest:
|
||||||
|
self._processopt(option)
|
||||||
|
|
||||||
|
def addgroup(self, name, description=""):
|
||||||
|
for group in self._groups:
|
||||||
|
if group.name == name:
|
||||||
|
raise ValueError("group %r already exists" % name)
|
||||||
|
group = OptionGroup(name, description, parser=self)
|
||||||
|
self._groups.append(group)
|
||||||
|
return group
|
||||||
|
|
||||||
|
def getgroup(self, name):
|
||||||
|
for group in self._groups:
|
||||||
|
if group.name == name:
|
||||||
|
return group
|
||||||
|
raise ValueError("group %r not found" %(name,))
|
||||||
|
|
||||||
|
def addoption(self, *opts, **attrs):
|
||||||
|
""" add an optparse-style option. """
|
||||||
|
self._anonymous.addoption(*opts, **attrs)
|
||||||
|
|
||||||
|
def parse(self, args):
|
||||||
|
optparser = optparse.OptionParser(usage=self._usage)
|
||||||
|
for group in self._groups:
|
||||||
|
if group.options:
|
||||||
|
optgroup = optparse.OptionGroup(optparser, group.name)
|
||||||
|
optgroup.add_options(group.options)
|
||||||
|
optparser.add_option_group(optgroup)
|
||||||
|
return optparser.parse_args([str(x) for x in args])
|
||||||
|
|
||||||
|
def parse_setoption(self, args, option):
|
||||||
|
parsedoption, args = self.parse(args)
|
||||||
|
for name, value in parsedoption.__dict__.items():
|
||||||
|
setattr(option, name, value)
|
||||||
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
class OptionGroup:
|
||||||
|
def __init__(self, name, description="", parser=None):
|
||||||
|
self.name = name
|
||||||
|
self.description = description
|
||||||
|
self.options = []
|
||||||
|
self.parser = parser
|
||||||
|
|
||||||
|
def addoption(self, *optnames, **attrs):
|
||||||
|
""" add an option to this group. """
|
||||||
|
option = py.compat.optparse.Option(*optnames, **attrs)
|
||||||
|
self._addoption_instance(option, shortupper=False)
|
||||||
|
|
||||||
|
def _addoption(self, *optnames, **attrs):
|
||||||
|
option = py.compat.optparse.Option(*optnames, **attrs)
|
||||||
|
self._addoption_instance(option, shortupper=True)
|
||||||
|
|
||||||
|
def _addoption_instance(self, option, shortupper=False):
|
||||||
|
if not shortupper:
|
||||||
|
for opt in option._short_opts:
|
||||||
|
if opt[0] == '-' and opt[1].islower():
|
||||||
|
raise ValueError("lowercase shortoptions reserved")
|
||||||
|
if self.parser:
|
||||||
|
self.parser.processoption(option)
|
||||||
|
self.options.append(option)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
#
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
import py
|
||||||
|
|
||||||
|
pytest_plugins = "pytester", "plugintester"
|
||||||
|
|
||||||
|
class ConftestPlugin:
|
||||||
|
def pytest_collect_file(self, path, parent):
|
||||||
|
if path.basename.startswith("pytest_") and path.ext == ".py":
|
||||||
|
mod = parent.Module(path, parent=parent)
|
||||||
|
return mod
|
||||||
|
|
||||||
|
|
@ -0,0 +1,82 @@
|
||||||
|
import py
|
||||||
|
|
||||||
|
class ApigenPlugin:
|
||||||
|
def pytest_addoption(self, parser):
|
||||||
|
group = parser.addgroup("apigen options")
|
||||||
|
group.addoption('--apigen', action="store_true", dest="apigen",
|
||||||
|
help="generate api documentation")
|
||||||
|
#group.addoption('--apigenpath',
|
||||||
|
# action="store", dest="apigenpath",
|
||||||
|
# default="../apigen",
|
||||||
|
# type="string",
|
||||||
|
# help="relative path to apigen doc output location (relative from py/)")
|
||||||
|
#group.addoption('--docpath',
|
||||||
|
# action='store', dest='docpath',
|
||||||
|
# default="doc", type='string',
|
||||||
|
# help="relative path to doc output location (relative from py/)")
|
||||||
|
|
||||||
|
def pytest_configure(self, config):
|
||||||
|
if config.option.apigen:
|
||||||
|
from py.__.apigen.tracer.tracer import Tracer, DocStorage
|
||||||
|
self.pkgdir = py.path.local(config.args[0]).pypkgpath()
|
||||||
|
apigenscriptpath = py.path.local(py.__file__).dirpath("apigen", "apigen.py")
|
||||||
|
apigenscript = apigenscriptpath.pyimport()
|
||||||
|
if not hasattr(apigenscript, 'get_documentable_items'):
|
||||||
|
raise NotImplementedError("%r needs to provide get_documentable_items" %(
|
||||||
|
apigenscriptpath,))
|
||||||
|
self.apigenscript = apigenscript
|
||||||
|
pkgname, items = apigenscript.get_documentable_items(self.pkgdir)
|
||||||
|
self.docstorage = DocStorage().from_dict(items,
|
||||||
|
module_name=pkgname)
|
||||||
|
self.tracer = Tracer(self.docstorage)
|
||||||
|
|
||||||
|
def pytest_pyfunc_call(self, pyfuncitem, args, kwargs):
|
||||||
|
if hasattr(self, 'tracer'):
|
||||||
|
self.tracer.start_tracing()
|
||||||
|
try:
|
||||||
|
pyfuncitem.obj(*args, **kwargs)
|
||||||
|
finally:
|
||||||
|
self.tracer.end_tracing()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def pytest_terminal_summary(self, terminalreporter):
|
||||||
|
if hasattr(self, 'tracer'):
|
||||||
|
tr = terminalreporter
|
||||||
|
from py.__.apigen.tracer.docstorage import DocStorageAccessor
|
||||||
|
terminalreporter.write_sep("=", "apigen: building documentation")
|
||||||
|
#assert hasattr(tr.config.option, 'apigenpath')
|
||||||
|
capture = py.io.StdCaptureFD()
|
||||||
|
try:
|
||||||
|
self.apigenscript.build(
|
||||||
|
tr.config,
|
||||||
|
self.pkgdir,
|
||||||
|
DocStorageAccessor(self.docstorage),
|
||||||
|
capture)
|
||||||
|
finally:
|
||||||
|
capture.reset()
|
||||||
|
terminalreporter.write_line("apigen build completed")
|
||||||
|
|
||||||
|
def test_generic(plugintester):
|
||||||
|
plugintester.apicheck(ApigenPlugin)
|
||||||
|
|
||||||
|
def test_functional_simple(testdir):
|
||||||
|
sub = testdir.tmpdir.mkdir("test_simple")
|
||||||
|
sub.join("__init__.py").write(py.code.Source("""
|
||||||
|
from py import initpkg
|
||||||
|
initpkg(__name__, exportdefs={
|
||||||
|
'simple.f': ('./test_simple.py', 'f',),
|
||||||
|
})
|
||||||
|
"""))
|
||||||
|
pyfile = sub.join("test_simple.py")
|
||||||
|
pyfile.write(py.code.Source("""
|
||||||
|
def f(arg):
|
||||||
|
pass
|
||||||
|
def test_f():
|
||||||
|
f(42)
|
||||||
|
"""))
|
||||||
|
testdir.makepyfile(conftest="pytest_plugins='apigen'")
|
||||||
|
result = testdir.runpytest(pyfile, "--apigen")
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
"*apigen: building documentation*",
|
||||||
|
"apigen build completed",
|
||||||
|
])
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
class DefaultPlugin:
|
||||||
|
""" Plugin implementing defaults and general options. """
|
||||||
|
|
||||||
|
def pytest_collect_file(self, path, parent):
|
||||||
|
ext = path.ext
|
||||||
|
pb = path.purebasename
|
||||||
|
if pb.startswith("test_") or pb.endswith("_test") or \
|
||||||
|
path in parent._config.args:
|
||||||
|
if ext == ".py":
|
||||||
|
return parent.Module(path, parent=parent)
|
||||||
|
|
||||||
|
def pytest_addoption(self, parser):
|
||||||
|
group = parser.addgroup("general", "general options")
|
||||||
|
group._addoption('-v', '--verbose', action="count",
|
||||||
|
dest="verbose", default=0, help="increase verbosity."),
|
||||||
|
group._addoption('-x', '--exitfirst',
|
||||||
|
action="store_true", dest="exitfirst", default=False,
|
||||||
|
help="exit instantly on first error or failed test."),
|
||||||
|
group._addoption('-s', '--nocapture',
|
||||||
|
action="store_true", dest="nocapture", default=False,
|
||||||
|
help="disable catching of sys.stdout/stderr output."),
|
||||||
|
group._addoption('-k',
|
||||||
|
action="store", dest="keyword", default='',
|
||||||
|
help="only run test items matching the given "
|
||||||
|
"space separated keywords. precede a keyword with '-' to negate. "
|
||||||
|
"Terminate the expression with ':' to treat a match as a signal "
|
||||||
|
"to run all subsequent tests. ")
|
||||||
|
group._addoption('-l', '--showlocals',
|
||||||
|
action="store_true", dest="showlocals", default=False,
|
||||||
|
help="show locals in tracebacks (disabled by default)."),
|
||||||
|
group._addoption('--showskipsummary',
|
||||||
|
action="store_true", dest="showskipsummary", default=False,
|
||||||
|
help="always show summary of skipped tests"),
|
||||||
|
group._addoption('', '--pdb',
|
||||||
|
action="store_true", dest="usepdb", default=False,
|
||||||
|
help="start pdb (the Python debugger) on errors."),
|
||||||
|
group._addoption('', '--tb',
|
||||||
|
action="store", dest="tbstyle", default='long',
|
||||||
|
type="choice", choices=['long', 'short', 'no'],
|
||||||
|
help="traceback verboseness (long/short/no)."),
|
||||||
|
group._addoption('', '--fulltrace',
|
||||||
|
action="store_true", dest="fulltrace", default=False,
|
||||||
|
help="don't cut any tracebacks (default is to cut)."),
|
||||||
|
group._addoption('', '--nomagic',
|
||||||
|
action="store_true", dest="nomagic", default=False,
|
||||||
|
help="refrain from using magic as much as possible."),
|
||||||
|
group._addoption('', '--traceconfig',
|
||||||
|
action="store_true", dest="traceconfig", default=False,
|
||||||
|
help="trace considerations of conftest.py files."),
|
||||||
|
group._addoption('-f', '--looponfailing',
|
||||||
|
action="store_true", dest="looponfailing", default=False,
|
||||||
|
help="loop on failing test set."),
|
||||||
|
group._addoption('', '--exec',
|
||||||
|
action="store", dest="executable", default=None,
|
||||||
|
help="python executable to run the tests with."),
|
||||||
|
group._addoption('-n', '--numprocesses', dest="numprocesses", default=0,
|
||||||
|
action="store", type="int",
|
||||||
|
help="number of local test processes."),
|
||||||
|
group._addoption('', '--debug',
|
||||||
|
action="store_true", dest="debug", default=False,
|
||||||
|
help="turn on debugging information."),
|
||||||
|
|
||||||
|
group = parser.addgroup("experimental", "experimental options")
|
||||||
|
group._addoption('-d', '--dist',
|
||||||
|
action="store_true", dest="dist", default=False,
|
||||||
|
help="ad-hoc distribute tests across machines (requires conftest settings)"),
|
||||||
|
group._addoption('-w', '--startserver',
|
||||||
|
action="store_true", dest="startserver", default=False,
|
||||||
|
help="starts local web server for displaying test progress.",
|
||||||
|
),
|
||||||
|
group._addoption('-r', '--runbrowser',
|
||||||
|
action="store_true", dest="runbrowser", default=False,
|
||||||
|
help="run browser (implies --startserver)."
|
||||||
|
),
|
||||||
|
group._addoption('', '--boxed',
|
||||||
|
action="store_true", dest="boxed", default=False,
|
||||||
|
help="box each test run in a separate process"),
|
||||||
|
group._addoption('', '--rest',
|
||||||
|
action='store_true', dest="restreport", default=False,
|
||||||
|
help="restructured text output reporting."),
|
||||||
|
group._addoption('', '--session',
|
||||||
|
action="store", dest="session", default=None,
|
||||||
|
help="lookup given sessioname in conftest.py files and use it."),
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
import py
|
||||||
|
|
||||||
|
class DoctestPlugin:
|
||||||
|
def pytest_addoption(self, parser):
|
||||||
|
parser.addoption("--doctest-modules",
|
||||||
|
action="store_true", dest="doctestmodules")
|
||||||
|
|
||||||
|
def pytest_collect_file(self, path, parent):
|
||||||
|
if path.ext == ".py":
|
||||||
|
if parent._config.getvalue("doctestmodules"):
|
||||||
|
return DoctestModule(path, parent)
|
||||||
|
if path.check(fnmatch="test_*.txt"):
|
||||||
|
return DoctestTextfile(path, parent)
|
||||||
|
|
||||||
|
from py.__.code.excinfo import Repr, ReprFileLocation
|
||||||
|
|
||||||
|
class ReprFailDoctest(Repr):
|
||||||
|
def __init__(self, reprlocation, lines):
|
||||||
|
self.reprlocation = reprlocation
|
||||||
|
self.lines = lines
|
||||||
|
def toterminal(self, tw):
|
||||||
|
for line in self.lines:
|
||||||
|
tw.line(line)
|
||||||
|
self.reprlocation.toterminal(tw)
|
||||||
|
|
||||||
|
class DoctestItem(py.test.collect.Item):
|
||||||
|
def __init__(self, path, parent):
|
||||||
|
name = self.__class__.__name__ + ":" + path.basename
|
||||||
|
super(DoctestItem, self).__init__(name=name, parent=parent)
|
||||||
|
self.fspath = path
|
||||||
|
|
||||||
|
def repr_failure(self, excinfo, outerr):
|
||||||
|
if excinfo.errisinstance(py.compat.doctest.DocTestFailure):
|
||||||
|
doctestfailure = excinfo.value
|
||||||
|
example = doctestfailure.example
|
||||||
|
test = doctestfailure.test
|
||||||
|
filename = test.filename
|
||||||
|
lineno = example.lineno + 1
|
||||||
|
message = excinfo.type.__name__
|
||||||
|
reprlocation = ReprFileLocation(filename, lineno, message)
|
||||||
|
checker = py.compat.doctest.OutputChecker()
|
||||||
|
REPORT_UDIFF = py.compat.doctest.REPORT_UDIFF
|
||||||
|
filelines = py.path.local(filename).readlines(cr=0)
|
||||||
|
i = max(0, lineno - 10)
|
||||||
|
lines = []
|
||||||
|
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")
|
||||||
|
return ReprFailDoctest(reprlocation, lines)
|
||||||
|
elif excinfo.errisinstance(py.compat.doctest.UnexpectedException):
|
||||||
|
excinfo = py.code.ExceptionInfo(excinfo.value.exc_info)
|
||||||
|
return super(DoctestItem, self).repr_failure(excinfo, outerr)
|
||||||
|
else:
|
||||||
|
return super(DoctestItem, self).repr_failure(excinfo, outerr)
|
||||||
|
|
||||||
|
class DoctestTextfile(DoctestItem):
|
||||||
|
def runtest(self):
|
||||||
|
if not self._deprecated_testexecution():
|
||||||
|
failed, tot = py.compat.doctest.testfile(
|
||||||
|
str(self.fspath), module_relative=False,
|
||||||
|
raise_on_error=True, verbose=0)
|
||||||
|
|
||||||
|
class DoctestModule(DoctestItem):
|
||||||
|
def runtest(self):
|
||||||
|
module = self.fspath.pyimport()
|
||||||
|
failed, tot = py.compat.doctest.testmod(
|
||||||
|
module, raise_on_error=True, verbose=0)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Plugin tests
|
||||||
|
#
|
||||||
|
|
||||||
|
class TestDoctests:
|
||||||
|
def test_collect_testtextfile(self, testdir):
|
||||||
|
testdir.plugins.append(DoctestPlugin())
|
||||||
|
testdir.maketxtfile(whatever="")
|
||||||
|
checkfile = testdir.maketxtfile(test_something="""
|
||||||
|
alskdjalsdk
|
||||||
|
>>> i = 5
|
||||||
|
>>> i-1
|
||||||
|
4
|
||||||
|
""")
|
||||||
|
for x in (testdir.tmpdir, checkfile):
|
||||||
|
#print "checking that %s returns custom items" % (x,)
|
||||||
|
items, events = testdir.inline_genitems(x)
|
||||||
|
print events.events
|
||||||
|
assert len(items) == 1
|
||||||
|
assert isinstance(items[0], DoctestTextfile)
|
||||||
|
|
||||||
|
def test_collect_module(self, testdir):
|
||||||
|
testdir.plugins.append(DoctestPlugin())
|
||||||
|
path = testdir.makepyfile(whatever="#")
|
||||||
|
for p in (path, testdir.tmpdir):
|
||||||
|
items, evrec = testdir.inline_genitems(p, '--doctest-modules')
|
||||||
|
print evrec.events
|
||||||
|
assert len(items) == 1
|
||||||
|
assert isinstance(items[0], DoctestModule)
|
||||||
|
|
||||||
|
def test_simple_doctestfile(self, testdir):
|
||||||
|
testdir.plugins.append(DoctestPlugin())
|
||||||
|
p = testdir.maketxtfile(test_doc="""
|
||||||
|
>>> x = 1
|
||||||
|
>>> x == 1
|
||||||
|
False
|
||||||
|
""")
|
||||||
|
events = testdir.inline_run_with_plugins(p)
|
||||||
|
ev, = events.getnamed("itemtestreport")
|
||||||
|
assert ev.failed
|
||||||
|
|
||||||
|
def test_doctest_unexpected_exception(self, testdir):
|
||||||
|
from py.__.test.outcome import Failed
|
||||||
|
|
||||||
|
testdir.plugins.append(DoctestPlugin())
|
||||||
|
p = testdir.maketxtfile("""
|
||||||
|
>>> i = 0
|
||||||
|
>>> i = 1
|
||||||
|
>>> x
|
||||||
|
2
|
||||||
|
""")
|
||||||
|
sorter = testdir.inline_run(p)
|
||||||
|
events = sorter.getnamed("itemtestreport")
|
||||||
|
assert len(events) == 1
|
||||||
|
ev, = events
|
||||||
|
assert ev.failed
|
||||||
|
assert ev.longrepr
|
||||||
|
# XXX
|
||||||
|
#testitem, = items
|
||||||
|
#excinfo = py.test.raises(Failed, "testitem.runtest()")
|
||||||
|
#repr = testitem.repr_failure(excinfo, ("", ""))
|
||||||
|
#assert repr.reprlocation
|
||||||
|
|
||||||
|
def test_doctestmodule(self, testdir):
|
||||||
|
testdir.plugins.append(DoctestPlugin())
|
||||||
|
p = testdir.makepyfile("""
|
||||||
|
'''
|
||||||
|
>>> x = 1
|
||||||
|
>>> x == 1
|
||||||
|
False
|
||||||
|
|
||||||
|
'''
|
||||||
|
""")
|
||||||
|
events = testdir.inline_run_with_plugins(p, "--doctest-modules")
|
||||||
|
ev, = events.getnamed("itemtestreport")
|
||||||
|
assert ev.failed
|
||||||
|
|
||||||
|
def test_txtfile_failing(self, testdir):
|
||||||
|
testdir.plugins.append('pytest_doctest')
|
||||||
|
p = testdir.maketxtfile("""
|
||||||
|
>>> i = 0
|
||||||
|
>>> i + 1
|
||||||
|
2
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
result.stdout.fnmatch_lines([
|
||||||
|
'001 >>> i = 0',
|
||||||
|
'002 >>> i + 1',
|
||||||
|
'Expected:',
|
||||||
|
" 2",
|
||||||
|
"Got:",
|
||||||
|
" 1",
|
||||||
|
"*test_txtfile_failing.txt:2: DocTestFailure"
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def test_generic(plugintester):
|
||||||
|
plugintester.apicheck(DoctestPlugin)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import py
|
||||||
|
|
||||||
|
class EventlogPlugin:
|
||||||
|
""" log pytest events to a file. """
|
||||||
|
def pytest_addoption(self, parser):
|
||||||
|
parser.addoption("--eventlog", dest="eventlog",
|
||||||
|
help="write all pytest events to the given file.")
|
||||||
|
|
||||||
|
def pytest_configure(self, config):
|
||||||
|
eventlog = config.getvalue("eventlog")
|
||||||
|
if eventlog:
|
||||||
|
self.eventlogfile = open(eventlog, 'w')
|
||||||
|
|
||||||
|
def pytest_unconfigure(self, config):
|
||||||
|
if hasattr(self, 'eventlogfile'):
|
||||||
|
self.eventlogfile.close()
|
||||||
|
del self.eventlogfile
|
||||||
|
|
||||||
|
def pyevent(self, eventname, *args, **kwargs):
|
||||||
|
if hasattr(self, 'eventlogfile'):
|
||||||
|
f = self.eventlogfile
|
||||||
|
print >>f, eventname, args, kwargs
|
||||||
|
f.flush()
|
||||||
|
|
||||||
|
# ===============================================================================
|
||||||
|
# plugin tests
|
||||||
|
# ===============================================================================
|
||||||
|
|
||||||
|
def test_generic(plugintester):
|
||||||
|
plugintester.apicheck(EventlogPlugin)
|
||||||
|
|
||||||
|
testdir = plugintester.testdir()
|
||||||
|
testdir.makepyfile("""
|
||||||
|
def test_pass():
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
testdir.runpytest("--eventlog=event.log")
|
||||||
|
s = testdir.tmpdir.join("event.log").read()
|
||||||
|
assert s.find("TestrunStart") != -1
|
||||||
|
assert s.find("ItemTestReport") != -1
|
||||||
|
assert s.find("TestrunFinish") != -1
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
import py
|
||||||
|
|
||||||
|
class IocapturePlugin:
|
||||||
|
""" capture sys.stdout/sys.stderr / fd1/fd2. """
|
||||||
|
def pytest_pyfuncarg_stdcapture(self, pyfuncitem):
|
||||||
|
capture = Capture(py.io.StdCapture)
|
||||||
|
pyfuncitem.addfinalizer(capture.finalize)
|
||||||
|
return capture
|
||||||
|
|
||||||
|
def pytest_pyfuncarg_stdcapturefd(self, pyfuncitem):
|
||||||
|
capture = Capture(py.io.StdCaptureFD)
|
||||||
|
pyfuncitem.addfinalizer(capture.finalize)
|
||||||
|
return capture
|
||||||
|
|
||||||
|
class Capture:
|
||||||
|
def __init__(self, captureclass):
|
||||||
|
self._captureclass = captureclass
|
||||||
|
self._capture = self._captureclass()
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
self._capture.reset()
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
|
res = self._capture.reset()
|
||||||
|
self._capture = self._captureclass()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def test_generic(plugintester):
|
||||||
|
plugintester.apicheck(IocapturePlugin)
|
||||||
|
|
||||||
|
class TestCapture:
|
||||||
|
def test_std_functional(self, testdir):
|
||||||
|
testdir.plugins.append(IocapturePlugin())
|
||||||
|
evrec = testdir.inline_runsource("""
|
||||||
|
def test_hello(stdcapture):
|
||||||
|
print 42
|
||||||
|
out, err = stdcapture.reset()
|
||||||
|
assert out.startswith("42")
|
||||||
|
""")
|
||||||
|
ev, = evrec.getnamed("itemtestreport")
|
||||||
|
assert ev.passed
|
||||||
|
|
||||||
|
def test_stdfd_functional(self, testdir):
|
||||||
|
testdir.plugins.append(IocapturePlugin())
|
||||||
|
evrec = testdir.inline_runsource("""
|
||||||
|
def test_hello(stdcapturefd):
|
||||||
|
import os
|
||||||
|
os.write(1, "42")
|
||||||
|
out, err = stdcapturefd.reset()
|
||||||
|
assert out.startswith("42")
|
||||||
|
""")
|
||||||
|
ev, = evrec.getnamed("itemtestreport")
|
||||||
|
assert ev.passed
|
||||||
|
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
class MonkeypatchPlugin:
|
||||||
|
""" setattr-monkeypatching with automatical reversal after test. """
|
||||||
|
def pytest_pyfuncarg_monkeypatch(self, pyfuncitem):
|
||||||
|
monkeypatch = MonkeyPatch()
|
||||||
|
pyfuncitem.addfinalizer(monkeypatch.finalize)
|
||||||
|
return monkeypatch
|
||||||
|
|
||||||
|
notset = object()
|
||||||
|
|
||||||
|
class MonkeyPatch:
|
||||||
|
def __init__(self):
|
||||||
|
self._setattr = []
|
||||||
|
self._setitem = []
|
||||||
|
|
||||||
|
def setattr(self, obj, name, value):
|
||||||
|
self._setattr.insert(0, (obj, name, getattr(obj, name, notset)))
|
||||||
|
setattr(obj, name, value)
|
||||||
|
|
||||||
|
def setitem(self, dictionary, name, value):
|
||||||
|
self._setitem.insert(0, (dictionary, name, dictionary.get(name, notset)))
|
||||||
|
dictionary[name] = value
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
for obj, name, value in self._setattr:
|
||||||
|
if value is not notset:
|
||||||
|
setattr(obj, name, value)
|
||||||
|
for dictionary, name, value in self._setitem:
|
||||||
|
if value is notset:
|
||||||
|
del dictionary[name]
|
||||||
|
else:
|
||||||
|
dictionary[name] = value
|
||||||
|
|
||||||
|
|
||||||
|
def test_setattr():
|
||||||
|
class A:
|
||||||
|
x = 1
|
||||||
|
monkeypatch = MonkeyPatch()
|
||||||
|
monkeypatch.setattr(A, 'x', 2)
|
||||||
|
assert A.x == 2
|
||||||
|
monkeypatch.setattr(A, 'x', 3)
|
||||||
|
assert A.x == 3
|
||||||
|
monkeypatch.finalize()
|
||||||
|
assert A.x == 1
|
||||||
|
|
||||||
|
def test_setitem():
|
||||||
|
d = {'x': 1}
|
||||||
|
monkeypatch = MonkeyPatch()
|
||||||
|
monkeypatch.setitem(d, 'x', 2)
|
||||||
|
monkeypatch.setitem(d, 'y', 1700)
|
||||||
|
assert d['x'] == 2
|
||||||
|
assert d['y'] == 1700
|
||||||
|
monkeypatch.setitem(d, 'x', 3)
|
||||||
|
assert d['x'] == 3
|
||||||
|
monkeypatch.finalize()
|
||||||
|
assert d['x'] == 1
|
||||||
|
assert 'y' not in d
|
||||||
|
|
||||||
|
def test_monkeypatch_plugin(testdir):
|
||||||
|
sorter = testdir.inline_runsource("""
|
||||||
|
pytest_plugins = 'pytest_monkeypatch',
|
||||||
|
def test_method(monkeypatch):
|
||||||
|
assert monkeypatch.__class__.__name__ == "MonkeyPatch"
|
||||||
|
""")
|
||||||
|
res = sorter.countoutcomes()
|
||||||
|
assert tuple(res) == (1, 0, 0), res
|
||||||
|
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
"""
|
||||||
|
plugin with support classes and functions for testing pytest functionality
|
||||||
|
"""
|
||||||
|
import py
|
||||||
|
|
||||||
|
class PlugintesterPlugin:
|
||||||
|
""" test support code for testing pytest plugins. """
|
||||||
|
def pytest_pyfuncarg_plugintester(self, pyfuncitem):
|
||||||
|
pt = PluginTester(pyfuncitem)
|
||||||
|
pyfuncitem.addfinalizer(pt.finalize)
|
||||||
|
return pt
|
||||||
|
|
||||||
|
class Support(object):
|
||||||
|
def __init__(self, pyfuncitem):
|
||||||
|
""" instantiated per function that requests it. """
|
||||||
|
self.pyfuncitem = pyfuncitem
|
||||||
|
|
||||||
|
def getmoditem(self):
|
||||||
|
for colitem in self.pyfuncitem.listchain():
|
||||||
|
if isinstance(colitem, colitem.Module):
|
||||||
|
return colitem
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
""" called after test function finished execution"""
|
||||||
|
|
||||||
|
class PluginTester(Support):
|
||||||
|
def testdir(self):
|
||||||
|
# XXX import differently, eg.
|
||||||
|
# FSTester = self.pyfuncitem._config.pytestplugins.getpluginattr("pytester", "FSTester")
|
||||||
|
from pytest_pytester import TmpTestdir
|
||||||
|
crunner = TmpTestdir(self.pyfuncitem)
|
||||||
|
#
|
||||||
|
for colitem in self.pyfuncitem.listchain():
|
||||||
|
if isinstance(colitem, py.test.collect.Module) and \
|
||||||
|
colitem.name.startswith("pytest_"):
|
||||||
|
crunner.plugins.append(colitem.fspath.purebasename)
|
||||||
|
break
|
||||||
|
return crunner
|
||||||
|
|
||||||
|
def apicheck(self, pluginclass):
|
||||||
|
print "loading and checking", pluginclass
|
||||||
|
fail = False
|
||||||
|
pm = py.test._PytestPlugins()
|
||||||
|
methods = collectattr(pluginclass)
|
||||||
|
hooks = collectattr(PytestPluginHooks)
|
||||||
|
getargs = py.std.inspect.getargs
|
||||||
|
|
||||||
|
def isgenerichook(name):
|
||||||
|
return name.startswith("pytest_pyfuncarg_")
|
||||||
|
|
||||||
|
while methods:
|
||||||
|
name, method = methods.popitem()
|
||||||
|
if isgenerichook(name):
|
||||||
|
continue
|
||||||
|
if name not in hooks:
|
||||||
|
print "found unknown hook: %s" % name
|
||||||
|
fail = True
|
||||||
|
else:
|
||||||
|
hook = hooks[name]
|
||||||
|
if not hasattr(hook, 'func_code'):
|
||||||
|
continue # XXX do some checks on attributes as well?
|
||||||
|
method_args = getargs(method.func_code)
|
||||||
|
hookargs = getargs(hook.func_code)
|
||||||
|
for arg, hookarg in zip(method_args[0], hookargs[0]):
|
||||||
|
if arg != hookarg:
|
||||||
|
print "argument mismatch:"
|
||||||
|
print "actual : %s.%s" %(pluginclass.__name__, formatdef(method))
|
||||||
|
print "required:", formatdef(hook)
|
||||||
|
fail = True
|
||||||
|
break
|
||||||
|
if not fail:
|
||||||
|
print "matching hook:", formatdef(method)
|
||||||
|
if fail:
|
||||||
|
py.test.fail("Plugin API error")
|
||||||
|
|
||||||
|
def collectattr(obj, prefixes=("pytest_", "pyevent_")):
|
||||||
|
methods = {}
|
||||||
|
for apiname in vars(obj):
|
||||||
|
for prefix in prefixes:
|
||||||
|
if apiname.startswith(prefix):
|
||||||
|
methods[apiname] = getattr(obj, apiname)
|
||||||
|
return methods
|
||||||
|
|
||||||
|
def formatdef(func):
|
||||||
|
formatargspec = py.std.inspect.formatargspec
|
||||||
|
getargspec = py.std.inspect.formatargspec
|
||||||
|
return "%s%s" %(
|
||||||
|
func.func_name,
|
||||||
|
py.std.inspect.formatargspec(*py.std.inspect.getargspec(func))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PytestPluginHooks:
|
||||||
|
def __init__(self):
|
||||||
|
""" usually called only once per test process. """
|
||||||
|
|
||||||
|
def pytest_addoption(self, parser):
|
||||||
|
""" called before commandline parsing. """
|
||||||
|
|
||||||
|
def pytest_configure(self, config):
|
||||||
|
""" called after command line options have been parsed.
|
||||||
|
and all plugins and initial conftest files been loaded.
|
||||||
|
``config`` provides access to all such configuration values.
|
||||||
|
"""
|
||||||
|
def pytest_unconfigure(self, config):
|
||||||
|
""" called before test process is exited.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def pytest_event(self, event):
|
||||||
|
""" called for each internal py.test event. """
|
||||||
|
|
||||||
|
#def pytest_pyfuncarg_NAME(self, pyfuncitem, argname):
|
||||||
|
# """ provide (value, finalizer) for an open test function argument.
|
||||||
|
#
|
||||||
|
# the finalizer (if not None) will be called after the test
|
||||||
|
# function has been executed (i.e. pyfuncitem.execute() returns).
|
||||||
|
# """
|
||||||
|
|
||||||
|
def pytest_pyfunc_call(self, pyfuncitem, args, kwargs):
|
||||||
|
""" return True if we consumed/did the call to the python function item. """
|
||||||
|
|
||||||
|
# collection hooks
|
||||||
|
def pytest_collect_file(self, path, parent):
|
||||||
|
""" return Collection node or None. """
|
||||||
|
|
||||||
|
def pytest_pymodule_makeitem(self, modcol, name, obj):
|
||||||
|
""" return custom item/collector or None. """
|
||||||
|
|
||||||
|
# from pytest_terminal plugin
|
||||||
|
def pytest_report_teststatus(self, event):
|
||||||
|
""" return shortletter and verbose word. """
|
||||||
|
|
||||||
|
def pytest_terminal_summary(self, terminalreporter):
|
||||||
|
""" add additional section in terminal summary reporting. """
|
||||||
|
|
||||||
|
# events
|
||||||
|
def pyevent(self, eventname, *args, **kwargs):
|
||||||
|
""" called for each testing event. """
|
||||||
|
|
||||||
|
def pyevent_internalerror(self, event):
|
||||||
|
""" called for internal errors. """
|
||||||
|
|
||||||
|
def pyevent_itemstart(self, event):
|
||||||
|
""" test item gets collected. """
|
||||||
|
|
||||||
|
def pyevent_itemtestreport(self, event):
|
||||||
|
""" test has been run. """
|
||||||
|
|
||||||
|
def pyevent_deselected(self, event):
|
||||||
|
""" item has been dselected. """
|
||||||
|
|
||||||
|
def pyevent_collectionstart(self, event):
|
||||||
|
""" collector starts collecting. """
|
||||||
|
|
||||||
|
def pyevent_collectionreport(self, event):
|
||||||
|
""" collector finished collecting. """
|
||||||
|
|
||||||
|
def pyevent_testrunstart(self, event):
|
||||||
|
""" whole test run starts. """
|
||||||
|
|
||||||
|
def pyevent_testrunfinish(self, event):
|
||||||
|
""" whole test run starts. """
|
||||||
|
|
||||||
|
def pyevent_hostup(self, event):
|
||||||
|
""" Host is up. """
|
||||||
|
|
||||||
|
def pyevent_hostgatewayready(self, event):
|
||||||
|
""" Connection to Host is ready. """
|
||||||
|
|
||||||
|
def pyevent_hostdown(self, event):
|
||||||
|
""" Host is down. """
|
||||||
|
|
||||||
|
def pyevent_rescheduleitems(self, event):
|
||||||
|
""" Items from a host that went down. """
|
||||||
|
|
||||||
|
def pyevent_looponfailinginfo(self, event):
|
||||||
|
""" info for repeating failing tests. """
|
||||||
|
|
||||||
|
def pyevent_plugin_registered(self, plugin):
|
||||||
|
""" a new py lib plugin got registered. """
|
||||||
|
|
||||||
|
|
||||||
|
# ===============================================================================
|
||||||
|
# plugin tests
|
||||||
|
# ===============================================================================
|
||||||
|
|
||||||
|
def test_generic(plugintester):
|
||||||
|
plugintester.apicheck(PlugintesterPlugin)
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
"""
|
||||||
|
py.test plugin for sending testing failure information to paste.pocoo.org
|
||||||
|
"""
|
||||||
|
import py
|
||||||
|
|
||||||
|
class url:
|
||||||
|
base = "http://paste.pocoo.org"
|
||||||
|
xmlrpc = base + "/xmlrpc/"
|
||||||
|
show = base + "/show/"
|
||||||
|
|
||||||
|
class PocooPlugin(object):
|
||||||
|
""" report URLs from sending test failures to the pocoo paste service. """
|
||||||
|
|
||||||
|
def pytest_addoption(self, parser):
|
||||||
|
parser.addoption('--pocoo-sendfailures',
|
||||||
|
action='store_true', dest="pocoo_sendfailures",
|
||||||
|
help="send failures to %s" %(url.base,))
|
||||||
|
|
||||||
|
def getproxy(self):
|
||||||
|
return py.std.xmlrpclib.ServerProxy(url.xmlrpc).pastes
|
||||||
|
|
||||||
|
def pytest_terminal_summary(self, terminalreporter):
|
||||||
|
if terminalreporter.config.option.pocoo_sendfailures:
|
||||||
|
tr = terminalreporter
|
||||||
|
if 'failed' in tr.stats and tr.config.option.tbstyle != "no":
|
||||||
|
terminalreporter.write_sep("=", "Sending failures to %s" %(url.base,))
|
||||||
|
terminalreporter.write_line("xmlrpcurl: %s" %(url.xmlrpc,))
|
||||||
|
serverproxy = self.getproxy()
|
||||||
|
for ev in terminalreporter.stats.get('failed'):
|
||||||
|
tw = py.io.TerminalWriter(stringio=True)
|
||||||
|
ev.toterminal(tw)
|
||||||
|
s = tw.stringio.getvalue()
|
||||||
|
# XXX add failure summary
|
||||||
|
assert len(s)
|
||||||
|
terminalreporter.write_line("newpaste() ...")
|
||||||
|
id = serverproxy.newPaste("python", s)
|
||||||
|
terminalreporter.write_line("%s%s\n" % (url.show, id))
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def test_apicheck(plugintester):
|
||||||
|
plugintester.apicheck(PocooPlugin)
|
||||||
|
|
||||||
|
pytest_plugins = 'pytest_monkeypatch',
|
||||||
|
def test_toproxy(testdir, monkeypatch):
|
||||||
|
testdir.makepyfile(conftest="pytest_plugins='pytest_pocoo',")
|
||||||
|
testpath = testdir.makepyfile("""
|
||||||
|
import py
|
||||||
|
def test_pass():
|
||||||
|
pass
|
||||||
|
def test_fail():
|
||||||
|
assert 0
|
||||||
|
def test_skip():
|
||||||
|
py.test.skip("")
|
||||||
|
""")
|
||||||
|
l = []
|
||||||
|
class MockProxy:
|
||||||
|
def newPaste(self, language, code):
|
||||||
|
l.append((language, code))
|
||||||
|
|
||||||
|
monkeypatch.setattr(PocooPlugin, 'getproxy', MockProxy)
|
||||||
|
result = testdir.inline_run(testpath, "--pocoo-sendfailures")
|
||||||
|
|
@ -0,0 +1,438 @@
|
||||||
|
"""
|
||||||
|
pytes plugin for easing testing of pytest runs themselves.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import py
|
||||||
|
from py.__.test import event
|
||||||
|
|
||||||
|
class PytesterPlugin:
|
||||||
|
def pytest_pyfuncarg_linecomp(self, pyfuncitem):
|
||||||
|
return LineComp()
|
||||||
|
|
||||||
|
def pytest_pyfuncarg_LineMatcher(self, pyfuncitem):
|
||||||
|
return LineMatcher
|
||||||
|
|
||||||
|
def pytest_pyfuncarg_testdir(self, pyfuncitem):
|
||||||
|
tmptestdir = TmpTestdir(pyfuncitem)
|
||||||
|
pyfuncitem.addfinalizer(tmptestdir.finalize)
|
||||||
|
return tmptestdir
|
||||||
|
|
||||||
|
def pytest_pyfuncarg_EventRecorder(self, pyfuncitem):
|
||||||
|
return EventRecorder
|
||||||
|
|
||||||
|
def test_generic(plugintester):
|
||||||
|
plugintester.apicheck(PytesterPlugin)
|
||||||
|
|
||||||
|
class RunResult:
|
||||||
|
def __init__(self, ret, outlines, errlines):
|
||||||
|
self.ret = ret
|
||||||
|
self.outlines = outlines
|
||||||
|
self.errlines = errlines
|
||||||
|
self.stdout = LineMatcher(outlines)
|
||||||
|
self.stderr = LineMatcher(errlines)
|
||||||
|
|
||||||
|
class TmpTestdir:
|
||||||
|
def __init__(self, pyfuncitem):
|
||||||
|
self.pyfuncitem = pyfuncitem
|
||||||
|
# XXX remove duplication with tmpdir plugin
|
||||||
|
basetmp = py.test.ensuretemp("testdir")
|
||||||
|
name = pyfuncitem.name
|
||||||
|
for i in range(100):
|
||||||
|
try:
|
||||||
|
tmpdir = basetmp.mkdir(name + str(i))
|
||||||
|
except py.error.EEXIST:
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
# we need to create another subdir
|
||||||
|
# because Directory.collect() currently loads
|
||||||
|
# conftest.py from sibling directories
|
||||||
|
self.tmpdir = tmpdir.mkdir(name)
|
||||||
|
self.plugins = []
|
||||||
|
self._syspathremove = []
|
||||||
|
from py.__.test.config import Config
|
||||||
|
self.Config = Config
|
||||||
|
|
||||||
|
def finalize(self):
|
||||||
|
for p in self._syspathremove:
|
||||||
|
py.std.sys.path.remove(p)
|
||||||
|
if hasattr(self, '_olddir'):
|
||||||
|
self._olddir.chdir()
|
||||||
|
|
||||||
|
def chdir(self):
|
||||||
|
old = self.testdir.chdir()
|
||||||
|
if not hasattr(self, '_olddir'):
|
||||||
|
self._olddir = old
|
||||||
|
|
||||||
|
def _makefile(self, ext, args, kwargs):
|
||||||
|
items = kwargs.items()
|
||||||
|
if args:
|
||||||
|
source = "\n".join(map(str, args))
|
||||||
|
basename = self.pyfuncitem.name
|
||||||
|
items.insert(0, (basename, source))
|
||||||
|
ret = None
|
||||||
|
for name, value in items:
|
||||||
|
p = self.tmpdir.join(name).new(ext=ext)
|
||||||
|
source = py.code.Source(value)
|
||||||
|
p.write(str(py.code.Source(value)).lstrip())
|
||||||
|
if ret is None:
|
||||||
|
ret = p
|
||||||
|
return ret
|
||||||
|
|
||||||
|
|
||||||
|
def makefile(self, ext, *args, **kwargs):
|
||||||
|
return self._makefile(ext, args, kwargs)
|
||||||
|
|
||||||
|
def makeconftest(self, source):
|
||||||
|
return self.makepyfile(conftest=source)
|
||||||
|
|
||||||
|
def makepyfile(self, *args, **kwargs):
|
||||||
|
return self._makefile('.py', args, kwargs)
|
||||||
|
|
||||||
|
def maketxtfile(self, *args, **kwargs):
|
||||||
|
return self._makefile('.txt', args, kwargs)
|
||||||
|
|
||||||
|
def syspathinsert(self, path=None):
|
||||||
|
if path is None:
|
||||||
|
path = self.tmpdir
|
||||||
|
py.std.sys.path.insert(0, str(path))
|
||||||
|
self._syspathremove.append(str(path))
|
||||||
|
|
||||||
|
def mkdir(self, name):
|
||||||
|
return self.tmpdir.mkdir(name)
|
||||||
|
|
||||||
|
def chdir(self):
|
||||||
|
return self.tmpdir.chdir()
|
||||||
|
|
||||||
|
def genitems(self, colitems):
|
||||||
|
return list(self.session.genitems(colitems))
|
||||||
|
|
||||||
|
def inline_genitems(self, *args):
|
||||||
|
#config = self.parseconfig(*args)
|
||||||
|
config = self.parseconfig(*args)
|
||||||
|
session = config.initsession()
|
||||||
|
rec = EventRecorder(config.bus)
|
||||||
|
colitems = [config.getfsnode(arg) for arg in config.args]
|
||||||
|
items = list(session.genitems(colitems))
|
||||||
|
return items, rec
|
||||||
|
|
||||||
|
def runitem(self, source, **runnerargs):
|
||||||
|
# used from runner functional tests
|
||||||
|
item = self.getitem(source)
|
||||||
|
# the test class where we are called from wants to provide the runner
|
||||||
|
testclassinstance = self.pyfuncitem.obj.im_self
|
||||||
|
runner = testclassinstance.getrunner()
|
||||||
|
return runner(item, **runnerargs)
|
||||||
|
|
||||||
|
def inline_runsource(self, source, *cmdlineargs):
|
||||||
|
p = self.makepyfile(source)
|
||||||
|
l = list(cmdlineargs) + [p]
|
||||||
|
return self.inline_run(*l)
|
||||||
|
|
||||||
|
def inline_runsession(self, session):
|
||||||
|
config = session.config
|
||||||
|
config.pytestplugins.configure(config)
|
||||||
|
sorter = EventRecorder(config.bus)
|
||||||
|
session.main()
|
||||||
|
config.pytestplugins.unconfigure(config)
|
||||||
|
return sorter
|
||||||
|
|
||||||
|
def inline_run(self, *args):
|
||||||
|
config = self.parseconfig(*args)
|
||||||
|
config.pytestplugins.configure(config)
|
||||||
|
session = config.initsession()
|
||||||
|
sorter = EventRecorder(config.bus)
|
||||||
|
session.main()
|
||||||
|
config.pytestplugins.unconfigure(config)
|
||||||
|
return sorter
|
||||||
|
|
||||||
|
def inline_run_with_plugins(self, *args):
|
||||||
|
config = self.parseconfig(*args)
|
||||||
|
config.pytestplugins.configure(config)
|
||||||
|
session = config.initsession()
|
||||||
|
sorter = EventRecorder(config.bus)
|
||||||
|
session.main()
|
||||||
|
config.pytestplugins.unconfigure(config)
|
||||||
|
return sorter
|
||||||
|
|
||||||
|
def config_preparse(self):
|
||||||
|
config = self.Config()
|
||||||
|
for plugin in self.plugins:
|
||||||
|
if isinstance(plugin, str):
|
||||||
|
config.pytestplugins.import_plugin(plugin)
|
||||||
|
else:
|
||||||
|
config.pytestplugins.register(plugin)
|
||||||
|
return config
|
||||||
|
|
||||||
|
def parseconfig(self, *args):
|
||||||
|
if not args:
|
||||||
|
args = (self.tmpdir,)
|
||||||
|
config = self.config_preparse()
|
||||||
|
config.parse(list(args))
|
||||||
|
return config
|
||||||
|
|
||||||
|
def getitem(self, source, funcname="test_func"):
|
||||||
|
modcol = self.getmodulecol(source)
|
||||||
|
item = modcol.join(funcname)
|
||||||
|
assert item is not None, "%r item not found in module:\n%s" %(funcname, source)
|
||||||
|
return item
|
||||||
|
|
||||||
|
def getitems(self, source):
|
||||||
|
modcol = self.getmodulecol(source)
|
||||||
|
return list(modcol._config.initsession().genitems([modcol]))
|
||||||
|
#assert item is not None, "%r item not found in module:\n%s" %(funcname, source)
|
||||||
|
#return item
|
||||||
|
|
||||||
|
def getfscol(self, path, configargs=()):
|
||||||
|
self.config = self.parseconfig(path, *configargs)
|
||||||
|
self.session = self.config.initsession()
|
||||||
|
return self.config.getfsnode(path)
|
||||||
|
|
||||||
|
def getmodulecol(self, source, configargs=(), withinit=False):
|
||||||
|
kw = {self.pyfuncitem.name: py.code.Source(source).strip()}
|
||||||
|
path = self.makepyfile(**kw)
|
||||||
|
if withinit:
|
||||||
|
self.makepyfile(__init__ = "#")
|
||||||
|
self.config = self.parseconfig(path, *configargs)
|
||||||
|
self.session = self.config.initsession()
|
||||||
|
return self.config.getfsnode(path)
|
||||||
|
|
||||||
|
def prepare(self):
|
||||||
|
p = self.tmpdir.join("conftest.py")
|
||||||
|
if not p.check():
|
||||||
|
plugins = [x for x in self.plugins if isinstance(x, str)]
|
||||||
|
p.write("import py ; pytest_plugins = %r" % plugins)
|
||||||
|
else:
|
||||||
|
if self.plugins:
|
||||||
|
print "warning, ignoring reusing existing con", p
|
||||||
|
|
||||||
|
def popen(self, cmdargs, stdout, stderr, **kw):
|
||||||
|
if not hasattr(py.std, 'subprocess'):
|
||||||
|
py.test.skip("no subprocess module")
|
||||||
|
return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
|
||||||
|
|
||||||
|
def run(self, *cmdargs):
|
||||||
|
self.prepare()
|
||||||
|
old = self.tmpdir.chdir()
|
||||||
|
#print "chdir", self.tmpdir
|
||||||
|
try:
|
||||||
|
return self._run(*cmdargs)
|
||||||
|
finally:
|
||||||
|
old.chdir()
|
||||||
|
|
||||||
|
def _run(self, *cmdargs):
|
||||||
|
cmdargs = map(str, cmdargs)
|
||||||
|
p1 = py.path.local("stdout")
|
||||||
|
p2 = py.path.local("stderr")
|
||||||
|
print "running", cmdargs, "curdir=", py.path.local()
|
||||||
|
popen = self.popen(cmdargs, stdout=p1.open("w"), stderr=p2.open("w"))
|
||||||
|
ret = popen.wait()
|
||||||
|
out, err = p1.readlines(cr=0), p2.readlines(cr=0)
|
||||||
|
if err:
|
||||||
|
for line in err:
|
||||||
|
print >>py.std.sys.stderr, line
|
||||||
|
if out:
|
||||||
|
for line in out:
|
||||||
|
print >>py.std.sys.stdout, line
|
||||||
|
return RunResult(ret, out, err)
|
||||||
|
|
||||||
|
def runpybin(self, scriptname, *args):
|
||||||
|
bindir = py.path.local(py.__file__).dirpath("bin")
|
||||||
|
if py.std.sys.platform == "win32":
|
||||||
|
script = bindir.join("win32", scriptname + ".cmd")
|
||||||
|
else:
|
||||||
|
script = bindir.join(scriptname)
|
||||||
|
assert script.check()
|
||||||
|
return self.run(script, *args)
|
||||||
|
|
||||||
|
def runpytest(self, *args):
|
||||||
|
return self.runpybin("py.test", *args)
|
||||||
|
|
||||||
|
|
||||||
|
class EventRecorder(object):
|
||||||
|
def __init__(self, pyplugins, debug=False): # True):
|
||||||
|
self.events = []
|
||||||
|
self.pyplugins = pyplugins
|
||||||
|
self.debug = debug
|
||||||
|
pyplugins.register(self)
|
||||||
|
|
||||||
|
def pyevent(self, name, *args, **kwargs):
|
||||||
|
if name == "plugin_registered" and args == (self,):
|
||||||
|
return
|
||||||
|
if self.debug:
|
||||||
|
print "[event] %s: %s **%s" %(name, ", ".join(map(str, args)), kwargs,)
|
||||||
|
if len(args) == 1:
|
||||||
|
event, = args
|
||||||
|
self.events.append((name, event))
|
||||||
|
|
||||||
|
def get(self, cls):
|
||||||
|
l = []
|
||||||
|
for name, value in self.events:
|
||||||
|
if isinstance(value, cls):
|
||||||
|
l.append(value)
|
||||||
|
return l
|
||||||
|
|
||||||
|
def getnamed(self, *names):
|
||||||
|
l = []
|
||||||
|
for evname, event in self.events:
|
||||||
|
if evname in names:
|
||||||
|
l.append(event)
|
||||||
|
return l
|
||||||
|
|
||||||
|
def getfirstnamed(self, name):
|
||||||
|
for evname, event in self.events:
|
||||||
|
if evname == name:
|
||||||
|
return event
|
||||||
|
|
||||||
|
def getfailures(self, names='itemtestreport collectionreport'):
|
||||||
|
l = []
|
||||||
|
for ev in self.getnamed(*names.split()):
|
||||||
|
if ev.failed:
|
||||||
|
l.append(ev)
|
||||||
|
return l
|
||||||
|
|
||||||
|
def getfailedcollections(self):
|
||||||
|
return self.getfailures('collectionreport')
|
||||||
|
|
||||||
|
def listoutcomes(self):
|
||||||
|
passed = []
|
||||||
|
skipped = []
|
||||||
|
failed = []
|
||||||
|
for ev in self.getnamed('itemtestreport'): # , 'collectionreport'):
|
||||||
|
if ev.passed:
|
||||||
|
passed.append(ev)
|
||||||
|
elif ev.skipped:
|
||||||
|
skipped.append(ev)
|
||||||
|
elif ev.failed:
|
||||||
|
failed.append(ev)
|
||||||
|
return passed, skipped, failed
|
||||||
|
|
||||||
|
def countoutcomes(self):
|
||||||
|
return map(len, self.listoutcomes())
|
||||||
|
|
||||||
|
def assertoutcome(self, passed=0, skipped=0, failed=0):
|
||||||
|
realpassed, realskipped, realfailed = self.listoutcomes()
|
||||||
|
assert passed == len(realpassed)
|
||||||
|
assert skipped == len(realskipped)
|
||||||
|
assert failed == len(realfailed)
|
||||||
|
|
||||||
|
def getreport(self, inamepart):
|
||||||
|
""" return a testreport whose dotted import path matches """
|
||||||
|
__tracebackhide__ = True
|
||||||
|
l = []
|
||||||
|
for rep in self.get(event.ItemTestReport):
|
||||||
|
if inamepart in rep.colitem.listnames():
|
||||||
|
l.append(rep)
|
||||||
|
if not l:
|
||||||
|
raise ValueError("could not find test report matching %r: no test reports at all!" %
|
||||||
|
(inamepart,))
|
||||||
|
if len(l) > 1:
|
||||||
|
raise ValueError("found more than one testreport matching %r: %s" %(
|
||||||
|
inamepart, l))
|
||||||
|
return l[0]
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.events[:] = []
|
||||||
|
|
||||||
|
def unregister(self):
|
||||||
|
self.pyplugins.unregister(self)
|
||||||
|
|
||||||
|
def test_eventrecorder():
|
||||||
|
bus = py._com.PyPlugins()
|
||||||
|
recorder = EventRecorder(bus)
|
||||||
|
bus.notify("anonymous", event.NOP())
|
||||||
|
assert recorder.events
|
||||||
|
assert not recorder.getfailures()
|
||||||
|
rep = event.ItemTestReport(None, None)
|
||||||
|
rep.passed = False
|
||||||
|
rep.failed = True
|
||||||
|
bus.notify("itemtestreport", rep)
|
||||||
|
failures = recorder.getfailures()
|
||||||
|
assert failures == [rep]
|
||||||
|
failures = recorder.get(event.ItemTestReport)
|
||||||
|
assert failures == [rep]
|
||||||
|
failures = recorder.getnamed("itemtestreport")
|
||||||
|
assert failures == [rep]
|
||||||
|
|
||||||
|
rep = event.ItemTestReport(None, None)
|
||||||
|
rep.passed = False
|
||||||
|
rep.skipped = True
|
||||||
|
bus.notify("itemtestreport", rep)
|
||||||
|
|
||||||
|
rep = event.CollectionReport(None, None)
|
||||||
|
rep.passed = False
|
||||||
|
rep.failed = True
|
||||||
|
bus.notify("itemtestreport", rep)
|
||||||
|
|
||||||
|
passed, skipped, failed = recorder.listoutcomes()
|
||||||
|
assert not passed and skipped and failed
|
||||||
|
|
||||||
|
numpassed, numskipped, numfailed = recorder.countoutcomes()
|
||||||
|
assert numpassed == 0
|
||||||
|
assert numskipped == 1
|
||||||
|
assert numfailed == 2
|
||||||
|
|
||||||
|
recorder.clear()
|
||||||
|
assert not recorder.events
|
||||||
|
assert not recorder.getfailures()
|
||||||
|
recorder.unregister()
|
||||||
|
bus.notify("itemtestreport", rep)
|
||||||
|
assert not recorder.events
|
||||||
|
assert not recorder.getfailures()
|
||||||
|
|
||||||
|
class LineComp:
|
||||||
|
def __init__(self):
|
||||||
|
self.stringio = py.std.StringIO.StringIO()
|
||||||
|
|
||||||
|
def assert_contains_lines(self, lines2):
|
||||||
|
""" assert that lines2 are contained (linearly) in lines1.
|
||||||
|
return a list of extralines found.
|
||||||
|
"""
|
||||||
|
__tracebackhide__ = True
|
||||||
|
val = self.stringio.getvalue()
|
||||||
|
self.stringio.truncate(0) # remove what we got
|
||||||
|
lines1 = val.split("\n")
|
||||||
|
return LineMatcher(lines1).fnmatch_lines(lines2)
|
||||||
|
|
||||||
|
class LineMatcher:
|
||||||
|
def __init__(self, lines):
|
||||||
|
self.lines = lines
|
||||||
|
|
||||||
|
def str(self):
|
||||||
|
return "\n".join(self.lines)
|
||||||
|
|
||||||
|
def fnmatch_lines(self, lines2):
|
||||||
|
if isinstance(lines2, str):
|
||||||
|
lines2 = py.code.Source(lines2)
|
||||||
|
if isinstance(lines2, py.code.Source):
|
||||||
|
lines2 = lines2.strip().lines
|
||||||
|
|
||||||
|
from fnmatch import fnmatch
|
||||||
|
__tracebackhide__ = True
|
||||||
|
lines1 = self.lines[:]
|
||||||
|
nextline = None
|
||||||
|
extralines = []
|
||||||
|
for line in lines2:
|
||||||
|
nomatchprinted = False
|
||||||
|
while lines1:
|
||||||
|
nextline = lines1.pop(0)
|
||||||
|
if line == nextline:
|
||||||
|
print "exact match:", repr(line)
|
||||||
|
break
|
||||||
|
elif fnmatch(nextline, line):
|
||||||
|
print "fnmatch:", repr(line)
|
||||||
|
print " with:", repr(nextline)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if not nomatchprinted:
|
||||||
|
print "nomatch:", repr(line)
|
||||||
|
nomatchprinted = True
|
||||||
|
print " and:", repr(nextline)
|
||||||
|
extralines.append(nextline)
|
||||||
|
else:
|
||||||
|
if line != nextline:
|
||||||
|
#__tracebackhide__ = True
|
||||||
|
raise AssertionError("expected line not found: %r" % line)
|
||||||
|
extralines.extend(lines1)
|
||||||
|
return extralines
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,418 @@
|
||||||
|
import py
|
||||||
|
|
||||||
|
class RestdocPlugin:
|
||||||
|
def pytest_addoption(self, parser):
|
||||||
|
group = parser.addgroup("ReST", "ReST documentation check options")
|
||||||
|
group.addoption('-R', '--urlcheck',
|
||||||
|
action="store_true", dest="urlcheck", default=False,
|
||||||
|
help="urlopen() remote links found in ReST text files.")
|
||||||
|
group.addoption('--urlcheck-timeout', action="store",
|
||||||
|
type="int", dest="urlcheck_timeout", default=5,
|
||||||
|
help="timeout in seconds for urlcheck")
|
||||||
|
group.addoption('--forcegen',
|
||||||
|
action="store_true", dest="forcegen", default=False,
|
||||||
|
help="force generation of html files even if they appear up-to-date")
|
||||||
|
|
||||||
|
def pytest_collect_file(self, path, parent):
|
||||||
|
if path.ext == ".txt":
|
||||||
|
project = getproject(path)
|
||||||
|
if project is not None:
|
||||||
|
return ReSTFile(path, parent=parent, project=project)
|
||||||
|
|
||||||
|
def getproject(path):
|
||||||
|
for parent in path.parts(reverse=True):
|
||||||
|
confrest = parent.join("confrest.py")
|
||||||
|
if confrest.check():
|
||||||
|
Project = confrest.pyimport().Project
|
||||||
|
return Project(parent.dirpath())
|
||||||
|
|
||||||
|
class ReSTFile(py.test.collect.File):
|
||||||
|
def __init__(self, fspath, parent, project=None):
|
||||||
|
super(ReSTFile, self).__init__(fspath=fspath, parent=parent)
|
||||||
|
if project is None:
|
||||||
|
project = getproject(fspath)
|
||||||
|
assert project is not None
|
||||||
|
self.project = project
|
||||||
|
|
||||||
|
def collect(self):
|
||||||
|
return [
|
||||||
|
ReSTSyntaxTest(self.project, "ReSTSyntax", parent=self),
|
||||||
|
LinkCheckerMaker("checklinks", parent=self),
|
||||||
|
DoctestText("doctest", parent=self),
|
||||||
|
]
|
||||||
|
|
||||||
|
def deindent(s, sep='\n'):
|
||||||
|
leastspaces = -1
|
||||||
|
lines = s.split(sep)
|
||||||
|
for line in lines:
|
||||||
|
if not line.strip():
|
||||||
|
continue
|
||||||
|
spaces = len(line) - len(line.lstrip())
|
||||||
|
if leastspaces == -1 or spaces < leastspaces:
|
||||||
|
leastspaces = spaces
|
||||||
|
if leastspaces == -1:
|
||||||
|
return s
|
||||||
|
for i, line in py.builtin.enumerate(lines):
|
||||||
|
if not line.strip():
|
||||||
|
lines[i] = ''
|
||||||
|
else:
|
||||||
|
lines[i] = line[leastspaces:]
|
||||||
|
return sep.join(lines)
|
||||||
|
|
||||||
|
class ReSTSyntaxTest(py.test.collect.Item):
|
||||||
|
def __init__(self, project, *args, **kwargs):
|
||||||
|
super(ReSTSyntaxTest, self).__init__(*args, **kwargs)
|
||||||
|
self.project = project
|
||||||
|
|
||||||
|
def runtest(self):
|
||||||
|
self.restcheck(py.path.svnwc(self.fspath))
|
||||||
|
|
||||||
|
def restcheck(self, path):
|
||||||
|
py.test.importorskip("docutils")
|
||||||
|
self.register_linkrole()
|
||||||
|
from docutils.utils import SystemMessage
|
||||||
|
try:
|
||||||
|
self._checkskip(path, self.project.get_htmloutputpath(path))
|
||||||
|
self.project.process(path)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
raise
|
||||||
|
except SystemMessage:
|
||||||
|
# we assume docutils printed info on stdout
|
||||||
|
py.test.fail("docutils processing failed, see captured stderr")
|
||||||
|
|
||||||
|
def register_linkrole(self):
|
||||||
|
from py.__.rest import directive
|
||||||
|
directive.register_linkrole('api', self.resolve_linkrole)
|
||||||
|
directive.register_linkrole('source', self.resolve_linkrole)
|
||||||
|
|
||||||
|
def resolve_linkrole(self, name, text, check=True):
|
||||||
|
apigen_relpath = self.project.apigen_relpath
|
||||||
|
|
||||||
|
if name == 'api':
|
||||||
|
if text == 'py':
|
||||||
|
return ('py', apigen_relpath + 'api/index.html')
|
||||||
|
else:
|
||||||
|
assert text.startswith('py.'), (
|
||||||
|
'api link "%s" does not point to the py package') % (text,)
|
||||||
|
dotted_name = text
|
||||||
|
if dotted_name.find('(') > -1:
|
||||||
|
dotted_name = dotted_name[:text.find('(')]
|
||||||
|
# remove pkg root
|
||||||
|
path = dotted_name.split('.')[1:]
|
||||||
|
dotted_name = '.'.join(path)
|
||||||
|
obj = py
|
||||||
|
if check:
|
||||||
|
for chunk in path:
|
||||||
|
try:
|
||||||
|
obj = getattr(obj, chunk)
|
||||||
|
except AttributeError:
|
||||||
|
raise AssertionError(
|
||||||
|
'problem with linkrole :api:`%s`: can not resolve '
|
||||||
|
'dotted name %s' % (text, dotted_name,))
|
||||||
|
return (text, apigen_relpath + 'api/%s.html' % (dotted_name,))
|
||||||
|
elif name == 'source':
|
||||||
|
assert text.startswith('py/'), ('source link "%s" does not point '
|
||||||
|
'to the py package') % (text,)
|
||||||
|
relpath = '/'.join(text.split('/')[1:])
|
||||||
|
if check:
|
||||||
|
pkgroot = py.__pkg__.getpath()
|
||||||
|
abspath = pkgroot.join(relpath)
|
||||||
|
assert pkgroot.join(relpath).check(), (
|
||||||
|
'problem with linkrole :source:`%s`: '
|
||||||
|
'path %s does not exist' % (text, relpath))
|
||||||
|
if relpath.endswith('/') or not relpath:
|
||||||
|
relpath += 'index.html'
|
||||||
|
else:
|
||||||
|
relpath += '.html'
|
||||||
|
return (text, apigen_relpath + 'source/%s' % (relpath,))
|
||||||
|
|
||||||
|
def _checkskip(self, lpath, htmlpath=None):
|
||||||
|
if not self._config.getvalue("forcegen"):
|
||||||
|
lpath = py.path.local(lpath)
|
||||||
|
if htmlpath is not None:
|
||||||
|
htmlpath = py.path.local(htmlpath)
|
||||||
|
if lpath.ext == '.txt':
|
||||||
|
htmlpath = htmlpath or lpath.new(ext='.html')
|
||||||
|
if htmlpath.check(file=1) and htmlpath.mtime() >= lpath.mtime():
|
||||||
|
py.test.skip("html file is up to date, use --forcegen to regenerate")
|
||||||
|
#return [] # no need to rebuild
|
||||||
|
|
||||||
|
class DoctestText(py.test.collect.Item):
|
||||||
|
def runtest(self):
|
||||||
|
content = self._normalize_linesep()
|
||||||
|
newcontent = self._config.pytestplugins.call_firstresult(
|
||||||
|
'pytest_doctest_prepare_content', content=content)
|
||||||
|
if newcontent is not None:
|
||||||
|
content = newcontent
|
||||||
|
s = content
|
||||||
|
l = []
|
||||||
|
prefix = '.. >>> '
|
||||||
|
mod = py.std.types.ModuleType(self.fspath.purebasename)
|
||||||
|
skipchunk = False
|
||||||
|
for line in deindent(s).split('\n'):
|
||||||
|
stripped = line.strip()
|
||||||
|
if skipchunk and line.startswith(skipchunk):
|
||||||
|
print "skipping", line
|
||||||
|
continue
|
||||||
|
skipchunk = False
|
||||||
|
if stripped.startswith(prefix):
|
||||||
|
try:
|
||||||
|
exec py.code.Source(stripped[len(prefix):]).compile() in \
|
||||||
|
mod.__dict__
|
||||||
|
except ValueError, e:
|
||||||
|
if e.args and e.args[0] == "skipchunk":
|
||||||
|
skipchunk = " " * (len(line) - len(line.lstrip()))
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
else:
|
||||||
|
l.append(line)
|
||||||
|
docstring = "\n".join(l)
|
||||||
|
mod.__doc__ = docstring
|
||||||
|
failed, tot = py.compat.doctest.testmod(mod, verbose=1)
|
||||||
|
if failed:
|
||||||
|
py.test.fail("doctest %s: %s failed out of %s" %(
|
||||||
|
self.fspath, failed, tot))
|
||||||
|
|
||||||
|
def _normalize_linesep(self):
|
||||||
|
# XXX quite nasty... but it works (fixes win32 issues)
|
||||||
|
s = self.fspath.read()
|
||||||
|
linesep = '\n'
|
||||||
|
if '\r' in s:
|
||||||
|
if '\n' not in s:
|
||||||
|
linesep = '\r'
|
||||||
|
else:
|
||||||
|
linesep = '\r\n'
|
||||||
|
s = s.replace(linesep, '\n')
|
||||||
|
return s
|
||||||
|
|
||||||
|
class LinkCheckerMaker(py.test.collect.Collector):
|
||||||
|
def collect(self):
|
||||||
|
return list(self.genlinkchecks())
|
||||||
|
|
||||||
|
def genlinkchecks(self):
|
||||||
|
path = self.fspath
|
||||||
|
# generating functions + args as single tests
|
||||||
|
timeout = self._config.getvalue("urlcheck_timeout")
|
||||||
|
for lineno, line in py.builtin.enumerate(path.readlines()):
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith('.. _'):
|
||||||
|
if line.startswith('.. _`'):
|
||||||
|
delim = '`:'
|
||||||
|
else:
|
||||||
|
delim = ':'
|
||||||
|
l = line.split(delim, 1)
|
||||||
|
if len(l) != 2:
|
||||||
|
continue
|
||||||
|
tryfn = l[1].strip()
|
||||||
|
name = "%s:%d" %(tryfn, lineno)
|
||||||
|
if tryfn.startswith('http:') or tryfn.startswith('https'):
|
||||||
|
if self._config.getvalue("urlcheck"):
|
||||||
|
yield CheckLink(name, parent=self,
|
||||||
|
args=(tryfn, path, lineno, timeout), callobj=urlcheck)
|
||||||
|
elif tryfn.startswith('webcal:'):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
i = tryfn.find('#')
|
||||||
|
if i != -1:
|
||||||
|
checkfn = tryfn[:i]
|
||||||
|
else:
|
||||||
|
checkfn = tryfn
|
||||||
|
if checkfn.strip() and (1 or checkfn.endswith('.html')):
|
||||||
|
yield CheckLink(name, parent=self,
|
||||||
|
args=(tryfn, path, lineno), callobj=localrefcheck)
|
||||||
|
|
||||||
|
class CheckLink(py.test.collect.Function):
|
||||||
|
def repr_metainfo(self):
|
||||||
|
return self.ReprMetaInfo(fspath=self.fspath, lineno=self._args[2],
|
||||||
|
modpath="checklink: %s" % (self._args[0],))
|
||||||
|
def setup(self):
|
||||||
|
pass
|
||||||
|
def teardown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def urlcheck(tryfn, path, lineno, TIMEOUT_URLOPEN):
|
||||||
|
old = py.std.socket.getdefaulttimeout()
|
||||||
|
py.std.socket.setdefaulttimeout(TIMEOUT_URLOPEN)
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
print "trying remote", tryfn
|
||||||
|
py.std.urllib2.urlopen(tryfn)
|
||||||
|
finally:
|
||||||
|
py.std.socket.setdefaulttimeout(old)
|
||||||
|
except (py.std.urllib2.URLError, py.std.urllib2.HTTPError), e:
|
||||||
|
if getattr(e, 'code', None) in (401, 403): # authorization required, forbidden
|
||||||
|
py.test.skip("%s: %s" %(tryfn, str(e)))
|
||||||
|
else:
|
||||||
|
py.test.fail("remote reference error %r in %s:%d\n%s" %(
|
||||||
|
tryfn, path.basename, lineno+1, e))
|
||||||
|
|
||||||
|
def localrefcheck(tryfn, path, lineno):
|
||||||
|
# assume it should be a file
|
||||||
|
i = tryfn.find('#')
|
||||||
|
if tryfn.startswith('javascript:'):
|
||||||
|
return # don't check JS refs
|
||||||
|
if i != -1:
|
||||||
|
anchor = tryfn[i+1:]
|
||||||
|
tryfn = tryfn[:i]
|
||||||
|
else:
|
||||||
|
anchor = ''
|
||||||
|
fn = path.dirpath(tryfn)
|
||||||
|
ishtml = fn.ext == '.html'
|
||||||
|
fn = ishtml and fn.new(ext='.txt') or fn
|
||||||
|
print "filename is", fn
|
||||||
|
if not fn.check(): # not ishtml or not fn.check():
|
||||||
|
if not py.path.local(tryfn).check(): # the html could be there
|
||||||
|
py.test.fail("reference error %r in %s:%d" %(
|
||||||
|
tryfn, path.basename, lineno+1))
|
||||||
|
if anchor:
|
||||||
|
source = unicode(fn.read(), 'latin1')
|
||||||
|
source = source.lower().replace('-', ' ') # aehem
|
||||||
|
|
||||||
|
anchor = anchor.replace('-', ' ')
|
||||||
|
match2 = ".. _`%s`:" % anchor
|
||||||
|
match3 = ".. _%s:" % anchor
|
||||||
|
candidates = (anchor, match2, match3)
|
||||||
|
print "candidates", repr(candidates)
|
||||||
|
for line in source.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if line in candidates:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
py.test.fail("anchor reference error %s#%s in %s:%d" %(
|
||||||
|
tryfn, anchor, path.basename, lineno+1))
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# PLUGIN tests
|
||||||
|
#
|
||||||
|
def test_generic(plugintester):
|
||||||
|
plugintester.apicheck(RestdocPlugin)
|
||||||
|
|
||||||
|
def test_deindent():
|
||||||
|
assert deindent('foo') == 'foo'
|
||||||
|
assert deindent('foo\n bar') == 'foo\n bar'
|
||||||
|
assert deindent(' foo\n bar\n') == 'foo\nbar\n'
|
||||||
|
assert deindent(' foo\n\n bar\n') == 'foo\n\nbar\n'
|
||||||
|
assert deindent(' foo\n bar\n') == 'foo\n bar\n'
|
||||||
|
assert deindent(' foo\n bar\n') == ' foo\nbar\n'
|
||||||
|
|
||||||
|
class TestApigenLinkRole:
|
||||||
|
disabled = True
|
||||||
|
# these tests are moved here from the former py/doc/conftest.py
|
||||||
|
def test_resolve_linkrole(self):
|
||||||
|
from py.__.doc.conftest import get_apigen_relpath
|
||||||
|
apigen_relpath = get_apigen_relpath()
|
||||||
|
|
||||||
|
assert resolve_linkrole('api', 'py.foo.bar', False) == (
|
||||||
|
'py.foo.bar', apigen_relpath + 'api/foo.bar.html')
|
||||||
|
assert resolve_linkrole('api', 'py.foo.bar()', False) == (
|
||||||
|
'py.foo.bar()', apigen_relpath + 'api/foo.bar.html')
|
||||||
|
assert resolve_linkrole('api', 'py', False) == (
|
||||||
|
'py', apigen_relpath + 'api/index.html')
|
||||||
|
py.test.raises(AssertionError, 'resolve_linkrole("api", "foo.bar")')
|
||||||
|
assert resolve_linkrole('source', 'py/foo/bar.py', False) == (
|
||||||
|
'py/foo/bar.py', apigen_relpath + 'source/foo/bar.py.html')
|
||||||
|
assert resolve_linkrole('source', 'py/foo/', False) == (
|
||||||
|
'py/foo/', apigen_relpath + 'source/foo/index.html')
|
||||||
|
assert resolve_linkrole('source', 'py/', False) == (
|
||||||
|
'py/', apigen_relpath + 'source/index.html')
|
||||||
|
py.test.raises(AssertionError, 'resolve_linkrole("source", "/foo/bar/")')
|
||||||
|
|
||||||
|
def test_resolve_linkrole_check_api(self):
|
||||||
|
assert resolve_linkrole('api', 'py.test.ensuretemp')
|
||||||
|
py.test.raises(AssertionError, "resolve_linkrole('api', 'py.foo.baz')")
|
||||||
|
|
||||||
|
def test_resolve_linkrole_check_source(self):
|
||||||
|
assert resolve_linkrole('source', 'py/path/common.py')
|
||||||
|
py.test.raises(AssertionError,
|
||||||
|
"resolve_linkrole('source', 'py/foo/bar.py')")
|
||||||
|
|
||||||
|
|
||||||
|
def pytest_pyfuncarg_testdir(__call__, pyfuncitem):
|
||||||
|
testdir = __call__.execute(firstresult=True)
|
||||||
|
testdir.makepyfile(confrest="from py.__.misc.rest import Project")
|
||||||
|
testdir.plugins.append(RestdocPlugin())
|
||||||
|
return testdir
|
||||||
|
|
||||||
|
class TestDoctest:
|
||||||
|
def test_doctest_extra_exec(self, testdir):
|
||||||
|
xtxt = testdir.maketxtfile(x="""
|
||||||
|
hello::
|
||||||
|
.. >>> raise ValueError
|
||||||
|
>>> None
|
||||||
|
""")
|
||||||
|
sorter = testdir.inline_run(xtxt)
|
||||||
|
passed, skipped, failed = sorter.countoutcomes()
|
||||||
|
assert failed == 1
|
||||||
|
|
||||||
|
def test_doctest_basic(self, testdir):
|
||||||
|
xtxt = testdir.maketxtfile(x="""
|
||||||
|
..
|
||||||
|
>>> from os.path import abspath
|
||||||
|
|
||||||
|
hello world
|
||||||
|
|
||||||
|
>>> assert abspath
|
||||||
|
>>> i=3
|
||||||
|
>>> print i
|
||||||
|
3
|
||||||
|
|
||||||
|
yes yes
|
||||||
|
|
||||||
|
>>> i
|
||||||
|
3
|
||||||
|
|
||||||
|
end
|
||||||
|
""")
|
||||||
|
sorter = testdir.inline_run(xtxt)
|
||||||
|
passed, skipped, failed = sorter.countoutcomes()
|
||||||
|
assert failed == 0
|
||||||
|
assert passed + skipped == 2
|
||||||
|
|
||||||
|
def test_doctest_eol(self, testdir):
|
||||||
|
ytxt = testdir.maketxtfile(y=".. >>> 1 + 1\r\n 2\r\n\r\n")
|
||||||
|
sorter = testdir.inline_run(ytxt)
|
||||||
|
passed, skipped, failed = sorter.countoutcomes()
|
||||||
|
assert failed == 0
|
||||||
|
assert passed + skipped == 2
|
||||||
|
|
||||||
|
def test_doctest_indentation(self, testdir):
|
||||||
|
footxt = testdir.maketxtfile(foo=
|
||||||
|
'..\n >>> print "foo\\n bar"\n foo\n bar\n')
|
||||||
|
sorter = testdir.inline_run(footxt)
|
||||||
|
passed, skipped, failed = sorter.countoutcomes()
|
||||||
|
assert failed == 0
|
||||||
|
assert skipped + passed == 2
|
||||||
|
|
||||||
|
def test_js_ignore(self, testdir):
|
||||||
|
xtxt = testdir.maketxtfile(xtxt="""
|
||||||
|
`blah`_
|
||||||
|
|
||||||
|
.. _`blah`: javascript:some_function()
|
||||||
|
""")
|
||||||
|
sorter = testdir.inline_run(xtxt)
|
||||||
|
passed, skipped, failed = sorter.countoutcomes()
|
||||||
|
assert failed == 0
|
||||||
|
assert skipped + passed == 3
|
||||||
|
|
||||||
|
def test_pytest_doctest_prepare_content(self, testdir):
|
||||||
|
l = []
|
||||||
|
class MyPlugin:
|
||||||
|
def pytest_doctest_prepare_content(self, content):
|
||||||
|
l.append(content)
|
||||||
|
return content.replace("False", "True")
|
||||||
|
|
||||||
|
testdir.plugins.append(MyPlugin())
|
||||||
|
|
||||||
|
xtxt = testdir.maketxtfile(x="""
|
||||||
|
hello:
|
||||||
|
|
||||||
|
>>> 2 == 2
|
||||||
|
False
|
||||||
|
|
||||||
|
""")
|
||||||
|
sorter = testdir.inline_run(xtxt)
|
||||||
|
assert len(l) == 1
|
||||||
|
passed, skipped, failed = sorter.countoutcomes()
|
||||||
|
assert not failed and not skipped
|
||||||
|
assert passed >= 1
|
||||||
|
|
@ -0,0 +1,253 @@
|
||||||
|
import py
|
||||||
|
|
||||||
|
class ResultlogPlugin:
|
||||||
|
"""resultlog plugin for machine-readable logging of test results.
|
||||||
|
Useful for buildbot integration code.
|
||||||
|
"""
|
||||||
|
def pytest_addoption(self, parser):
|
||||||
|
parser.addoption('--resultlog', action="store", dest="resultlog",
|
||||||
|
help="path for machine-readable result log")
|
||||||
|
|
||||||
|
def pytest_configure(self, config):
|
||||||
|
resultlog = config.option.resultlog
|
||||||
|
if resultlog:
|
||||||
|
logfile = open(resultlog, 'w', 1) # line buffered
|
||||||
|
self.resultlog = ResultLog(logfile)
|
||||||
|
config.bus.register(self.resultlog)
|
||||||
|
|
||||||
|
def pytest_unconfigure(self, config):
|
||||||
|
if hasattr(self, 'resultlog'):
|
||||||
|
self.resultlog.logfile.close()
|
||||||
|
del self.resultlog
|
||||||
|
#config.bus.unregister(self.resultlog)
|
||||||
|
|
||||||
|
def generic_path(item):
|
||||||
|
chain = item.listchain()
|
||||||
|
gpath = [chain[0].name]
|
||||||
|
fspath = chain[0].fspath
|
||||||
|
fspart = False
|
||||||
|
for node in chain[1:]:
|
||||||
|
newfspath = node.fspath
|
||||||
|
if newfspath == fspath:
|
||||||
|
if fspart:
|
||||||
|
gpath.append(':')
|
||||||
|
fspart = False
|
||||||
|
else:
|
||||||
|
gpath.append('.')
|
||||||
|
else:
|
||||||
|
gpath.append('/')
|
||||||
|
fspart = True
|
||||||
|
name = node.name
|
||||||
|
if name[0] in '([':
|
||||||
|
gpath.pop()
|
||||||
|
gpath.append(name)
|
||||||
|
fspath = newfspath
|
||||||
|
return ''.join(gpath)
|
||||||
|
|
||||||
|
class ResultLog(object):
|
||||||
|
def __init__(self, logfile):
|
||||||
|
self.logfile = logfile # preferably line buffered
|
||||||
|
|
||||||
|
def write_log_entry(self, shortrepr, name, longrepr):
|
||||||
|
print >>self.logfile, "%s %s" % (shortrepr, name)
|
||||||
|
for line in longrepr.splitlines():
|
||||||
|
print >>self.logfile, " %s" % line
|
||||||
|
|
||||||
|
def getoutcomecodes(self, ev):
|
||||||
|
if isinstance(ev, ev.CollectionReport):
|
||||||
|
# encode pass/fail/skip indepedent of terminal reporting semantics
|
||||||
|
# XXX handle collection and item reports more uniformly
|
||||||
|
assert not ev.passed
|
||||||
|
if ev.failed:
|
||||||
|
code = "F"
|
||||||
|
elif ev.skipped:
|
||||||
|
code = "S"
|
||||||
|
longrepr = str(ev.longrepr.reprcrash)
|
||||||
|
else:
|
||||||
|
assert isinstance(ev, ev.ItemTestReport)
|
||||||
|
code = ev.shortrepr
|
||||||
|
if ev.passed:
|
||||||
|
longrepr = ""
|
||||||
|
elif ev.failed:
|
||||||
|
longrepr = str(ev.longrepr)
|
||||||
|
elif ev.skipped:
|
||||||
|
longrepr = str(ev.longrepr.reprcrash.message)
|
||||||
|
return code, longrepr
|
||||||
|
|
||||||
|
def log_outcome(self, event):
|
||||||
|
if (not event.passed or isinstance(event, event.ItemTestReport)):
|
||||||
|
gpath = generic_path(event.colitem)
|
||||||
|
shortrepr, longrepr = self.getoutcomecodes(event)
|
||||||
|
self.write_log_entry(shortrepr, gpath, longrepr)
|
||||||
|
|
||||||
|
def pyevent(self, eventname, event, *args, **kwargs):
|
||||||
|
if eventname == "itemtestreport":
|
||||||
|
self.log_outcome(event)
|
||||||
|
elif eventname == "collectionreport":
|
||||||
|
if not event.passed:
|
||||||
|
self.log_outcome(event)
|
||||||
|
elif eventname == "internalerror":
|
||||||
|
path = event.repr.reprcrash.path # fishing :(
|
||||||
|
self.write_log_entry('!', path, str(event.repr))
|
||||||
|
|
||||||
|
# ===============================================================================
|
||||||
|
#
|
||||||
|
# plugin tests
|
||||||
|
#
|
||||||
|
# ===============================================================================
|
||||||
|
|
||||||
|
import os, StringIO
|
||||||
|
|
||||||
|
def test_generic_path():
|
||||||
|
from py.__.test.collect import Node, Item, FSCollector
|
||||||
|
p1 = Node('a', config='dummy')
|
||||||
|
assert p1.fspath is None
|
||||||
|
p2 = Node('B', parent=p1)
|
||||||
|
p3 = Node('()', parent = p2)
|
||||||
|
item = Item('c', parent = p3)
|
||||||
|
|
||||||
|
res = generic_path(item)
|
||||||
|
assert res == 'a.B().c'
|
||||||
|
|
||||||
|
p0 = FSCollector('proj/test', config='dummy')
|
||||||
|
p1 = FSCollector('proj/test/a', parent=p0)
|
||||||
|
p2 = Node('B', parent=p1)
|
||||||
|
p3 = Node('()', parent = p2)
|
||||||
|
p4 = Node('c', parent=p3)
|
||||||
|
item = Item('[1]', parent = p4)
|
||||||
|
|
||||||
|
res = generic_path(item)
|
||||||
|
assert res == 'test/a:B().c[1]'
|
||||||
|
|
||||||
|
def test_write_log_entry():
|
||||||
|
reslog = ResultLog(None)
|
||||||
|
reslog.logfile = StringIO.StringIO()
|
||||||
|
reslog.write_log_entry('.', 'name', '')
|
||||||
|
entry = reslog.logfile.getvalue()
|
||||||
|
assert entry[-1] == '\n'
|
||||||
|
entry_lines = entry.splitlines()
|
||||||
|
assert len(entry_lines) == 1
|
||||||
|
assert entry_lines[0] == '. name'
|
||||||
|
|
||||||
|
reslog.logfile = StringIO.StringIO()
|
||||||
|
reslog.write_log_entry('s', 'name', 'Skipped')
|
||||||
|
entry = reslog.logfile.getvalue()
|
||||||
|
assert entry[-1] == '\n'
|
||||||
|
entry_lines = entry.splitlines()
|
||||||
|
assert len(entry_lines) == 2
|
||||||
|
assert entry_lines[0] == 's name'
|
||||||
|
assert entry_lines[1] == ' Skipped'
|
||||||
|
|
||||||
|
reslog.logfile = StringIO.StringIO()
|
||||||
|
reslog.write_log_entry('s', 'name', 'Skipped\n')
|
||||||
|
entry = reslog.logfile.getvalue()
|
||||||
|
assert entry[-1] == '\n'
|
||||||
|
entry_lines = entry.splitlines()
|
||||||
|
assert len(entry_lines) == 2
|
||||||
|
assert entry_lines[0] == 's name'
|
||||||
|
assert entry_lines[1] == ' Skipped'
|
||||||
|
|
||||||
|
reslog.logfile = StringIO.StringIO()
|
||||||
|
longrepr = ' tb1\n tb 2\nE tb3\nSome Error'
|
||||||
|
reslog.write_log_entry('F', 'name', longrepr)
|
||||||
|
entry = reslog.logfile.getvalue()
|
||||||
|
assert entry[-1] == '\n'
|
||||||
|
entry_lines = entry.splitlines()
|
||||||
|
assert len(entry_lines) == 5
|
||||||
|
assert entry_lines[0] == 'F name'
|
||||||
|
assert entry_lines[1:] == [' '+line for line in longrepr.splitlines()]
|
||||||
|
|
||||||
|
|
||||||
|
class TestWithFunctionIntegration:
|
||||||
|
# XXX (hpk) i think that the resultlog plugin should
|
||||||
|
# provide a Parser object so that one can remain
|
||||||
|
# ignorant regarding formatting details.
|
||||||
|
def getresultlog(self, testdir, arg):
|
||||||
|
resultlog = testdir.tmpdir.join("resultlog")
|
||||||
|
args = ["--resultlog=%s" % resultlog] + [arg]
|
||||||
|
testdir.runpytest(*args)
|
||||||
|
return filter(None, resultlog.readlines(cr=0))
|
||||||
|
|
||||||
|
def test_collection_report(self, plugintester):
|
||||||
|
testdir = plugintester.testdir()
|
||||||
|
ok = testdir.makepyfile(test_collection_ok="")
|
||||||
|
skip = testdir.makepyfile(test_collection_skip="import py ; py.test.skip('hello')")
|
||||||
|
fail = testdir.makepyfile(test_collection_fail="XXX")
|
||||||
|
|
||||||
|
lines = self.getresultlog(testdir, ok)
|
||||||
|
assert not lines
|
||||||
|
|
||||||
|
lines = self.getresultlog(testdir, skip)
|
||||||
|
assert len(lines) == 2
|
||||||
|
assert lines[0].startswith("S ")
|
||||||
|
assert lines[0].endswith("test_collection_skip.py")
|
||||||
|
assert lines[1].startswith(" ")
|
||||||
|
assert lines[1].endswith("test_collection_skip.py:1: Skipped: 'hello'")
|
||||||
|
|
||||||
|
lines = self.getresultlog(testdir, fail)
|
||||||
|
assert lines
|
||||||
|
assert lines[0].startswith("F ")
|
||||||
|
assert lines[0].endswith("test_collection_fail.py"), lines[0]
|
||||||
|
for x in lines[1:]:
|
||||||
|
assert x.startswith(" ")
|
||||||
|
assert "XXX" in "".join(lines[1:])
|
||||||
|
|
||||||
|
def test_log_test_outcomes(self, plugintester):
|
||||||
|
testdir = plugintester.testdir()
|
||||||
|
mod = testdir.makepyfile(test_mod="""
|
||||||
|
import py
|
||||||
|
def test_pass(): pass
|
||||||
|
def test_skip(): py.test.skip("hello")
|
||||||
|
def test_fail(): raise ValueError("val")
|
||||||
|
""")
|
||||||
|
lines = self.getresultlog(testdir, mod)
|
||||||
|
assert len(lines) >= 3
|
||||||
|
assert lines[0].startswith(". ")
|
||||||
|
assert lines[0].endswith("test_pass")
|
||||||
|
assert lines[1].startswith("s "), lines[1]
|
||||||
|
assert lines[1].endswith("test_skip")
|
||||||
|
assert lines[2].find("hello") != -1
|
||||||
|
|
||||||
|
assert lines[3].startswith("F ")
|
||||||
|
assert lines[3].endswith("test_fail")
|
||||||
|
tb = "".join(lines[4:])
|
||||||
|
assert tb.find("ValueError") != -1
|
||||||
|
|
||||||
|
def test_internal_exception(self):
|
||||||
|
# they are produced for example by a teardown failing
|
||||||
|
# at the end of the run
|
||||||
|
from py.__.test import event
|
||||||
|
try:
|
||||||
|
raise ValueError
|
||||||
|
except ValueError:
|
||||||
|
excinfo = event.InternalException()
|
||||||
|
reslog = ResultLog(StringIO.StringIO())
|
||||||
|
reslog.pyevent("internalerror", excinfo)
|
||||||
|
entry = reslog.logfile.getvalue()
|
||||||
|
entry_lines = entry.splitlines()
|
||||||
|
|
||||||
|
assert entry_lines[0].startswith('! ')
|
||||||
|
assert os.path.basename(__file__)[:-1] in entry_lines[0] #.py/.pyc
|
||||||
|
assert entry_lines[-1][0] == ' '
|
||||||
|
assert 'ValueError' in entry
|
||||||
|
|
||||||
|
def test_generic(plugintester, LineMatcher):
|
||||||
|
plugintester.apicheck(ResultlogPlugin)
|
||||||
|
testdir = plugintester.testdir()
|
||||||
|
testdir.makepyfile("""
|
||||||
|
import py
|
||||||
|
def test_pass():
|
||||||
|
pass
|
||||||
|
def test_fail():
|
||||||
|
assert 0
|
||||||
|
def test_skip():
|
||||||
|
py.test.skip("")
|
||||||
|
""")
|
||||||
|
testdir.runpytest("--resultlog=result.log")
|
||||||
|
lines = testdir.tmpdir.join("result.log").readlines(cr=0)
|
||||||
|
LineMatcher(lines).fnmatch_lines([
|
||||||
|
". *:test_pass",
|
||||||
|
"F *:test_fail",
|
||||||
|
"s *:test_skip",
|
||||||
|
])
|
||||||
|
|
||||||
|
|
@ -0,0 +1,628 @@
|
||||||
|
import py
|
||||||
|
import sys
|
||||||
|
|
||||||
|
class TerminalPlugin(object):
|
||||||
|
""" Report a test run to a terminal. """
|
||||||
|
def pytest_addoption(self, parser):
|
||||||
|
parser.addoption('--collectonly',
|
||||||
|
action="store_true", dest="collectonly",
|
||||||
|
help="only collect tests, don't execute them."),
|
||||||
|
|
||||||
|
def pytest_configure(self, config):
|
||||||
|
if config.option.collectonly:
|
||||||
|
self.reporter = CollectonlyReporter(config)
|
||||||
|
else:
|
||||||
|
self.reporter = TerminalReporter(config)
|
||||||
|
# XXX see remote.py's XXX
|
||||||
|
for attr in 'pytest_terminal_hasmarkup', 'pytest_terminal_fullwidth':
|
||||||
|
if hasattr(config, attr):
|
||||||
|
#print "SETTING TERMINAL OPTIONS", attr, getattr(config, attr)
|
||||||
|
name = attr.split("_")[-1]
|
||||||
|
assert hasattr(self.reporter._tw, name), name
|
||||||
|
setattr(self.reporter._tw, name, getattr(config, attr))
|
||||||
|
config.bus.register(self.reporter)
|
||||||
|
|
||||||
|
class TerminalReporter:
|
||||||
|
def __init__(self, config, file=None):
|
||||||
|
self.config = config
|
||||||
|
self.stats = {}
|
||||||
|
self.curdir = py.path.local()
|
||||||
|
if file is None:
|
||||||
|
file = py.std.sys.stdout
|
||||||
|
self._tw = py.io.TerminalWriter(file)
|
||||||
|
self.currentfspath = None
|
||||||
|
|
||||||
|
def write_fspath_result(self, fspath, res):
|
||||||
|
if fspath != self.currentfspath:
|
||||||
|
self._tw.line()
|
||||||
|
relpath = self.curdir.bestrelpath(fspath)
|
||||||
|
self._tw.write(relpath + " ")
|
||||||
|
self.currentfspath = fspath
|
||||||
|
self._tw.write(res)
|
||||||
|
|
||||||
|
def write_ensure_prefix(self, prefix, extra=""):
|
||||||
|
if self.currentfspath != prefix:
|
||||||
|
self._tw.line()
|
||||||
|
self.currentfspath = prefix
|
||||||
|
self._tw.write(prefix)
|
||||||
|
if extra:
|
||||||
|
self._tw.write(extra)
|
||||||
|
self.currentfspath = -2
|
||||||
|
|
||||||
|
def ensure_newline(self):
|
||||||
|
if self.currentfspath:
|
||||||
|
self._tw.line()
|
||||||
|
self.currentfspath = None
|
||||||
|
|
||||||
|
def write_line(self, line, **markup):
|
||||||
|
line = str(line)
|
||||||
|
self.ensure_newline()
|
||||||
|
self._tw.line(line, **markup)
|
||||||
|
|
||||||
|
def write_sep(self, sep, title=None, **markup):
|
||||||
|
self.ensure_newline()
|
||||||
|
self._tw.sep(sep, title, **markup)
|
||||||
|
|
||||||
|
def getcategoryletterword(self, event):
|
||||||
|
res = self.config.pytestplugins.call_firstresult("pytest_report_teststatus", event=event)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
for cat in 'skipped failed passed ???'.split():
|
||||||
|
if getattr(event, cat, None):
|
||||||
|
break
|
||||||
|
return cat, self.getoutcomeletter(event), self.getoutcomeword(event)
|
||||||
|
|
||||||
|
def getoutcomeletter(self, event):
|
||||||
|
return event.shortrepr
|
||||||
|
|
||||||
|
def getoutcomeword(self, event):
|
||||||
|
if event.passed:
|
||||||
|
return self._tw.markup("PASS", green=True)
|
||||||
|
elif event.failed:
|
||||||
|
return self._tw.markup("FAIL", red=True)
|
||||||
|
elif event.skipped:
|
||||||
|
return "SKIP"
|
||||||
|
else:
|
||||||
|
return self._tw.markup("???", red=True)
|
||||||
|
|
||||||
|
def pyevent_internalerror(self, event):
|
||||||
|
for line in str(event.repr).split("\n"):
|
||||||
|
self.write_line("InternalException: " + line)
|
||||||
|
|
||||||
|
def pyevent_hostgatewayready(self, event):
|
||||||
|
if self.config.option.verbose:
|
||||||
|
self.write_line("HostGatewayReady: %s" %(event.host,))
|
||||||
|
|
||||||
|
def pyevent_plugin_registered(self, plugin):
|
||||||
|
if self.config.option.traceconfig:
|
||||||
|
msg = "PLUGIN registered: %s" %(plugin,)
|
||||||
|
# XXX this event may happen during setup/teardown time
|
||||||
|
# which unfortunately captures our output here
|
||||||
|
# which garbles our output if we use self.write_line
|
||||||
|
self.write_line(msg)
|
||||||
|
|
||||||
|
def pyevent_hostup(self, event):
|
||||||
|
d = event.platinfo.copy()
|
||||||
|
d['hostid'] = event.host.hostid
|
||||||
|
d['version'] = repr_pythonversion(d['sys.version_info'])
|
||||||
|
self.write_line("HOSTUP: %(hostid)s %(sys.platform)s "
|
||||||
|
"%(sys.executable)s - Python %(version)s" %
|
||||||
|
d)
|
||||||
|
|
||||||
|
def pyevent_hostdown(self, event):
|
||||||
|
host = event.host
|
||||||
|
error = event.error
|
||||||
|
if error:
|
||||||
|
self.write_line("HostDown %s: %s" %(host.hostid, error))
|
||||||
|
|
||||||
|
def pyevent_itemstart(self, event):
|
||||||
|
if self.config.option.verbose:
|
||||||
|
info = event.item.repr_metainfo()
|
||||||
|
line = info.verboseline(basedir=self.curdir) + " "
|
||||||
|
extra = ""
|
||||||
|
if event.host:
|
||||||
|
extra = "-> " + event.host.hostid
|
||||||
|
self.write_ensure_prefix(line, extra)
|
||||||
|
else:
|
||||||
|
# ensure that the path is printed before the 1st test of
|
||||||
|
# a module starts running
|
||||||
|
fspath = event.item.fspath
|
||||||
|
self.write_fspath_result(fspath, "")
|
||||||
|
|
||||||
|
def pyevent_rescheduleitems(self, event):
|
||||||
|
if self.config.option.debug:
|
||||||
|
self.write_sep("!", "RESCHEDULING %s " %(event.items,))
|
||||||
|
|
||||||
|
def pyevent_deselected(self, event):
|
||||||
|
self.stats.setdefault('deselected', []).append(event)
|
||||||
|
|
||||||
|
def pyevent_itemtestreport(self, event):
|
||||||
|
fspath = event.colitem.fspath
|
||||||
|
cat, letter, word = self.getcategoryletterword(event)
|
||||||
|
self.stats.setdefault(cat, []).append(event)
|
||||||
|
if not self.config.option.verbose:
|
||||||
|
self.write_fspath_result(fspath, letter)
|
||||||
|
else:
|
||||||
|
info = event.colitem.repr_metainfo()
|
||||||
|
line = info.verboseline(basedir=self.curdir) + " "
|
||||||
|
self.write_ensure_prefix(line, word)
|
||||||
|
|
||||||
|
def pyevent_collectionreport(self, event):
|
||||||
|
if not event.passed:
|
||||||
|
if event.failed:
|
||||||
|
self.stats.setdefault("failed", []).append(event)
|
||||||
|
msg = event.longrepr.reprcrash.message
|
||||||
|
self.write_fspath_result(event.colitem.fspath, "F")
|
||||||
|
elif event.skipped:
|
||||||
|
self.stats.setdefault("skipped", []).append(event)
|
||||||
|
self.write_fspath_result(event.colitem.fspath, "S")
|
||||||
|
|
||||||
|
def pyevent_testrunstart(self, event):
|
||||||
|
self.write_sep("=", "test session starts", bold=True)
|
||||||
|
self._sessionstarttime = py.std.time.time()
|
||||||
|
rev = py.__pkg__.getrev()
|
||||||
|
self.write_line("using py lib: %s <rev %s>" % (
|
||||||
|
py.path.local(py.__file__).dirpath(), rev))
|
||||||
|
plugins = []
|
||||||
|
for x in self.config.pytestplugins._plugins:
|
||||||
|
if isinstance(x, str) and x.startswith("pytest_"):
|
||||||
|
plugins.append(x[7:])
|
||||||
|
else:
|
||||||
|
plugins.append(str(x)) # XXX display conftest plugins more nicely
|
||||||
|
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 pyevent_testrunfinish(self, event):
|
||||||
|
self._tw.line("")
|
||||||
|
if event.exitstatus in (0, 1, 2):
|
||||||
|
self.summary_failures()
|
||||||
|
self.summary_skips()
|
||||||
|
self.config.pytestplugins.call_each("pytest_terminal_summary", terminalreporter=self)
|
||||||
|
if event.excrepr is not None:
|
||||||
|
self.summary_final_exc(event.excrepr)
|
||||||
|
if event.exitstatus == 2:
|
||||||
|
self.write_sep("!", "KEYBOARD INTERRUPT")
|
||||||
|
self.summary_deselected()
|
||||||
|
self.summary_stats()
|
||||||
|
|
||||||
|
def pyevent_looponfailinginfo(self, event):
|
||||||
|
if event.failreports:
|
||||||
|
self.write_sep("#", "LOOPONFAILING", red=True)
|
||||||
|
for report in event.failreports:
|
||||||
|
try:
|
||||||
|
loc = report.longrepr.reprcrash
|
||||||
|
except AttributeError:
|
||||||
|
loc = str(report.longrepr)[:50]
|
||||||
|
self.write_line(loc, red=True)
|
||||||
|
self.write_sep("#", "waiting for changes")
|
||||||
|
for rootdir in event.rootdirs:
|
||||||
|
self.write_line("### Watching: %s" %(rootdir,), bold=True)
|
||||||
|
|
||||||
|
#
|
||||||
|
# summaries for TestrunFinish
|
||||||
|
#
|
||||||
|
|
||||||
|
def summary_failures(self):
|
||||||
|
if 'failed' in self.stats and self.config.option.tbstyle != "no":
|
||||||
|
self.write_sep("=", "FAILURES")
|
||||||
|
for ev in self.stats['failed']:
|
||||||
|
self.write_sep("_")
|
||||||
|
ev.toterminal(self._tw)
|
||||||
|
|
||||||
|
def summary_stats(self):
|
||||||
|
session_duration = py.std.time.time() - self._sessionstarttime
|
||||||
|
|
||||||
|
keys = "failed passed skipped deselected".split()
|
||||||
|
parts = []
|
||||||
|
for key in keys:
|
||||||
|
val = self.stats.get(key, None)
|
||||||
|
if val:
|
||||||
|
parts.append("%d %s" %(len(val), key))
|
||||||
|
line = ", ".join(parts)
|
||||||
|
# XXX coloring
|
||||||
|
self.write_sep("=", "%s in %.2f seconds" %(line, session_duration))
|
||||||
|
|
||||||
|
def summary_deselected(self):
|
||||||
|
if 'deselected' in self.stats:
|
||||||
|
self.write_sep("=", "%d tests deselected by %r" %(
|
||||||
|
len(self.stats['deselected']), self.config.option.keyword), bold=True)
|
||||||
|
|
||||||
|
def summary_skips(self):
|
||||||
|
if 'skipped' in self.stats:
|
||||||
|
if 'failed' not in self.stats or self.config.option.showskipsummary:
|
||||||
|
fskips = folded_skips(self.stats['skipped'])
|
||||||
|
if fskips:
|
||||||
|
self.write_sep("_", "skipped test summary")
|
||||||
|
for num, fspath, lineno, reason in fskips:
|
||||||
|
self._tw.line("%s:%d: [%d] %s" %(fspath, lineno, num, reason))
|
||||||
|
|
||||||
|
def summary_final_exc(self, excrepr):
|
||||||
|
self.write_sep("!")
|
||||||
|
if self.config.option.verbose:
|
||||||
|
excrepr.toterminal(self._tw)
|
||||||
|
else:
|
||||||
|
excrepr.reprcrash.toterminal(self._tw)
|
||||||
|
|
||||||
|
def out_hostinfo(self):
|
||||||
|
self._tw.line("host 0: %s %s - Python %s" %
|
||||||
|
(py.std.sys.platform,
|
||||||
|
py.std.sys.executable,
|
||||||
|
repr_pythonversion()))
|
||||||
|
|
||||||
|
class CollectonlyReporter:
|
||||||
|
INDENT = " "
|
||||||
|
|
||||||
|
def __init__(self, config, out=None):
|
||||||
|
self.config = config
|
||||||
|
if out is None:
|
||||||
|
out = py.std.sys.stdout
|
||||||
|
self.out = py.io.TerminalWriter(out)
|
||||||
|
self.indent = ""
|
||||||
|
self._failed = []
|
||||||
|
|
||||||
|
def outindent(self, line):
|
||||||
|
self.out.line(self.indent + str(line))
|
||||||
|
|
||||||
|
def pyevent_collectionstart(self, event):
|
||||||
|
self.outindent(event.collector)
|
||||||
|
self.indent += self.INDENT
|
||||||
|
|
||||||
|
def pyevent_itemstart(self, event):
|
||||||
|
self.outindent(event.item)
|
||||||
|
|
||||||
|
def pyevent_collectionreport(self, event):
|
||||||
|
if not event.passed:
|
||||||
|
self.outindent("!!! %s !!!" % event.longrepr.reprcrash.message)
|
||||||
|
self._failed.append(event)
|
||||||
|
self.indent = self.indent[:-len(self.INDENT)]
|
||||||
|
|
||||||
|
def pyevent_testrunfinish(self, event):
|
||||||
|
if self._failed:
|
||||||
|
self.out.sep("!", "collection failures")
|
||||||
|
for event in self._failed:
|
||||||
|
event.toterminal(self.out)
|
||||||
|
|
||||||
|
def folded_skips(skipped):
|
||||||
|
d = {}
|
||||||
|
for event in skipped:
|
||||||
|
entry = event.longrepr.reprcrash
|
||||||
|
key = entry.path, entry.lineno, entry.message
|
||||||
|
d.setdefault(key, []).append(event)
|
||||||
|
l = []
|
||||||
|
for key, events in d.iteritems():
|
||||||
|
l.append((len(events),) + key)
|
||||||
|
return l
|
||||||
|
|
||||||
|
def repr_pythonversion(v=None):
|
||||||
|
if v is None:
|
||||||
|
v = sys.version_info
|
||||||
|
try:
|
||||||
|
return "%s.%s.%s-%s-%s" % v
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return str(v)
|
||||||
|
|
||||||
|
# ===============================================================================
|
||||||
|
#
|
||||||
|
# plugin tests
|
||||||
|
#
|
||||||
|
# ===============================================================================
|
||||||
|
|
||||||
|
from py.__.test import event
|
||||||
|
from py.__.test.runner import basic_run_report
|
||||||
|
from py.__.test.dsession.hostmanage import Host, makehostup
|
||||||
|
|
||||||
|
class TestTerminal:
|
||||||
|
def test_hostup(self, testdir, linecomp):
|
||||||
|
item = testdir.getitem("def test_func(): pass")
|
||||||
|
rep = TerminalReporter(item._config, linecomp.stringio)
|
||||||
|
host = Host("localhost")
|
||||||
|
rep.pyevent_hostup(makehostup(host))
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
"*%s %s %s - Python %s" %(host.hostid, sys.platform,
|
||||||
|
sys.executable, repr_pythonversion(sys.version_info))
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_pass_skip_fail(self, testdir, linecomp):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
import py
|
||||||
|
def test_ok():
|
||||||
|
pass
|
||||||
|
def test_skip():
|
||||||
|
py.test.skip("xx")
|
||||||
|
def test_func():
|
||||||
|
assert 0
|
||||||
|
""")
|
||||||
|
rep = TerminalReporter(modcol._config, file=linecomp.stringio)
|
||||||
|
rep.config.bus.register(rep)
|
||||||
|
rep.config.bus.notify("testrunstart", event.TestrunStart())
|
||||||
|
|
||||||
|
for item in testdir.genitems([modcol]):
|
||||||
|
ev = basic_run_report(item)
|
||||||
|
rep.config.bus.notify("itemtestreport", ev)
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
"*test_pass_skip_fail.py .sF"
|
||||||
|
])
|
||||||
|
rep.config.bus.notify("testrunfinish", event.TestrunFinish())
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
" def test_func():",
|
||||||
|
"> assert 0",
|
||||||
|
"E assert 0",
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_pass_skip_fail_verbose(self, testdir, linecomp):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
import py
|
||||||
|
def test_ok():
|
||||||
|
pass
|
||||||
|
def test_skip():
|
||||||
|
py.test.skip("xx")
|
||||||
|
def test_func():
|
||||||
|
assert 0
|
||||||
|
""", configargs=("-v",))
|
||||||
|
rep = TerminalReporter(modcol._config, file=linecomp.stringio)
|
||||||
|
rep.config.bus.register(rep)
|
||||||
|
rep.config.bus.notify("testrunstart", event.TestrunStart())
|
||||||
|
items = modcol.collect()
|
||||||
|
for item in items:
|
||||||
|
rep.config.bus.notify("itemstart", event.ItemStart(item))
|
||||||
|
s = linecomp.stringio.getvalue().strip()
|
||||||
|
assert s.endswith(item.name)
|
||||||
|
rep.config.bus.notify("itemtestreport", basic_run_report(item))
|
||||||
|
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
"*test_pass_skip_fail_verbose.py:2: *test_ok*PASS",
|
||||||
|
"*test_pass_skip_fail_verbose.py:4: *test_skip*SKIP",
|
||||||
|
"*test_pass_skip_fail_verbose.py:6: *test_func*FAIL",
|
||||||
|
])
|
||||||
|
rep.config.bus.notify("testrunfinish", event.TestrunFinish())
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
" def test_func():",
|
||||||
|
"> assert 0",
|
||||||
|
"E assert 0",
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_collect_fail(self, testdir, linecomp):
|
||||||
|
modcol = testdir.getmodulecol("import xyz")
|
||||||
|
rep = TerminalReporter(modcol._config, file=linecomp.stringio)
|
||||||
|
rep.config.bus.register(rep)
|
||||||
|
rep.config.bus.notify("testrunstart", event.TestrunStart())
|
||||||
|
l = list(testdir.genitems([modcol]))
|
||||||
|
assert len(l) == 0
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
"*test_collect_fail.py F*"
|
||||||
|
])
|
||||||
|
rep.config.bus.notify("testrunfinish", event.TestrunFinish())
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
"> import xyz",
|
||||||
|
"E ImportError: No module named xyz"
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_internalerror(self, testdir, linecomp):
|
||||||
|
modcol = testdir.getmodulecol("def test_one(): pass")
|
||||||
|
rep = TerminalReporter(modcol._config, file=linecomp.stringio)
|
||||||
|
excinfo = py.test.raises(ValueError, "raise ValueError('hello')")
|
||||||
|
rep.pyevent_internalerror(event.InternalException(excinfo))
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
"InternalException: >*raise ValueError*"
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_hostready_crash(self, testdir, linecomp):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
def test_one():
|
||||||
|
pass
|
||||||
|
""", configargs=("-v",))
|
||||||
|
host1 = Host("localhost")
|
||||||
|
rep = TerminalReporter(modcol._config, file=linecomp.stringio)
|
||||||
|
rep.pyevent_hostgatewayready(event.HostGatewayReady(host1, None))
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
"*HostGatewayReady*"
|
||||||
|
])
|
||||||
|
rep.pyevent_hostdown(event.HostDown(host1, "myerror"))
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
"*HostDown*myerror*",
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_writeline(self, testdir, linecomp):
|
||||||
|
modcol = testdir.getmodulecol("def test_one(): pass")
|
||||||
|
stringio = py.std.cStringIO.StringIO()
|
||||||
|
rep = TerminalReporter(modcol._config, file=linecomp.stringio)
|
||||||
|
rep.write_fspath_result(py.path.local("xy.py"), '.')
|
||||||
|
rep.write_line("hello world")
|
||||||
|
lines = linecomp.stringio.getvalue().split('\n')
|
||||||
|
assert not lines[0]
|
||||||
|
assert lines[1].endswith("xy.py .")
|
||||||
|
assert lines[2] == "hello world"
|
||||||
|
|
||||||
|
def test_looponfailingreport(self, testdir, linecomp):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
def test_fail():
|
||||||
|
assert 0
|
||||||
|
def test_fail2():
|
||||||
|
raise ValueError()
|
||||||
|
""")
|
||||||
|
rep = TerminalReporter(modcol._config, file=linecomp.stringio)
|
||||||
|
reports = [basic_run_report(x) for x in modcol.collect()]
|
||||||
|
rep.pyevent_looponfailinginfo(event.LooponfailingInfo(reports, [modcol._config.topdir]))
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
"*test_looponfailingreport.py:2: assert 0",
|
||||||
|
"*test_looponfailingreport.py:4: ValueError*",
|
||||||
|
"*waiting*",
|
||||||
|
"*%s*" % (modcol._config.topdir),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_tb_option(self, testdir, linecomp):
|
||||||
|
# XXX usage of testdir and event bus
|
||||||
|
for tbopt in ["long", "short", "no"]:
|
||||||
|
print 'testing --tb=%s...' % tbopt
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
import py
|
||||||
|
def g():
|
||||||
|
raise IndexError
|
||||||
|
def test_func():
|
||||||
|
print 6*7
|
||||||
|
g() # --calling--
|
||||||
|
""", configargs=("--tb=%s" % tbopt,))
|
||||||
|
rep = TerminalReporter(modcol._config, file=linecomp.stringio)
|
||||||
|
rep.config.bus.register(rep)
|
||||||
|
rep.config.bus.notify("testrunstart", event.TestrunStart())
|
||||||
|
rep.config.bus.notify("testrunstart", event.TestrunStart())
|
||||||
|
for item in testdir.genitems([modcol]):
|
||||||
|
rep.config.bus.notify("itemtestreport", basic_run_report(item))
|
||||||
|
rep.config.bus.notify("testrunfinish", event.TestrunFinish())
|
||||||
|
s = linecomp.stringio.getvalue()
|
||||||
|
if tbopt == "long":
|
||||||
|
print s
|
||||||
|
assert 'print 6*7' in s
|
||||||
|
else:
|
||||||
|
assert 'print 6*7' not in s
|
||||||
|
if tbopt != "no":
|
||||||
|
assert '--calling--' in s
|
||||||
|
assert 'IndexError' in s
|
||||||
|
else:
|
||||||
|
assert 'FAILURES' not in s
|
||||||
|
assert '--calling--' not in s
|
||||||
|
assert 'IndexError' not in s
|
||||||
|
linecomp.stringio.truncate(0)
|
||||||
|
|
||||||
|
def test_show_path_before_running_test(self, testdir, linecomp):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
def test_foobar():
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
rep = TerminalReporter(modcol._config, file=linecomp.stringio)
|
||||||
|
modcol._config.bus.register(rep)
|
||||||
|
l = list(testdir.genitems([modcol]))
|
||||||
|
assert len(l) == 1
|
||||||
|
rep.config.bus.notify("itemstart", event.ItemStart(l[0]))
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
"*test_show_path_before_running_test.py*"
|
||||||
|
])
|
||||||
|
|
||||||
|
def pseudo_keyboard_interrupt(self, testdir, linecomp, verbose=False):
|
||||||
|
modcol = testdir.getmodulecol("""
|
||||||
|
def test_foobar():
|
||||||
|
assert 0
|
||||||
|
def test_spamegg():
|
||||||
|
import py; py.test.skip('skip me please!')
|
||||||
|
def test_interrupt_me():
|
||||||
|
raise KeyboardInterrupt # simulating the user
|
||||||
|
""", configargs=("--showskipsummary",) + ("-v",)*verbose)
|
||||||
|
rep = TerminalReporter(modcol._config, file=linecomp.stringio)
|
||||||
|
modcol._config.bus.register(rep)
|
||||||
|
bus = modcol._config.bus
|
||||||
|
bus.notify("testrunstart", event.TestrunStart())
|
||||||
|
try:
|
||||||
|
for item in testdir.genitems([modcol]):
|
||||||
|
bus.notify("itemtestreport", basic_run_report(item))
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
excinfo = py.code.ExceptionInfo()
|
||||||
|
else:
|
||||||
|
py.test.fail("no KeyboardInterrupt??")
|
||||||
|
s = linecomp.stringio.getvalue()
|
||||||
|
if not verbose:
|
||||||
|
assert s.find("_keyboard_interrupt.py Fs") != -1
|
||||||
|
bus.notify("testrunfinish", event.TestrunFinish(exitstatus=2, excinfo=excinfo))
|
||||||
|
text = linecomp.stringio.getvalue()
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
" def test_foobar():",
|
||||||
|
"> assert 0",
|
||||||
|
"E assert 0",
|
||||||
|
])
|
||||||
|
assert "Skipped: 'skip me please!'" in text
|
||||||
|
assert "_keyboard_interrupt.py:6: KeyboardInterrupt" in text
|
||||||
|
see_details = "raise KeyboardInterrupt # simulating the user" in text
|
||||||
|
assert see_details == verbose
|
||||||
|
|
||||||
|
def test_keyboard_interrupt(self, testdir, linecomp):
|
||||||
|
self.pseudo_keyboard_interrupt(testdir, linecomp)
|
||||||
|
|
||||||
|
def test_verbose_keyboard_interrupt(self, testdir, linecomp):
|
||||||
|
self.pseudo_keyboard_interrupt(testdir, linecomp, verbose=True)
|
||||||
|
|
||||||
|
def test_skip_reasons_folding(self):
|
||||||
|
class longrepr:
|
||||||
|
class reprcrash:
|
||||||
|
path = 'xyz'
|
||||||
|
lineno = 3
|
||||||
|
message = "justso"
|
||||||
|
|
||||||
|
ev1 = event.CollectionReport(None, None)
|
||||||
|
ev1.when = "execute"
|
||||||
|
ev1.skipped = True
|
||||||
|
ev1.longrepr = longrepr
|
||||||
|
|
||||||
|
ev2 = event.ItemTestReport(None, excinfo=longrepr)
|
||||||
|
ev2.skipped = True
|
||||||
|
|
||||||
|
l = folded_skips([ev1, ev2])
|
||||||
|
assert len(l) == 1
|
||||||
|
num, fspath, lineno, reason = l[0]
|
||||||
|
assert num == 2
|
||||||
|
assert fspath == longrepr.reprcrash.path
|
||||||
|
assert lineno == longrepr.reprcrash.lineno
|
||||||
|
assert reason == longrepr.reprcrash.message
|
||||||
|
|
||||||
|
class TestCollectonly:
|
||||||
|
def test_collectonly_basic(self, testdir, linecomp):
|
||||||
|
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
|
||||||
|
def test_func():
|
||||||
|
pass
|
||||||
|
""")
|
||||||
|
rep = CollectonlyReporter(modcol._config, out=linecomp.stringio)
|
||||||
|
modcol._config.bus.register(rep)
|
||||||
|
indent = rep.indent
|
||||||
|
rep.config.bus.notify("collectionstart", event.CollectionStart(modcol))
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
"<Module 'test_collectonly_basic.py'>"
|
||||||
|
])
|
||||||
|
item = modcol.join("test_func")
|
||||||
|
rep.config.bus.notify("itemstart", event.ItemStart(item))
|
||||||
|
linecomp.assert_contains_lines([
|
||||||
|
" <Function 'test_func'>",
|
||||||
|
])
|
||||||
|
rep.config.bus.notify( "collectionreport",
|
||||||
|
event.CollectionReport(modcol, [], excinfo=None))
|
||||||
|
assert rep.indent == indent
|
||||||
|
|
||||||
|
def test_collectonly_skipped_module(self, testdir, linecomp):
|
||||||
|
modcol = testdir.getmodulecol(configargs=['--collectonly'], source="""
|
||||||
|
import py
|
||||||
|
py.test.skip("nomod")
|
||||||
|
""")
|
||||||
|
rep = CollectonlyReporter(modcol._config, out=linecomp.stringio)
|
||||||
|
modcol._config.bus.register(rep)
|
||||||
|
cols = list(testdir.genitems([modcol]))
|
||||||
|
assert len(cols) == 0
|
||||||
|
linecomp.assert_contains_lines("""
|
||||||
|
<Module 'test_collectonly_skipped_module.py'>
|
||||||
|
!!! Skipped: 'nomod' !!!
|
||||||
|
""")
|
||||||
|
|
||||||
|
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.bus.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_repr_python_version():
|
||||||
|
py.magic.patch(sys, 'version_info', (2, 5, 1, 'final', 0))
|
||||||
|
try:
|
||||||
|
assert repr_pythonversion() == "2.5.1-final-0"
|
||||||
|
py.std.sys.version_info = x = (2,3)
|
||||||
|
assert repr_pythonversion() == str(x)
|
||||||
|
finally:
|
||||||
|
py.magic.revert(sys, 'version_info')
|
||||||
|
|
||||||
|
def test_generic(plugintester):
|
||||||
|
plugintester.apicheck(TerminalPlugin)
|
||||||
|
plugintester.apicheck(TerminalReporter)
|
||||||
|
plugintester.apicheck(CollectonlyReporter)
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
"""
|
||||||
|
example:
|
||||||
|
|
||||||
|
pytest_plugins = "pytest_tmpdir"
|
||||||
|
|
||||||
|
def test_plugin(tmpdir):
|
||||||
|
tmpdir.join("hello").write("hello")
|
||||||
|
|
||||||
|
"""
|
||||||
|
import py
|
||||||
|
|
||||||
|
class TmpdirPlugin:
|
||||||
|
""" provide temporary directories to test functions and methods.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def pytest_configure(self, config):
|
||||||
|
# XXX make ensuretemp live on config
|
||||||
|
self.basetmp = py.test.ensuretemp("tmpdir")
|
||||||
|
|
||||||
|
def pytest_pyfuncarg_tmpdir(self, pyfuncitem):
|
||||||
|
name = pyfuncitem.name
|
||||||
|
for i in range(10000):
|
||||||
|
try:
|
||||||
|
tmpdir = self.basetmp.mkdir(name + (i and str(i) or ''))
|
||||||
|
except py.error.EEXIST:
|
||||||
|
continue
|
||||||
|
break
|
||||||
|
return tmpdir
|
||||||
|
|
||||||
|
# ===============================================================================
|
||||||
|
#
|
||||||
|
# plugin tests
|
||||||
|
#
|
||||||
|
# ===============================================================================
|
||||||
|
#
|
||||||
|
def test_generic(plugintester):
|
||||||
|
plugintester.apicheck(TmpdirPlugin)
|
||||||
|
|
||||||
|
def test_pyfuncarg(testdir):
|
||||||
|
item = testdir.getitem("def test_func(tmpdir): pass")
|
||||||
|
plugin = TmpdirPlugin()
|
||||||
|
plugin.pytest_configure(item._config)
|
||||||
|
p = plugin.pytest_pyfuncarg_tmpdir(item)
|
||||||
|
assert p.check()
|
||||||
|
assert p.basename.endswith("test_func")
|
||||||
|
|
@ -0,0 +1,124 @@
|
||||||
|
"""
|
||||||
|
automatically collect and run traditional "unittest.py" style tests.
|
||||||
|
|
||||||
|
you can mix unittest TestCase subclasses and
|
||||||
|
py.test style tests in one test module.
|
||||||
|
|
||||||
|
XXX consider user-specified test_suite()
|
||||||
|
|
||||||
|
this code is somewhat derived from Guido Wesdorps
|
||||||
|
|
||||||
|
http://johnnydebris.net/svn/projects/py_unittest
|
||||||
|
|
||||||
|
$HeadURL: https://codespeak.net/svn/py/branch/pytestplugin/contrib/py_unittest/conftest.py $
|
||||||
|
$Id: conftest.py 60979 2009-01-14 22:29:32Z hpk $
|
||||||
|
"""
|
||||||
|
import py
|
||||||
|
|
||||||
|
class UnittestPlugin:
|
||||||
|
""" discover and integrate traditional ``unittest.py`` tests.
|
||||||
|
"""
|
||||||
|
def pytest_pymodule_makeitem(self, modcol, name, obj):
|
||||||
|
if py.std.inspect.isclass(obj) and issubclass(obj, py.std.unittest.TestCase):
|
||||||
|
return UnitTestCase(name, parent=modcol)
|
||||||
|
|
||||||
|
class UnitTestCase(py.test.collect.Class):
|
||||||
|
def collect(self):
|
||||||
|
return [UnitTestCaseInstance("()", self)]
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
_dummy = object()
|
||||||
|
class UnitTestCaseInstance(py.test.collect.Instance):
|
||||||
|
def collect(self):
|
||||||
|
loader = py.std.unittest.TestLoader()
|
||||||
|
names = loader.getTestCaseNames(self.obj.__class__)
|
||||||
|
l = []
|
||||||
|
for name in names:
|
||||||
|
callobj = getattr(self.obj, name)
|
||||||
|
if callable(callobj):
|
||||||
|
l.append(UnitTestFunction(name, parent=self))
|
||||||
|
return l
|
||||||
|
|
||||||
|
def _getobj(self):
|
||||||
|
x = self.parent.obj
|
||||||
|
return self.parent.obj(methodName='run')
|
||||||
|
|
||||||
|
class UnitTestFunction(py.test.collect.Function):
|
||||||
|
def __init__(self, name, parent, args=(), obj=_dummy, sort_value=None):
|
||||||
|
super(UnitTestFunction, self).__init__(name, parent)
|
||||||
|
self._args = args
|
||||||
|
if obj is not _dummy:
|
||||||
|
self._obj = obj
|
||||||
|
self._sort_value = sort_value
|
||||||
|
|
||||||
|
def runtest(self):
|
||||||
|
target = self.obj
|
||||||
|
args = self._args
|
||||||
|
target(*args)
|
||||||
|
|
||||||
|
def setup(self):
|
||||||
|
instance = self.obj.im_self
|
||||||
|
instance.setUp()
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
instance = self.obj.im_self
|
||||||
|
instance.tearDown()
|
||||||
|
|
||||||
|
|
||||||
|
def test_generic(plugintester):
|
||||||
|
plugintester.apicheck(UnittestPlugin)
|
||||||
|
|
||||||
|
def test_simple_unittest(testdir):
|
||||||
|
testpath = testdir.makepyfile("""
|
||||||
|
import unittest
|
||||||
|
pytest_plugins = "pytest_unittest"
|
||||||
|
class MyTestCase(unittest.TestCase):
|
||||||
|
def testpassing(self):
|
||||||
|
self.assertEquals('foo', 'foo')
|
||||||
|
def test_failing(self):
|
||||||
|
self.assertEquals('foo', 'bar')
|
||||||
|
""")
|
||||||
|
sorter = testdir.inline_run(testpath)
|
||||||
|
assert sorter.getreport("testpassing").passed
|
||||||
|
assert sorter.getreport("test_failing").failed
|
||||||
|
|
||||||
|
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
|
||||||
|
def test_setUp(self):
|
||||||
|
self.assertEquals(1, self.foo)
|
||||||
|
""")
|
||||||
|
sorter = testdir.inline_run(testpath)
|
||||||
|
rep = sorter.getreport("test_setUp")
|
||||||
|
assert rep.passed
|
||||||
|
|
||||||
|
def test_teardown(testdir):
|
||||||
|
testpath = testdir.makepyfile(test_three="""
|
||||||
|
import unittest
|
||||||
|
pytest_plugins = "pytest_unittest" # XXX
|
||||||
|
class MyTestCase(unittest.TestCase):
|
||||||
|
l = []
|
||||||
|
def test_one(self):
|
||||||
|
pass
|
||||||
|
def tearDown(self):
|
||||||
|
self.l.append(None)
|
||||||
|
class Second(unittest.TestCase):
|
||||||
|
def test_check(self):
|
||||||
|
self.assertEquals(MyTestCase.l, [None])
|
||||||
|
""")
|
||||||
|
sorter = testdir.inline_run(testpath)
|
||||||
|
passed, skipped, failed = sorter.countoutcomes()
|
||||||
|
print "COUNTS", passed, skipped, failed
|
||||||
|
assert failed == 0, failed
|
||||||
|
assert passed == 2
|
||||||
|
assert passed + skipped + failed == 2
|
||||||
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
"""
|
||||||
|
for marking and reporting "expected to fail" tests.
|
||||||
|
@py.test.keywords(xfail="needs refactoring")
|
||||||
|
def test_hello():
|
||||||
|
...
|
||||||
|
assert 0
|
||||||
|
"""
|
||||||
|
import py
|
||||||
|
|
||||||
|
class XfailPlugin(object):
|
||||||
|
""" mark and report specially about "expected to fail" tests. """
|
||||||
|
def pytest_report_teststatus(self, event):
|
||||||
|
""" return shortletter and verbose word. """
|
||||||
|
if 'xfail' in event.keywords:
|
||||||
|
if event.failed:
|
||||||
|
return "xfailed", "x", "xfail"
|
||||||
|
else:
|
||||||
|
return "xpassed", "P", "xpass"
|
||||||
|
|
||||||
|
# a hook implemented called by the terminalreporter instance/plugin
|
||||||
|
def pytest_terminal_summary(self, terminalreporter):
|
||||||
|
tr = terminalreporter
|
||||||
|
xfailed = tr.stats.get("xfailed")
|
||||||
|
if xfailed:
|
||||||
|
tr.write_sep("_", "EXPECTED XFAILURES")
|
||||||
|
for event in xfailed:
|
||||||
|
entry = event.longrepr.reprcrash
|
||||||
|
key = entry.path, entry.lineno, entry.message
|
||||||
|
reason = event.longrepr.reprcrash.message
|
||||||
|
modpath = event.colitem.getmodpath(includemodule=True)
|
||||||
|
#tr._tw.line("%s %s:%d: %s" %(modpath, entry.path, entry.lineno, entry.message))
|
||||||
|
tr._tw.line("%s %s:%d: " %(modpath, entry.path, entry.lineno))
|
||||||
|
|
||||||
|
xpassed = terminalreporter.stats.get("xpassed")
|
||||||
|
if xpassed:
|
||||||
|
tr.write_sep("_", "UNEXPECTEDLY PASSING")
|
||||||
|
for event in xpassed:
|
||||||
|
tr._tw.line("%s: xpassed" %(event.colitem,))
|
||||||
|
|
||||||
|
# ===============================================================================
|
||||||
|
#
|
||||||
|
# plugin tests
|
||||||
|
#
|
||||||
|
# ===============================================================================
|
||||||
|
|
||||||
|
def test_generic(plugintester):
|
||||||
|
plugintester.apicheck(XfailPlugin)
|
||||||
|
|
||||||
|
def test_xfail(plugintester, linecomp):
|
||||||
|
testdir = plugintester.testdir()
|
||||||
|
p = testdir.makepyfile(test_one="""
|
||||||
|
import py
|
||||||
|
pytest_plugins="pytest_xfail",
|
||||||
|
@py.test.keywords(xfail=True)
|
||||||
|
def test_this():
|
||||||
|
assert 0
|
||||||
|
""")
|
||||||
|
result = testdir.runpytest(p)
|
||||||
|
extra = result.stdout.fnmatch_lines([
|
||||||
|
"*XFAILURES*",
|
||||||
|
"*test_one.test_this*test_one.py:5*",
|
||||||
|
])
|
||||||
|
assert result.ret == 1
|
||||||
|
|
@ -36,7 +36,7 @@ class PyobjMixin(object):
|
||||||
def _getobj(self):
|
def _getobj(self):
|
||||||
return getattr(self.parent.obj, self.name)
|
return getattr(self.parent.obj, self.name)
|
||||||
|
|
||||||
def getmodpath(self, stopatmodule=True):
|
def getmodpath(self, stopatmodule=True, includemodule=False):
|
||||||
""" return python path relative to the containing module. """
|
""" return python path relative to the containing module. """
|
||||||
chain = self.listchain()
|
chain = self.listchain()
|
||||||
chain.reverse()
|
chain.reverse()
|
||||||
|
|
@ -46,10 +46,12 @@ class PyobjMixin(object):
|
||||||
continue
|
continue
|
||||||
name = node.name
|
name = node.name
|
||||||
if isinstance(node, Module):
|
if isinstance(node, Module):
|
||||||
if stopatmodule:
|
|
||||||
break
|
|
||||||
assert name.endswith(".py")
|
assert name.endswith(".py")
|
||||||
name = name[:-3]
|
name = name[:-3]
|
||||||
|
if stopatmodule:
|
||||||
|
if includemodule:
|
||||||
|
parts.append(name)
|
||||||
|
break
|
||||||
parts.append(name)
|
parts.append(name)
|
||||||
parts.reverse()
|
parts.reverse()
|
||||||
s = ".".join(parts)
|
s = ".".join(parts)
|
||||||
|
|
@ -136,14 +138,18 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
|
||||||
warnoldcollect()
|
warnoldcollect()
|
||||||
return self.join(name)
|
return self.join(name)
|
||||||
|
|
||||||
def makeitem(self, name, obj, usefilters=True):
|
def makeitem(self, name, obj):
|
||||||
if (not usefilters or self.classnamefilter(name)) and \
|
res = self._config.pytestplugins.call_firstresult(
|
||||||
|
"pytest_pymodule_makeitem", modcol=self, name=name, obj=obj)
|
||||||
|
if res:
|
||||||
|
return res
|
||||||
|
if (self.classnamefilter(name)) and \
|
||||||
py.std.inspect.isclass(obj):
|
py.std.inspect.isclass(obj):
|
||||||
res = self._deprecated_join(name)
|
res = self._deprecated_join(name)
|
||||||
if res is not None:
|
if res is not None:
|
||||||
return res
|
return res
|
||||||
return self.Class(name, parent=self)
|
return self.Class(name, parent=self)
|
||||||
elif (not usefilters or self.funcnamefilter(name)) and callable(obj):
|
elif self.funcnamefilter(name) and callable(obj):
|
||||||
res = self._deprecated_join(name)
|
res = self._deprecated_join(name)
|
||||||
if res is not None:
|
if res is not None:
|
||||||
return res
|
return res
|
||||||
|
|
@ -159,14 +165,23 @@ class Module(py.test.collect.File, PyCollectorMixin):
|
||||||
return super(Module, self).collect()
|
return super(Module, self).collect()
|
||||||
|
|
||||||
def _getobj(self):
|
def _getobj(self):
|
||||||
return self._memoizedcall('_obj', self.fspath.pyimport)
|
return self._memoizedcall('_obj', self._importtestmodule)
|
||||||
|
|
||||||
|
def _importtestmodule(self):
|
||||||
|
# we assume we are only called once per module
|
||||||
|
mod = self.fspath.pyimport()
|
||||||
|
#print "imported test module", mod
|
||||||
|
self._config.pytestplugins.consider_module(mod)
|
||||||
|
return mod
|
||||||
|
|
||||||
def setup(self):
|
def setup(self):
|
||||||
if not self._config.option.nomagic:
|
if not self._config.option.nomagic:
|
||||||
#print "*" * 20, "INVOKE assertion", self
|
#print "*" * 20, "INVOKE assertion", self
|
||||||
py.magic.invoke(assertion=1)
|
py.magic.invoke(assertion=1)
|
||||||
if hasattr(self.obj, 'setup_module'):
|
mod = self.obj
|
||||||
self.obj.setup_module(self.obj)
|
self._config.pytestplugins.register(mod)
|
||||||
|
if hasattr(mod, 'setup_module'):
|
||||||
|
self.obj.setup_module(mod)
|
||||||
|
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
if hasattr(self.obj, 'teardown_module'):
|
if hasattr(self.obj, 'teardown_module'):
|
||||||
|
|
@ -174,6 +189,7 @@ class Module(py.test.collect.File, PyCollectorMixin):
|
||||||
if not self._config.option.nomagic:
|
if not self._config.option.nomagic:
|
||||||
#print "*" * 20, "revoke assertion", self
|
#print "*" * 20, "revoke assertion", self
|
||||||
py.magic.revoke(assertion=1)
|
py.magic.revoke(assertion=1)
|
||||||
|
self._config.pytestplugins.unregister(self.obj)
|
||||||
|
|
||||||
class Class(PyCollectorMixin, py.test.collect.Collector):
|
class Class(PyCollectorMixin, py.test.collect.Collector):
|
||||||
|
|
||||||
|
|
@ -305,14 +321,57 @@ class Function(FunctionMixin, py.test.collect.Item):
|
||||||
"""
|
"""
|
||||||
def __init__(self, name, parent=None, config=None, args=(), callobj=_dummy):
|
def __init__(self, name, parent=None, config=None, args=(), callobj=_dummy):
|
||||||
super(Function, self).__init__(name, parent, config=config)
|
super(Function, self).__init__(name, parent, config=config)
|
||||||
|
self._finalizers = []
|
||||||
self._args = args
|
self._args = args
|
||||||
if callobj is not _dummy:
|
if callobj is not _dummy:
|
||||||
self._obj = callobj
|
self._obj = callobj
|
||||||
|
|
||||||
|
def addfinalizer(self, func):
|
||||||
|
self._finalizers.append(func)
|
||||||
|
|
||||||
|
def teardown(self):
|
||||||
|
finalizers = self._finalizers
|
||||||
|
while finalizers:
|
||||||
|
call = finalizers.pop()
|
||||||
|
call()
|
||||||
|
super(Function, self).teardown()
|
||||||
|
|
||||||
|
def readkeywords(self):
|
||||||
|
d = super(Function, self).readkeywords()
|
||||||
|
d.update(self.obj.func_dict)
|
||||||
|
return d
|
||||||
|
|
||||||
def runtest(self):
|
def runtest(self):
|
||||||
""" execute the given test function. """
|
""" execute the given test function. """
|
||||||
if not self._deprecated_testexecution():
|
if not self._deprecated_testexecution():
|
||||||
self.obj(*self._args)
|
kw = self.lookup_allargs()
|
||||||
|
pytest_pyfunc_call = self._config.pytestplugins.getfirst("pytest_pyfunc_call")
|
||||||
|
if pytest_pyfunc_call is not None:
|
||||||
|
if pytest_pyfunc_call(pyfuncitem=self, args=self._args, kwargs=kw):
|
||||||
|
return
|
||||||
|
self.obj(*self._args, **kw)
|
||||||
|
|
||||||
|
def lookup_allargs(self):
|
||||||
|
kwargs = {}
|
||||||
|
if not self._args:
|
||||||
|
# standard Python Test function/method case
|
||||||
|
funcobj = self.obj
|
||||||
|
startindex = getattr(funcobj, 'im_self', None) and 1 or 0
|
||||||
|
for argname in py.std.inspect.getargs(self.obj.func_code)[0][startindex:]:
|
||||||
|
kwargs[argname] = self.lookup_onearg(argname)
|
||||||
|
else:
|
||||||
|
pass # XXX lookup of arguments for yielded/generated tests as well
|
||||||
|
return kwargs
|
||||||
|
|
||||||
|
def lookup_onearg(self, argname):
|
||||||
|
value = self._config.pytestplugins.call_firstresult(
|
||||||
|
"pytest_pyfuncarg_" + argname, pyfuncitem=self)
|
||||||
|
if value is not None:
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
metainfo = self.repr_metainfo()
|
||||||
|
#self._config.bus.notify("pyfuncarg_lookuperror", argname)
|
||||||
|
raise LookupError("funcargument %r not found for: %s" %(argname,metainfo.verboseline()))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
try:
|
try:
|
||||||
|
|
@ -326,50 +385,6 @@ class Function(FunctionMixin, py.test.collect.Item):
|
||||||
def __ne__(self, other):
|
def __ne__(self, other):
|
||||||
return not self == other
|
return not self == other
|
||||||
|
|
||||||
class DoctestFile(py.test.collect.File):
|
|
||||||
|
|
||||||
def collect(self):
|
|
||||||
return [DoctestFileContent(self.fspath.basename, parent=self)]
|
|
||||||
|
|
||||||
from py.__.code.excinfo import Repr, ReprFileLocation
|
|
||||||
|
|
||||||
class ReprFailDoctest(Repr):
|
|
||||||
def __init__(self, reprlocation, lines):
|
|
||||||
self.reprlocation = reprlocation
|
|
||||||
self.lines = lines
|
|
||||||
def toterminal(self, tw):
|
|
||||||
for line in self.lines:
|
|
||||||
tw.line(line)
|
|
||||||
self.reprlocation.toterminal(tw)
|
|
||||||
|
|
||||||
class DoctestFileContent(py.test.collect.Item):
|
|
||||||
def repr_failure(self, excinfo, outerr):
|
|
||||||
if excinfo.errisinstance(py.compat.doctest.DocTestFailure):
|
|
||||||
doctestfailure = excinfo.value
|
|
||||||
example = doctestfailure.example
|
|
||||||
test = doctestfailure.test
|
|
||||||
filename = test.filename
|
|
||||||
lineno = example.lineno + 1
|
|
||||||
message = excinfo.type.__name__
|
|
||||||
reprlocation = ReprFileLocation(filename, lineno, message)
|
|
||||||
checker = py.compat.doctest.OutputChecker()
|
|
||||||
REPORT_UDIFF = py.compat.doctest.REPORT_UDIFF
|
|
||||||
filelines = py.path.local(filename).readlines(cr=0)
|
|
||||||
i = max(0, lineno - 10)
|
|
||||||
lines = []
|
|
||||||
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")
|
|
||||||
return ReprFailDoctest(reprlocation, lines)
|
|
||||||
#elif excinfo.errisinstance(py.compat.doctest.UnexpectedException):
|
|
||||||
else:
|
|
||||||
return super(DoctestFileContent, self).repr_failure(excinfo, outerr)
|
|
||||||
|
|
||||||
def runtest(self):
|
|
||||||
if not self._deprecated_testexecution():
|
|
||||||
failed, tot = py.compat.doctest.testfile(
|
|
||||||
str(self.fspath), module_relative=False,
|
|
||||||
raise_on_error=True, verbose=0)
|
|
||||||
|
|
||||||
|
# DEPRECATED
|
||||||
|
#from py.__.test.plugin.pytest_doctest import DoctestFile
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
"""
|
||||||
|
handling py.test plugins.
|
||||||
|
"""
|
||||||
|
import py
|
||||||
|
|
||||||
|
class PytestPlugins(object):
|
||||||
|
def __init__(self, pyplugins=None):
|
||||||
|
if pyplugins is None:
|
||||||
|
pyplugins = py._com.PyPlugins()
|
||||||
|
self.pyplugins = pyplugins
|
||||||
|
self.MultiCall = self.pyplugins.MultiCall
|
||||||
|
self._plugins = {}
|
||||||
|
|
||||||
|
def register(self, plugin):
|
||||||
|
self.pyplugins.register(plugin)
|
||||||
|
def unregister(self, plugin):
|
||||||
|
self.pyplugins.unregister(plugin)
|
||||||
|
def isregistered(self, plugin):
|
||||||
|
return self.pyplugins.isregistered(plugin)
|
||||||
|
|
||||||
|
def getplugins(self):
|
||||||
|
return self.pyplugins.getplugins()
|
||||||
|
|
||||||
|
# API for bootstrapping
|
||||||
|
#
|
||||||
|
def getplugin(self, importname):
|
||||||
|
impname, clsname = canonical_names(importname)
|
||||||
|
return self._plugins[impname]
|
||||||
|
|
||||||
|
def consider_env(self):
|
||||||
|
for spec in self.pyplugins._envlist("PYTEST_PLUGINS"):
|
||||||
|
self.import_plugin(spec)
|
||||||
|
|
||||||
|
def consider_conftest(self, conftestmodule):
|
||||||
|
cls = getattr(conftestmodule, 'ConftestPlugin', None)
|
||||||
|
if cls is not None and cls not in self._plugins:
|
||||||
|
self._plugins[cls] = True
|
||||||
|
self.register(cls())
|
||||||
|
self.consider_module(conftestmodule)
|
||||||
|
|
||||||
|
def consider_module(self, mod):
|
||||||
|
attr = getattr(mod, "pytest_plugins", ())
|
||||||
|
if attr:
|
||||||
|
if not isinstance(attr, (list, tuple)):
|
||||||
|
attr = (attr,)
|
||||||
|
for spec in attr:
|
||||||
|
self.import_plugin(spec)
|
||||||
|
|
||||||
|
def import_plugin(self, spec):
|
||||||
|
assert isinstance(spec, str)
|
||||||
|
modname, clsname = canonical_names(spec)
|
||||||
|
if modname in self._plugins:
|
||||||
|
return
|
||||||
|
mod = importplugin(modname)
|
||||||
|
plugin = registerplugin(self.pyplugins.register, mod, clsname)
|
||||||
|
self._plugins[modname] = plugin
|
||||||
|
self.consider_module(mod)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# API for interacting with registered and instantiated plugin objects
|
||||||
|
#
|
||||||
|
#
|
||||||
|
def getfirst(self, attrname):
|
||||||
|
for x in self.pyplugins.listattr(attrname):
|
||||||
|
return x
|
||||||
|
|
||||||
|
def listattr(self, attrname):
|
||||||
|
return self.pyplugins.listattr(attrname)
|
||||||
|
|
||||||
|
def call_firstresult(self, *args, **kwargs):
|
||||||
|
return self.pyplugins.call_firstresult(*args, **kwargs)
|
||||||
|
|
||||||
|
def call_each(self, *args, **kwargs):
|
||||||
|
#print "plugins.call_each", args[0], args[1:], kwargs
|
||||||
|
return self.pyplugins.call_each(*args, **kwargs)
|
||||||
|
|
||||||
|
def notify(self, eventname, *args, **kwargs):
|
||||||
|
return self.pyplugins.notify(eventname, *args, **kwargs)
|
||||||
|
|
||||||
|
def do_addoption(self, parser):
|
||||||
|
self.pyplugins.call_each('pytest_addoption', parser=parser)
|
||||||
|
|
||||||
|
def pyevent_plugin_registered(self, plugin):
|
||||||
|
if hasattr(self, '_config'):
|
||||||
|
self.pyplugins.call_plugin(plugin, "pytest_addoption", parser=self._config._parser)
|
||||||
|
self.pyplugins.call_plugin(plugin, "pytest_configure", config=self._config)
|
||||||
|
|
||||||
|
def configure(self, config):
|
||||||
|
assert not hasattr(self, '_config')
|
||||||
|
config.bus.register(self)
|
||||||
|
self._config = config
|
||||||
|
self.pyplugins.call_each("pytest_configure", config=self._config)
|
||||||
|
|
||||||
|
def unconfigure(self, config):
|
||||||
|
config = self._config
|
||||||
|
del self._config
|
||||||
|
self.pyplugins.call_each("pytest_unconfigure", config=config)
|
||||||
|
|
||||||
|
#
|
||||||
|
# XXX old code to automatically load classes
|
||||||
|
#
|
||||||
|
def canonical_names(importspec):
|
||||||
|
importspec = importspec.lower()
|
||||||
|
modprefix = "pytest_"
|
||||||
|
if not importspec.startswith(modprefix):
|
||||||
|
importspec = modprefix + importspec
|
||||||
|
clsname = importspec[len(modprefix):].capitalize() + "Plugin"
|
||||||
|
return importspec, clsname
|
||||||
|
|
||||||
|
def registerplugin(registerfunc, mod, clsname):
|
||||||
|
pluginclass = getattr(mod, clsname)
|
||||||
|
plugin = pluginclass()
|
||||||
|
registerfunc(plugin)
|
||||||
|
return plugin
|
||||||
|
|
||||||
|
def importplugin(importspec):
|
||||||
|
try:
|
||||||
|
return __import__(importspec)
|
||||||
|
except ImportError, e:
|
||||||
|
try:
|
||||||
|
return __import__("py.__.test.plugin.%s" %(importspec), None, None, '__doc__')
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError(importspec)
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
from py.__.test import event
|
|
||||||
from py.__.test.collect import getrelpath
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
class BaseReporter(object):
|
|
||||||
def __init__(self, bus=None):
|
|
||||||
self._reset()
|
|
||||||
self._bus = bus
|
|
||||||
if bus:
|
|
||||||
self._bus.subscribe(self.processevent)
|
|
||||||
|
|
||||||
def _reset(self):
|
|
||||||
self._passed = []
|
|
||||||
self._skipped = []
|
|
||||||
self._failed = []
|
|
||||||
self._deselected = []
|
|
||||||
|
|
||||||
def deactivate(self):
|
|
||||||
if self._bus:
|
|
||||||
self._bus.unsubscribe(self.processevent)
|
|
||||||
|
|
||||||
def processevent(self, ev):
|
|
||||||
evname = ev.__class__.__name__
|
|
||||||
repmethod = getattr(self, "rep_%s" % evname, None)
|
|
||||||
if repmethod is None:
|
|
||||||
self.rep(ev)
|
|
||||||
else:
|
|
||||||
repmethod(ev)
|
|
||||||
|
|
||||||
def rep(self, ev):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def rep_ItemTestReport(self, ev):
|
|
||||||
if ev.skipped:
|
|
||||||
self._skipped.append(ev)
|
|
||||||
elif ev.failed:
|
|
||||||
self._failed.append(ev)
|
|
||||||
elif ev.passed:
|
|
||||||
self._passed.append(ev)
|
|
||||||
|
|
||||||
def rep_CollectionReport(self, ev):
|
|
||||||
if ev.skipped:
|
|
||||||
self._skipped.append(ev)
|
|
||||||
elif ev.failed:
|
|
||||||
self._failed.append(ev)
|
|
||||||
else:
|
|
||||||
pass # don't record passed collections
|
|
||||||
|
|
||||||
def rep_TestrunStart(self, ev):
|
|
||||||
self._reset()
|
|
||||||
|
|
||||||
def rep_Deselected(self, ev):
|
|
||||||
self._deselected.extend(ev.items)
|
|
||||||
|
|
||||||
def _folded_skips(self):
|
|
||||||
d = {}
|
|
||||||
for event in self._skipped:
|
|
||||||
longrepr = event.outcome.longrepr
|
|
||||||
key = longrepr.path, longrepr.lineno, longrepr.message
|
|
||||||
d.setdefault(key, []).append(event)
|
|
||||||
l = []
|
|
||||||
for key, events in d.iteritems():
|
|
||||||
l.append((len(events),) + key)
|
|
||||||
return l
|
|
||||||
|
|
||||||
def repr_pythonversion(v=None):
|
|
||||||
if v is None:
|
|
||||||
v = sys.version_info
|
|
||||||
try:
|
|
||||||
return "%s.%s.%s-%s-%s" % v
|
|
||||||
except (TypeError, ValueError):
|
|
||||||
return str(v)
|
|
||||||
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
|
|
||||||
""" --collectonly session, not to spread logic all over the place
|
|
||||||
"""
|
|
||||||
import py
|
|
||||||
from py.__.test.report.base import BaseReporter
|
|
||||||
from py.__.test.outcome import Skipped as Skipped2
|
|
||||||
from py.__.test.outcome import Skipped
|
|
||||||
|
|
||||||
class CollectonlyReporter(BaseReporter):
|
|
||||||
INDENT = " "
|
|
||||||
|
|
||||||
def __init__(self, config, out=None, bus=None):
|
|
||||||
super(CollectonlyReporter, self).__init__(bus=bus)
|
|
||||||
self.config = config
|
|
||||||
if out is None:
|
|
||||||
out = py.std.sys.stdout
|
|
||||||
self.out = py.io.TerminalWriter(out)
|
|
||||||
self.indent = ""
|
|
||||||
self._failed = []
|
|
||||||
|
|
||||||
def outindent(self, line):
|
|
||||||
self.out.line(self.indent + str(line))
|
|
||||||
|
|
||||||
def rep_CollectionStart(self, ev):
|
|
||||||
self.outindent(ev.collector)
|
|
||||||
self.indent += self.INDENT
|
|
||||||
|
|
||||||
def rep_ItemStart(self, event):
|
|
||||||
self.outindent(event.item)
|
|
||||||
|
|
||||||
def rep_CollectionReport(self, ev):
|
|
||||||
super(CollectonlyReporter, self).rep_CollectionReport(ev)
|
|
||||||
if ev.failed:
|
|
||||||
self.outindent("!!! %s !!!" % ev.outcome.longrepr.reprcrash.message)
|
|
||||||
elif ev.skipped:
|
|
||||||
self.outindent("!!! %s !!!" % ev.outcome.longrepr.message)
|
|
||||||
self.indent = self.indent[:-len(self.INDENT)]
|
|
||||||
|
|
||||||
def rep_TestrunFinish(self, session):
|
|
||||||
for ev in self._failed:
|
|
||||||
ev.toterminal(self.out)
|
|
||||||
|
|
||||||
Reporter = CollectonlyReporter
|
|
||||||
|
|
@ -1,219 +0,0 @@
|
||||||
import py
|
|
||||||
import sys
|
|
||||||
from py.__.test import event
|
|
||||||
from py.__.test.report.base import BaseReporter
|
|
||||||
from py.__.test.report.base import getrelpath, repr_pythonversion
|
|
||||||
|
|
||||||
class TerminalReporter(BaseReporter):
|
|
||||||
def __init__(self, config, file=None, bus=None):
|
|
||||||
super(TerminalReporter, self).__init__(bus=bus)
|
|
||||||
self.config = config
|
|
||||||
self.curdir = py.path.local()
|
|
||||||
if file is None:
|
|
||||||
file = py.std.sys.stdout
|
|
||||||
self._tw = py.io.TerminalWriter(file)
|
|
||||||
|
|
||||||
def _reset(self):
|
|
||||||
self.currentfspath = None
|
|
||||||
super(TerminalReporter, self)._reset()
|
|
||||||
|
|
||||||
def write_fspath_result(self, fspath, res):
|
|
||||||
if fspath != self.currentfspath:
|
|
||||||
self._tw.line()
|
|
||||||
relpath = getrelpath(self.curdir, fspath)
|
|
||||||
self._tw.write(relpath + " ")
|
|
||||||
self.currentfspath = fspath
|
|
||||||
self._tw.write(res)
|
|
||||||
|
|
||||||
def write_ensure_prefix(self, prefix, extra=""):
|
|
||||||
if self.currentfspath != prefix:
|
|
||||||
self._tw.line()
|
|
||||||
self.currentfspath = prefix
|
|
||||||
self._tw.write(prefix)
|
|
||||||
if extra:
|
|
||||||
self._tw.write(extra)
|
|
||||||
self.currentfspath = -2
|
|
||||||
|
|
||||||
def ensure_newline(self):
|
|
||||||
if self.currentfspath:
|
|
||||||
self._tw.line()
|
|
||||||
self.currentfspath = None
|
|
||||||
|
|
||||||
def write_line(self, line, **markup):
|
|
||||||
line = str(line)
|
|
||||||
self.ensure_newline()
|
|
||||||
self._tw.line(line, **markup)
|
|
||||||
|
|
||||||
def write_sep(self, sep, title=None, **markup):
|
|
||||||
self.ensure_newline()
|
|
||||||
self._tw.sep(sep, title, **markup)
|
|
||||||
|
|
||||||
def getoutcomeletter(self, item):
|
|
||||||
return item.outcome.shortrepr
|
|
||||||
|
|
||||||
def getoutcomeword(self, item):
|
|
||||||
if item.passed: return self._tw.markup("PASS", green=True)
|
|
||||||
elif item.failed: return self._tw.markup("FAIL", red=True)
|
|
||||||
elif item.skipped: return "SKIP"
|
|
||||||
else: return self._tw.markup("???", red=True)
|
|
||||||
|
|
||||||
def getcollectoutcome(self, item):
|
|
||||||
if item.skipped:
|
|
||||||
return str(item.outcome.longrepr.message)
|
|
||||||
else:
|
|
||||||
return str(item.outcome.longrepr.reprcrash.message)
|
|
||||||
|
|
||||||
def rep_InternalException(self, ev):
|
|
||||||
for line in str(ev.repr).split("\n"):
|
|
||||||
self.write_line("InternalException: " + line)
|
|
||||||
|
|
||||||
def rep_HostGatewayReady(self, ev):
|
|
||||||
if self.config.option.verbose:
|
|
||||||
self.write_line("HostGatewayReady: %s" %(ev.host,))
|
|
||||||
|
|
||||||
def rep_HostUp(self, ev):
|
|
||||||
d = ev.platinfo.copy()
|
|
||||||
d['hostid'] = ev.host.hostid
|
|
||||||
d['version'] = repr_pythonversion(d['sys.version_info'])
|
|
||||||
self.write_line("HOSTUP: %(hostid)s %(sys.platform)s "
|
|
||||||
"%(sys.executable)s - Python %(version)s" %
|
|
||||||
d)
|
|
||||||
|
|
||||||
def rep_HostDown(self, ev):
|
|
||||||
host = ev.host
|
|
||||||
error = ev.error
|
|
||||||
if error:
|
|
||||||
self.write_line("HostDown %s: %s" %(host.hostid, error))
|
|
||||||
|
|
||||||
def rep_ItemStart(self, ev):
|
|
||||||
if self.config.option.verbose:
|
|
||||||
info = ev.item.repr_metainfo()
|
|
||||||
line = info.verboseline(basedir=self.curdir) + " "
|
|
||||||
extra = ""
|
|
||||||
if ev.host:
|
|
||||||
extra = "-> " + ev.host.hostid
|
|
||||||
self.write_ensure_prefix(line, extra)
|
|
||||||
else:
|
|
||||||
# ensure that the path is printed before the 1st test of
|
|
||||||
# a module starts running
|
|
||||||
fspath = ev.item.fspath
|
|
||||||
self.write_fspath_result(fspath, "")
|
|
||||||
|
|
||||||
def rep_RescheduleItems(self, ev):
|
|
||||||
if self.config.option.debug:
|
|
||||||
self.write_sep("!", "RESCHEDULING %s " %(ev.items,))
|
|
||||||
|
|
||||||
def rep_ItemTestReport(self, ev):
|
|
||||||
super(TerminalReporter, self).rep_ItemTestReport(ev)
|
|
||||||
fspath = ev.colitem.fspath
|
|
||||||
if not self.config.option.verbose:
|
|
||||||
self.write_fspath_result(fspath, self.getoutcomeletter(ev))
|
|
||||||
else:
|
|
||||||
info = ev.colitem.repr_metainfo()
|
|
||||||
line = info.verboseline(basedir=self.curdir) + " "
|
|
||||||
word = self.getoutcomeword(ev)
|
|
||||||
self.write_ensure_prefix(line, word)
|
|
||||||
|
|
||||||
def rep_CollectionReport(self, ev):
|
|
||||||
super(TerminalReporter, self).rep_CollectionReport(ev)
|
|
||||||
fspath = ev.colitem.fspath
|
|
||||||
if ev.failed or ev.skipped:
|
|
||||||
msg = self.getcollectoutcome(ev)
|
|
||||||
self.write_fspath_result(fspath, "- " + msg)
|
|
||||||
|
|
||||||
def rep_TestrunStart(self, ev):
|
|
||||||
super(TerminalReporter, self).rep_TestrunStart(ev)
|
|
||||||
self.write_sep("=", "test session starts", bold=True)
|
|
||||||
self._sessionstarttime = py.std.time.time()
|
|
||||||
#self.out_hostinfo()
|
|
||||||
|
|
||||||
def rep_TestrunFinish(self, ev):
|
|
||||||
self._tw.line("")
|
|
||||||
if ev.exitstatus in (0, 1, 2):
|
|
||||||
self.summary_failures()
|
|
||||||
self.summary_skips()
|
|
||||||
if ev.excrepr is not None:
|
|
||||||
self.summary_final_exc(ev.excrepr)
|
|
||||||
if ev.exitstatus == 2:
|
|
||||||
self.write_sep("!", "KEYBOARD INTERRUPT")
|
|
||||||
self.summary_deselected()
|
|
||||||
self.summary_stats()
|
|
||||||
|
|
||||||
def rep_LooponfailingInfo(self, ev):
|
|
||||||
if ev.failreports:
|
|
||||||
self.write_sep("#", "LOOPONFAILING", red=True)
|
|
||||||
for report in ev.failreports:
|
|
||||||
try:
|
|
||||||
loc = report.outcome.longrepr.reprcrash
|
|
||||||
except AttributeError:
|
|
||||||
loc = str(report.outcome.longrepr)[:50]
|
|
||||||
self.write_line(loc, red=True)
|
|
||||||
self.write_sep("#", "waiting for changes")
|
|
||||||
for rootdir in ev.rootdirs:
|
|
||||||
self.write_line("### Watching: %s" %(rootdir,), bold=True)
|
|
||||||
|
|
||||||
if 0:
|
|
||||||
print "#" * 60
|
|
||||||
print "# looponfailing: mode: %d failures args" % len(failures)
|
|
||||||
for ev in failurereports:
|
|
||||||
name = "/".join(ev.colitem.listnames()) # XXX
|
|
||||||
print "Failure at: %r" % (name,)
|
|
||||||
print "# watching py files below %s" % rootdir
|
|
||||||
print "# ", "^" * len(str(rootdir))
|
|
||||||
failures = [ev.colitem for ev in failurereports]
|
|
||||||
if not failures:
|
|
||||||
failures = colitems
|
|
||||||
|
|
||||||
#
|
|
||||||
# summaries for TestrunFinish
|
|
||||||
#
|
|
||||||
|
|
||||||
def summary_failures(self):
|
|
||||||
if self._failed and self.config.option.tbstyle != "no":
|
|
||||||
self.write_sep("=", "FAILURES")
|
|
||||||
for ev in self._failed:
|
|
||||||
self.write_sep("_")
|
|
||||||
ev.toterminal(self._tw)
|
|
||||||
|
|
||||||
def summary_stats(self):
|
|
||||||
session_duration = py.std.time.time() - self._sessionstarttime
|
|
||||||
numfailed = len(self._failed)
|
|
||||||
numskipped = len(self._skipped)
|
|
||||||
numpassed = len(self._passed)
|
|
||||||
sum = numfailed + numpassed
|
|
||||||
self.write_sep("=", "%d/%d passed + %d skips in %.2f seconds" %
|
|
||||||
(numpassed, sum, numskipped, session_duration), bold=True)
|
|
||||||
if numfailed == 0:
|
|
||||||
self.write_sep("=", "failures: no failures :)", green=True)
|
|
||||||
else:
|
|
||||||
self.write_sep("=", "failures: %d" %(numfailed), red=True)
|
|
||||||
|
|
||||||
def summary_deselected(self):
|
|
||||||
if not self._deselected:
|
|
||||||
return
|
|
||||||
self.write_sep("=", "%d tests deselected by %r" %(
|
|
||||||
len(self._deselected), self.config.option.keyword), bold=True)
|
|
||||||
|
|
||||||
|
|
||||||
def summary_skips(self):
|
|
||||||
if not self._failed or self.config.option.showskipsummary:
|
|
||||||
folded_skips = self._folded_skips()
|
|
||||||
if folded_skips:
|
|
||||||
self.write_sep("_", "skipped test summary")
|
|
||||||
for num, fspath, lineno, reason in folded_skips:
|
|
||||||
self._tw.line("%s:%d: [%d] %s" %(fspath, lineno, num, reason))
|
|
||||||
|
|
||||||
def summary_final_exc(self, excrepr):
|
|
||||||
self.write_sep("!")
|
|
||||||
if self.config.option.verbose:
|
|
||||||
excrepr.toterminal(self._tw)
|
|
||||||
else:
|
|
||||||
excrepr.reprcrash.toterminal(self._tw)
|
|
||||||
|
|
||||||
def out_hostinfo(self):
|
|
||||||
self._tw.line("host 0: %s %s - Python %s" %
|
|
||||||
(py.std.sys.platform,
|
|
||||||
py.std.sys.executable,
|
|
||||||
repr_pythonversion()))
|
|
||||||
|
|
||||||
Reporter = TerminalReporter
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
import py
|
|
||||||
from py.__.test.report.base import BaseReporter
|
|
||||||
from py.__.test.event import EventBus
|
|
||||||
from py.__.test import event
|
|
||||||
from py.__.test.runner import OutcomeRepr
|
|
||||||
from py.__.test.report.base import getrelpath, repr_pythonversion
|
|
||||||
import sys
|
|
||||||
|
|
||||||
class TestBaseReporter:
|
|
||||||
def test_activate(self):
|
|
||||||
bus = EventBus()
|
|
||||||
rep = BaseReporter(bus=bus)
|
|
||||||
assert bus._subscribers
|
|
||||||
assert rep.processevent in bus._subscribers
|
|
||||||
rep.deactivate()
|
|
||||||
assert not bus._subscribers
|
|
||||||
|
|
||||||
def test_dispatch_to_matching_method(self):
|
|
||||||
l = []
|
|
||||||
class MyReporter(BaseReporter):
|
|
||||||
def rep_TestrunStart(self, ev):
|
|
||||||
l.append(ev)
|
|
||||||
rep = MyReporter()
|
|
||||||
ev = event.TestrunStart()
|
|
||||||
rep.processevent(ev)
|
|
||||||
assert len(l) == 1
|
|
||||||
assert l[0] is ev
|
|
||||||
|
|
||||||
def test_dispatch_to_default(self):
|
|
||||||
l = []
|
|
||||||
class MyReporter(BaseReporter):
|
|
||||||
def rep(self, ev):
|
|
||||||
l.append(ev)
|
|
||||||
rep = MyReporter()
|
|
||||||
ev = event.NOP()
|
|
||||||
rep.processevent(ev)
|
|
||||||
assert len(l) == 1
|
|
||||||
assert l[0] is ev
|
|
||||||
|
|
||||||
def test_TestItemReport_one(self):
|
|
||||||
for outcome in 'passed skipped failed'.split():
|
|
||||||
rep = BaseReporter()
|
|
||||||
ev = event.ItemTestReport(None, **{outcome:True})
|
|
||||||
rep.processevent(ev)
|
|
||||||
assert getattr(rep, '_' + outcome) == [ev]
|
|
||||||
|
|
||||||
def test_CollectionReport(self):
|
|
||||||
for outcome in 'skipped failed'.split():
|
|
||||||
rep = BaseReporter()
|
|
||||||
ev = event.CollectionReport(None, None, **{outcome:True})
|
|
||||||
rep.processevent(ev)
|
|
||||||
assert getattr(rep, '_' + outcome) == [ev]
|
|
||||||
|
|
||||||
def test_skip_reasons(self):
|
|
||||||
rep = BaseReporter()
|
|
||||||
class longrepr:
|
|
||||||
path = 'xyz'
|
|
||||||
lineno = 3
|
|
||||||
message = "justso"
|
|
||||||
out1 = OutcomeRepr(None, None, longrepr)
|
|
||||||
out2 = OutcomeRepr(None, None, longrepr)
|
|
||||||
ev1 = event.CollectionReport(None, None, skipped=out1)
|
|
||||||
ev2 = event.ItemTestReport(None, skipped=out2)
|
|
||||||
rep.processevent(ev1)
|
|
||||||
rep.processevent(ev2)
|
|
||||||
assert len(rep._skipped) == 2
|
|
||||||
l = rep._folded_skips()
|
|
||||||
assert len(l) == 1
|
|
||||||
num, fspath, lineno, reason = l[0]
|
|
||||||
assert num == 2
|
|
||||||
assert fspath == longrepr.path
|
|
||||||
assert lineno == longrepr.lineno
|
|
||||||
assert reason == longrepr.message
|
|
||||||
|
|
||||||
def test_repr_python_version():
|
|
||||||
py.magic.patch(sys, 'version_info', (2, 5, 1, 'final', 0))
|
|
||||||
try:
|
|
||||||
assert repr_pythonversion() == "2.5.1-final-0"
|
|
||||||
py.std.sys.version_info = x = (2,3)
|
|
||||||
assert repr_pythonversion() == str(x)
|
|
||||||
finally:
|
|
||||||
py.magic.revert(sys, 'version_info')
|
|
||||||
|
|
||||||
def test_getrelpath():
|
|
||||||
curdir = py.path.local()
|
|
||||||
sep = curdir.sep
|
|
||||||
s = getrelpath(curdir, curdir.join("hello", "world"))
|
|
||||||
assert s == "hello" + sep + "world"
|
|
||||||
|
|
||||||
s = getrelpath(curdir, curdir.dirpath().join("sister"))
|
|
||||||
assert s == ".." + sep + "sister"
|
|
||||||
assert getrelpath(curdir, curdir.dirpath()) == ".."
|
|
||||||
|
|
||||||
assert getrelpath(curdir, "hello") == "hello"
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
import py
|
|
||||||
from py.__.test.report.collectonly import CollectonlyReporter
|
|
||||||
from py.__.test import event
|
|
||||||
from py.__.test.testing.suptest import InlineCollection, popvalue
|
|
||||||
from py.__.test.testing.suptest import assert_stringio_contains_lines
|
|
||||||
|
|
||||||
class TestCollectonly(InlineCollection):
|
|
||||||
def test_collectonly_basic(self):
|
|
||||||
modcol = self.getmodulecol(configargs=['--collectonly'], source="""
|
|
||||||
def test_func():
|
|
||||||
pass
|
|
||||||
""")
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
rep = CollectonlyReporter(modcol._config, out=stringio)
|
|
||||||
indent = rep.indent
|
|
||||||
rep.processevent(event.CollectionStart(modcol))
|
|
||||||
s = popvalue(stringio)
|
|
||||||
assert s == "<Module 'test_TestCollectonly_test_collectonly_basic.py'>"
|
|
||||||
|
|
||||||
item = modcol.join("test_func")
|
|
||||||
rep.processevent(event.ItemStart(item))
|
|
||||||
s = popvalue(stringio)
|
|
||||||
assert s.find("Function 'test_func'") != -1
|
|
||||||
rep.processevent(event.CollectionReport(modcol, [], passed=""))
|
|
||||||
assert rep.indent == indent
|
|
||||||
|
|
||||||
def test_collectonly_skipped_module(self):
|
|
||||||
modcol = self.getmodulecol(configargs=['--collectonly'], source="""
|
|
||||||
import py
|
|
||||||
py.test.skip("nomod")
|
|
||||||
""", withsession=True)
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
rep = CollectonlyReporter(modcol._config, bus=self.session.bus, out=stringio)
|
|
||||||
cols = list(self.session.genitems([modcol]))
|
|
||||||
assert len(cols) == 0
|
|
||||||
assert_stringio_contains_lines(stringio, """
|
|
||||||
<Module 'test_TestCollectonly_test_collectonly_skipped_module.py'>
|
|
||||||
!!! Skipped: 'nomod' !!!
|
|
||||||
""")
|
|
||||||
|
|
||||||
def test_collectonly_failed_module(self):
|
|
||||||
modcol = self.getmodulecol(configargs=['--collectonly'], source="""
|
|
||||||
raise ValueError(0)
|
|
||||||
""", withsession=True)
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
rep = CollectonlyReporter(modcol._config, bus=self.session.bus, out=stringio)
|
|
||||||
cols = list(self.session.genitems([modcol]))
|
|
||||||
assert len(cols) == 0
|
|
||||||
assert_stringio_contains_lines(stringio, """
|
|
||||||
<Module 'test_TestCollectonly_test_collectonly_failed_module.py'>
|
|
||||||
!!! ValueError: 0 !!!
|
|
||||||
""")
|
|
||||||
|
|
||||||
|
|
@ -1,247 +0,0 @@
|
||||||
import py
|
|
||||||
import sys
|
|
||||||
from py.__.test.report.terminal import TerminalReporter
|
|
||||||
from py.__.test import event
|
|
||||||
#from py.__.test.testing import suptest
|
|
||||||
from py.__.test.runner import basic_run_report
|
|
||||||
from py.__.test.testing.suptest import InlineCollection, popvalue
|
|
||||||
from py.__.test.testing.suptest import assert_stringio_contains_lines
|
|
||||||
from py.__.test.dsession.hostmanage import Host, makehostup
|
|
||||||
from py.__.test.report.base import repr_pythonversion
|
|
||||||
|
|
||||||
class TestTerminal(InlineCollection):
|
|
||||||
def test_session_reporter_subscription(self):
|
|
||||||
config = py.test.config._reparse(['xxx'])
|
|
||||||
session = config.initsession()
|
|
||||||
session.sessionstarts()
|
|
||||||
rep = session.reporter
|
|
||||||
assert isinstance(rep, TerminalReporter)
|
|
||||||
assert rep.processevent in session.bus._subscribers
|
|
||||||
session.sessionfinishes()
|
|
||||||
#assert rep.processevent not in session.bus._subscribers
|
|
||||||
|
|
||||||
def test_hostup(self):
|
|
||||||
item = self.getitem("def test_func(): pass")
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
rep = TerminalReporter(item._config, file=stringio)
|
|
||||||
rep.processevent(event.TestrunStart())
|
|
||||||
host = Host("localhost")
|
|
||||||
rep.processevent(makehostup(host))
|
|
||||||
s = popvalue(stringio)
|
|
||||||
expect = "%s %s %s - Python %s" %(host.hostid, sys.platform,
|
|
||||||
sys.executable, repr_pythonversion(sys.version_info))
|
|
||||||
assert s.find(expect) != -1
|
|
||||||
|
|
||||||
def test_pass_skip_fail(self):
|
|
||||||
modcol = self.getmodulecol("""
|
|
||||||
import py
|
|
||||||
def test_ok():
|
|
||||||
pass
|
|
||||||
def test_skip():
|
|
||||||
py.test.skip("xx")
|
|
||||||
def test_func():
|
|
||||||
assert 0
|
|
||||||
""", withsession=True)
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
rep = TerminalReporter(modcol._config, file=stringio)
|
|
||||||
rep.processevent(event.TestrunStart())
|
|
||||||
for item in self.session.genitems([modcol]):
|
|
||||||
ev = basic_run_report(item)
|
|
||||||
rep.processevent(ev)
|
|
||||||
s = popvalue(stringio)
|
|
||||||
assert s.find("test_pass_skip_fail.py .sF") != -1
|
|
||||||
rep.processevent(event.TestrunFinish())
|
|
||||||
assert_stringio_contains_lines(stringio, [
|
|
||||||
" def test_func():",
|
|
||||||
"> assert 0",
|
|
||||||
"E assert 0",
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_pass_skip_fail_verbose(self):
|
|
||||||
modcol = self.getmodulecol("""
|
|
||||||
import py
|
|
||||||
def test_ok():
|
|
||||||
pass
|
|
||||||
def test_skip():
|
|
||||||
py.test.skip("xx")
|
|
||||||
def test_func():
|
|
||||||
assert 0
|
|
||||||
""", configargs=("-v",), withsession=True)
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
rep = TerminalReporter(modcol._config, file=stringio)
|
|
||||||
rep.processevent(event.TestrunStart())
|
|
||||||
items = modcol.collect()
|
|
||||||
for item in items:
|
|
||||||
rep.processevent(event.ItemStart(item))
|
|
||||||
s = stringio.getvalue().strip()
|
|
||||||
assert s.endswith(item.name)
|
|
||||||
ev = basic_run_report(item)
|
|
||||||
rep.processevent(ev)
|
|
||||||
|
|
||||||
assert_stringio_contains_lines(stringio, [
|
|
||||||
"*test_pass_skip_fail_verbose.py:2: *test_ok*PASS",
|
|
||||||
"*test_pass_skip_fail_verbose.py:4: *test_skip*SKIP",
|
|
||||||
"*test_pass_skip_fail_verbose.py:6: *test_func*FAIL",
|
|
||||||
])
|
|
||||||
rep.processevent(event.TestrunFinish())
|
|
||||||
assert_stringio_contains_lines(stringio, [
|
|
||||||
" def test_func():",
|
|
||||||
"> assert 0",
|
|
||||||
"E assert 0",
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_collect_fail(self):
|
|
||||||
modcol = self.getmodulecol("""
|
|
||||||
import xyz
|
|
||||||
""", withsession=True)
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
rep = TerminalReporter(modcol._config, bus=self.session.bus, file=stringio)
|
|
||||||
rep.processevent(event.TestrunStart())
|
|
||||||
l = list(self.session.genitems([modcol]))
|
|
||||||
assert len(l) == 0
|
|
||||||
s = popvalue(stringio)
|
|
||||||
print s
|
|
||||||
assert s.find("test_collect_fail.py - ImportError: No module named") != -1
|
|
||||||
rep.processevent(event.TestrunFinish())
|
|
||||||
assert_stringio_contains_lines(stringio, [
|
|
||||||
"> import xyz",
|
|
||||||
"E ImportError: No module named xyz"
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_internal_exception(self):
|
|
||||||
modcol = self.getmodulecol("def test_one(): pass")
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
rep = TerminalReporter(modcol._config, file=stringio)
|
|
||||||
excinfo = py.test.raises(ValueError, "raise ValueError('hello')")
|
|
||||||
rep.processevent(event.InternalException(excinfo))
|
|
||||||
s = popvalue(stringio)
|
|
||||||
assert s.find("InternalException:") != -1
|
|
||||||
|
|
||||||
def test_hostready_crash(self):
|
|
||||||
modcol = self.getmodulecol("""
|
|
||||||
def test_one():
|
|
||||||
pass
|
|
||||||
""", configargs=("-v",))
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
host1 = Host("localhost")
|
|
||||||
rep = TerminalReporter(modcol._config, file=stringio)
|
|
||||||
rep.processevent(event.HostGatewayReady(host1, None))
|
|
||||||
s = popvalue(stringio)
|
|
||||||
assert s.find("HostGatewayReady") != -1
|
|
||||||
rep.processevent(event.HostDown(host1, "myerror"))
|
|
||||||
s = popvalue(stringio)
|
|
||||||
assert s.find("HostDown") != -1
|
|
||||||
assert s.find("myerror") != -1
|
|
||||||
|
|
||||||
def test_writeline(self):
|
|
||||||
modcol = self.getmodulecol("def test_one(): pass")
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
rep = TerminalReporter(modcol._config, file=stringio)
|
|
||||||
rep.write_fspath_result(py.path.local("xy.py"), '.')
|
|
||||||
rep.write_line("hello world")
|
|
||||||
lines = popvalue(stringio).split('\n')
|
|
||||||
assert not lines[0]
|
|
||||||
assert lines[1].endswith("xy.py .")
|
|
||||||
assert lines[2] == "hello world"
|
|
||||||
|
|
||||||
def test_looponfailingreport(self):
|
|
||||||
modcol = self.getmodulecol("""
|
|
||||||
def test_fail():
|
|
||||||
assert 0
|
|
||||||
def test_fail2():
|
|
||||||
raise ValueError()
|
|
||||||
""")
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
rep = TerminalReporter(modcol._config, file=stringio)
|
|
||||||
reports = [basic_run_report(x) for x in modcol.collect()]
|
|
||||||
rep.processevent(event.LooponfailingInfo(reports, [modcol._config.topdir]))
|
|
||||||
assert_stringio_contains_lines(stringio, [
|
|
||||||
"*test_looponfailingreport.py:2: assert 0",
|
|
||||||
"*test_looponfailingreport.py:4: ValueError*",
|
|
||||||
"*waiting*",
|
|
||||||
"*%s*" % (modcol._config.topdir),
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_tb_option(self):
|
|
||||||
for tbopt in ["no", "short", "long"]:
|
|
||||||
print 'testing --tb=%s...' % tbopt
|
|
||||||
modcol = self.getmodulecol("""
|
|
||||||
import py
|
|
||||||
def g():
|
|
||||||
raise IndexError
|
|
||||||
def test_func():
|
|
||||||
print 6*7
|
|
||||||
g() # --calling--
|
|
||||||
""", configargs=("--tb=%s" % tbopt,), withsession=True)
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
rep = TerminalReporter(modcol._config, file=stringio)
|
|
||||||
rep.processevent(event.TestrunStart())
|
|
||||||
for item in self.session.genitems([modcol]):
|
|
||||||
ev = basic_run_report(item)
|
|
||||||
rep.processevent(ev)
|
|
||||||
rep.processevent(event.TestrunFinish())
|
|
||||||
s = popvalue(stringio)
|
|
||||||
if tbopt == "long":
|
|
||||||
assert 'print 6*7' in s
|
|
||||||
else:
|
|
||||||
assert 'print 6*7' not in s
|
|
||||||
if tbopt != "no":
|
|
||||||
assert '--calling--' in s
|
|
||||||
assert 'IndexError' in s
|
|
||||||
else:
|
|
||||||
assert 'FAILURES' not in s
|
|
||||||
assert '--calling--' not in s
|
|
||||||
assert 'IndexError' not in s
|
|
||||||
|
|
||||||
def test_show_path_before_running_test(self):
|
|
||||||
modcol = self.getmodulecol("""
|
|
||||||
def test_foobar():
|
|
||||||
pass
|
|
||||||
""", withsession=True)
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
rep = TerminalReporter(modcol._config, bus=self.session.bus, file=stringio)
|
|
||||||
l = list(self.session.genitems([modcol]))
|
|
||||||
assert len(l) == 1
|
|
||||||
rep.processevent(event.ItemStart(l[0]))
|
|
||||||
s = popvalue(stringio)
|
|
||||||
print s
|
|
||||||
assert s.find("test_show_path_before_running_test.py") != -1
|
|
||||||
|
|
||||||
def test_keyboard_interrupt(self, verbose=False):
|
|
||||||
modcol = self.getmodulecol("""
|
|
||||||
def test_foobar():
|
|
||||||
assert 0
|
|
||||||
def test_spamegg():
|
|
||||||
import py; py.test.skip('skip me please!')
|
|
||||||
def test_interrupt_me():
|
|
||||||
raise KeyboardInterrupt # simulating the user
|
|
||||||
""", configargs=("--showskipsummary",) + ("-v",)*verbose,
|
|
||||||
withsession=True)
|
|
||||||
stringio = py.std.cStringIO.StringIO()
|
|
||||||
rep = TerminalReporter(modcol._config, bus=self.session.bus, file=stringio)
|
|
||||||
rep.processevent(event.TestrunStart())
|
|
||||||
try:
|
|
||||||
for item in self.session.genitems([modcol]):
|
|
||||||
ev = basic_run_report(item)
|
|
||||||
rep.processevent(ev)
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
excinfo = py.code.ExceptionInfo()
|
|
||||||
else:
|
|
||||||
py.test.fail("no KeyboardInterrupt??")
|
|
||||||
s = popvalue(stringio)
|
|
||||||
if not verbose:
|
|
||||||
assert s.find("_keyboard_interrupt.py Fs") != -1
|
|
||||||
rep.processevent(event.TestrunFinish(exitstatus=2, excinfo=excinfo))
|
|
||||||
assert_stringio_contains_lines(stringio, [
|
|
||||||
" def test_foobar():",
|
|
||||||
"> assert 0",
|
|
||||||
"E assert 0",
|
|
||||||
])
|
|
||||||
text = stringio.getvalue()
|
|
||||||
assert "Skipped: 'skip me please!'" in text
|
|
||||||
assert "_keyboard_interrupt.py:6: KeyboardInterrupt" in text
|
|
||||||
see_details = "raise KeyboardInterrupt # simulating the user" in text
|
|
||||||
assert see_details == verbose
|
|
||||||
|
|
||||||
def test_verbose_keyboard_interrupt(self):
|
|
||||||
self.test_keyboard_interrupt(verbose=True)
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
import py
|
|
||||||
from py.__.test import event
|
|
||||||
|
|
||||||
def generic_path(item):
|
|
||||||
chain = item.listchain()
|
|
||||||
gpath = [chain[0].name]
|
|
||||||
fspath = chain[0].fspath
|
|
||||||
fspart = False
|
|
||||||
for node in chain[1:]:
|
|
||||||
newfspath = node.fspath
|
|
||||||
if newfspath == fspath:
|
|
||||||
if fspart:
|
|
||||||
gpath.append(':')
|
|
||||||
fspart = False
|
|
||||||
else:
|
|
||||||
gpath.append('.')
|
|
||||||
else:
|
|
||||||
gpath.append('/')
|
|
||||||
fspart = True
|
|
||||||
name = node.name
|
|
||||||
if name[0] in '([':
|
|
||||||
gpath.pop()
|
|
||||||
gpath.append(name)
|
|
||||||
fspath = newfspath
|
|
||||||
return ''.join(gpath)
|
|
||||||
|
|
||||||
class ResultLog(object):
|
|
||||||
|
|
||||||
def __init__(self, bus, logfile):
|
|
||||||
bus.subscribe(self.log_event_to_file)
|
|
||||||
self.logfile = logfile # preferably line buffered
|
|
||||||
|
|
||||||
def write_log_entry(self, shortrepr, name, longrepr):
|
|
||||||
print >>self.logfile, "%s %s" % (shortrepr, name)
|
|
||||||
for line in longrepr.splitlines():
|
|
||||||
print >>self.logfile, " %s" % line
|
|
||||||
|
|
||||||
def log_outcome(self, ev):
|
|
||||||
outcome = ev.outcome
|
|
||||||
gpath = generic_path(ev.colitem)
|
|
||||||
self.write_log_entry(outcome.shortrepr, gpath, str(outcome.longrepr))
|
|
||||||
|
|
||||||
def log_event_to_file(self, ev):
|
|
||||||
if isinstance(ev, event.ItemTestReport):
|
|
||||||
self.log_outcome(ev)
|
|
||||||
elif isinstance(ev, event.CollectionReport):
|
|
||||||
if not ev.passed:
|
|
||||||
self.log_outcome(ev)
|
|
||||||
elif isinstance(ev, event.InternalException):
|
|
||||||
path = ev.repr.reprcrash.path # fishing :(
|
|
||||||
self.write_log_entry('!', path, str(ev.repr))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -9,12 +9,14 @@
|
||||||
import py, os, sys
|
import py, os, sys
|
||||||
|
|
||||||
from py.__.test import event
|
from py.__.test import event
|
||||||
from py.__.test.outcome import Skipped, Exit
|
from py.__.test.outcome import Exit
|
||||||
from py.__.test.dsession.mypickle import ImmutablePickler
|
from py.__.test.dsession.mypickle import ImmutablePickler
|
||||||
import py.__.test.custompdb
|
import py.__.test.custompdb
|
||||||
|
|
||||||
class RobustRun(object):
|
class RobustRun(object):
|
||||||
""" a robust setup/execute/teardown protocol. """
|
""" a robust setup/execute/teardown protocol used both for test collectors
|
||||||
|
and test items.
|
||||||
|
"""
|
||||||
def __init__(self, colitem, pdb=None):
|
def __init__(self, colitem, pdb=None):
|
||||||
self.colitem = colitem
|
self.colitem = colitem
|
||||||
self.getcapture = colitem._config._getcapture
|
self.getcapture = colitem._config._getcapture
|
||||||
|
|
@ -46,35 +48,18 @@ class RobustRun(object):
|
||||||
excinfo = py.code.ExceptionInfo()
|
excinfo = py.code.ExceptionInfo()
|
||||||
return self.makereport(res, when, excinfo, outerr)
|
return self.makereport(res, when, excinfo, outerr)
|
||||||
|
|
||||||
def getkw(self, when, excinfo, outerr):
|
|
||||||
if excinfo.errisinstance(Skipped):
|
|
||||||
outcome = "skipped"
|
|
||||||
shortrepr = "s"
|
|
||||||
longrepr = excinfo._getreprcrash()
|
|
||||||
else:
|
|
||||||
outcome = "failed"
|
|
||||||
if when == "execute":
|
|
||||||
longrepr = self.colitem.repr_failure(excinfo, outerr)
|
|
||||||
shortrepr = self.colitem.shortfailurerepr
|
|
||||||
else:
|
|
||||||
longrepr = self.colitem._repr_failure_py(excinfo, outerr)
|
|
||||||
shortrepr = self.colitem.shortfailurerepr.lower()
|
|
||||||
kw = { outcome: OutcomeRepr(when, shortrepr, longrepr)}
|
|
||||||
return kw
|
|
||||||
|
|
||||||
class ItemRunner(RobustRun):
|
class ItemRunner(RobustRun):
|
||||||
def setup(self):
|
def setup(self):
|
||||||
self.colitem._setupstate.prepare(self.colitem)
|
self.colitem._setupstate.prepare(self.colitem)
|
||||||
def teardown(self):
|
def teardown(self):
|
||||||
self.colitem._setupstate.teardown_exact(self.colitem)
|
self.colitem._setupstate.teardown_exact(self.colitem)
|
||||||
def execute(self):
|
def execute(self):
|
||||||
|
#self.colitem.config.pytestplugins.pre_execute(self.colitem)
|
||||||
self.colitem.runtest()
|
self.colitem.runtest()
|
||||||
|
#self.colitem.config.pytestplugins.post_execute(self.colitem)
|
||||||
|
|
||||||
def makereport(self, res, when, excinfo, outerr):
|
def makereport(self, res, when, excinfo, outerr):
|
||||||
if excinfo:
|
testrep = event.ItemTestReport(self.colitem, excinfo, when, outerr)
|
||||||
kw = self.getkw(when, excinfo, outerr)
|
|
||||||
else:
|
|
||||||
kw = {'passed': OutcomeRepr(when, '.', "")}
|
|
||||||
testrep = event.ItemTestReport(self.colitem, **kw)
|
|
||||||
if self.pdb and testrep.failed:
|
if self.pdb and testrep.failed:
|
||||||
tw = py.io.TerminalWriter()
|
tw = py.io.TerminalWriter()
|
||||||
testrep.toterminal(tw)
|
testrep.toterminal(tw)
|
||||||
|
|
@ -89,26 +74,13 @@ class CollectorRunner(RobustRun):
|
||||||
def execute(self):
|
def execute(self):
|
||||||
return self.colitem._memocollect()
|
return self.colitem._memocollect()
|
||||||
def makereport(self, res, when, excinfo, outerr):
|
def makereport(self, res, when, excinfo, outerr):
|
||||||
if excinfo:
|
return event.CollectionReport(self.colitem, res, excinfo, when, outerr)
|
||||||
kw = self.getkw(when, excinfo, outerr)
|
|
||||||
else:
|
|
||||||
kw = {'passed': OutcomeRepr(when, '', "")}
|
|
||||||
return event.CollectionReport(self.colitem, res, **kw)
|
|
||||||
|
|
||||||
NORESULT = object()
|
NORESULT = object()
|
||||||
#
|
#
|
||||||
# public entrypoints / objects
|
# public entrypoints / objects
|
||||||
#
|
#
|
||||||
|
|
||||||
class OutcomeRepr(object):
|
|
||||||
def __init__(self, when, shortrepr, longrepr):
|
|
||||||
self.when = when
|
|
||||||
self.shortrepr = shortrepr
|
|
||||||
self.longrepr = longrepr
|
|
||||||
def __str__(self):
|
|
||||||
return "<OutcomeRepr when=%r, shortrepr=%r, len-longrepr=%s" %(
|
|
||||||
self.when, self.shortrepr, len(str(self.longrepr)))
|
|
||||||
|
|
||||||
def basic_collect_report(item):
|
def basic_collect_report(item):
|
||||||
return CollectorRunner(item).run()
|
return CollectorRunner(item).run()
|
||||||
|
|
||||||
|
|
@ -137,13 +109,12 @@ def forked_run_report(item, pdb=None):
|
||||||
else:
|
else:
|
||||||
if result.exitstatus == EXITSTATUS_TESTEXIT:
|
if result.exitstatus == EXITSTATUS_TESTEXIT:
|
||||||
raise Exit("forked test item %s raised Exit" %(item,))
|
raise Exit("forked test item %s raised Exit" %(item,))
|
||||||
return report_crash(item, result)
|
return report_process_crash(item, result)
|
||||||
|
|
||||||
def report_crash(item, result):
|
def report_process_crash(item, result):
|
||||||
path, lineno = item.getfslineno()
|
path, lineno = item.getfslineno()
|
||||||
longrepr = [
|
longrepr = [
|
||||||
("X", "CRASHED"),
|
("X", "CRASHED"),
|
||||||
("%s:%s: CRASHED with signal %d" %(path, lineno, result.signal)),
|
("%s:%s: CRASHED with signal %d" %(path, lineno, result.signal)),
|
||||||
]
|
]
|
||||||
outcomerepr = OutcomeRepr(when="???", shortrepr="X", longrepr=longrepr)
|
return event.ItemTestReport(item, excinfo=longrepr, when="???")
|
||||||
return event.ItemTestReport(item, failed=outcomerepr)
|
|
||||||
|
|
|
||||||
|
|
@ -7,14 +7,10 @@
|
||||||
|
|
||||||
import py
|
import py
|
||||||
from py.__.test import event, outcome
|
from py.__.test import event, outcome
|
||||||
from py.__.test.event import EventBus
|
|
||||||
import py.__.test.custompdb
|
|
||||||
from py.__.test.resultlog import ResultLog
|
|
||||||
|
|
||||||
# used for genitems()
|
# imports used for genitems()
|
||||||
from py.__.test.outcome import Exit
|
Item = py.test.collect.Item
|
||||||
Item = (py.test.collect.Item, py.test.collect.Item)
|
Collector = py.test.collect.Collector
|
||||||
Collector = (py.test.collect.Collector, py.test.collect.Collector)
|
|
||||||
from runner import basic_collect_report
|
from runner import basic_collect_report
|
||||||
from py.__.test.dsession.hostmanage import makehostup
|
from py.__.test.dsession.hostmanage import makehostup
|
||||||
|
|
||||||
|
|
@ -25,20 +21,10 @@ class Session(object):
|
||||||
"""
|
"""
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.bus = EventBus()
|
self.bus = config.bus # shortcut
|
||||||
|
self.bus.register(self)
|
||||||
|
self._testsfailed = False
|
||||||
self._nomatch = False
|
self._nomatch = False
|
||||||
eventlog = self.config.option.eventlog
|
|
||||||
if eventlog:
|
|
||||||
self.eventlog = py.path.local(eventlog)
|
|
||||||
f = self.eventlog.open("w")
|
|
||||||
def eventwrite(ev):
|
|
||||||
print >>f, ev
|
|
||||||
f.flush()
|
|
||||||
self.bus.subscribe(eventwrite)
|
|
||||||
resultlog = self.config.option.resultlog
|
|
||||||
if resultlog:
|
|
||||||
f = open(resultlog, 'w', 1) # line buffered
|
|
||||||
self.resultlog = ResultLog(self.bus, f)
|
|
||||||
|
|
||||||
def fixoptions(self):
|
def fixoptions(self):
|
||||||
""" check, fix and determine conflicting options. """
|
""" check, fix and determine conflicting options. """
|
||||||
|
|
@ -56,19 +42,24 @@ class Session(object):
|
||||||
""" yield Items from iterating over the given colitems. """
|
""" yield Items from iterating over the given colitems. """
|
||||||
while colitems:
|
while colitems:
|
||||||
next = colitems.pop(0)
|
next = colitems.pop(0)
|
||||||
|
if isinstance(next, (tuple, list)):
|
||||||
|
colitems[:] = list(next) + colitems
|
||||||
|
continue
|
||||||
|
assert self.bus is next._config.bus
|
||||||
|
notify = self.bus.notify
|
||||||
if isinstance(next, Item):
|
if isinstance(next, Item):
|
||||||
remaining = self.filteritems([next])
|
remaining = self.filteritems([next])
|
||||||
if remaining:
|
if remaining:
|
||||||
self.bus.notify(event.ItemStart(next))
|
notify("itemstart", event.ItemStart(next))
|
||||||
yield next
|
yield next
|
||||||
else:
|
else:
|
||||||
assert isinstance(next, Collector)
|
assert isinstance(next, Collector)
|
||||||
self.bus.notify(event.CollectionStart(next))
|
notify("collectionstart", event.CollectionStart(next))
|
||||||
ev = basic_collect_report(next)
|
ev = basic_collect_report(next)
|
||||||
if ev.passed:
|
if ev.passed:
|
||||||
for x in self.genitems(ev.result, keywordexpr):
|
for x in self.genitems(ev.result, keywordexpr):
|
||||||
yield x
|
yield x
|
||||||
self.bus.notify(ev)
|
notify("collectionreport", ev)
|
||||||
|
|
||||||
def filteritems(self, colitems):
|
def filteritems(self, colitems):
|
||||||
""" return items to process (some may be deselected)"""
|
""" return items to process (some may be deselected)"""
|
||||||
|
|
@ -86,7 +77,7 @@ class Session(object):
|
||||||
continue
|
continue
|
||||||
remaining.append(colitem)
|
remaining.append(colitem)
|
||||||
if deselected:
|
if deselected:
|
||||||
self.bus.notify(event.Deselected(deselected, ))
|
self.bus.notify("deselected", event.Deselected(deselected, ))
|
||||||
if self.config.option.keyword.endswith(":"):
|
if self.config.option.keyword.endswith(":"):
|
||||||
self._nomatch = True
|
self._nomatch = True
|
||||||
return remaining
|
return remaining
|
||||||
|
|
@ -98,23 +89,19 @@ class Session(object):
|
||||||
|
|
||||||
def sessionstarts(self):
|
def sessionstarts(self):
|
||||||
""" setup any neccessary resources ahead of the test run. """
|
""" setup any neccessary resources ahead of the test run. """
|
||||||
self.bus.notify(event.TestrunStart())
|
self.bus.notify("testrunstart", event.TestrunStart())
|
||||||
self._failurelist = []
|
|
||||||
self.bus.subscribe(self._processfailures)
|
|
||||||
|
|
||||||
def _processfailures(self, ev):
|
def pyevent_itemtestreport(self, rep):
|
||||||
if isinstance(ev, event.BaseReport) and ev.failed:
|
if rep.failed:
|
||||||
self._failurelist.append(ev)
|
self._testsfailed = True
|
||||||
if self.config.option.exitfirst:
|
if self.config.option.exitfirst:
|
||||||
self.shouldstop = True
|
self.shouldstop = True
|
||||||
|
pyevent_collectionreport = pyevent_itemtestreport
|
||||||
|
|
||||||
def sessionfinishes(self, exitstatus=0, excinfo=None):
|
def sessionfinishes(self, exitstatus=0, excinfo=None):
|
||||||
""" teardown any resources after a test run. """
|
""" teardown any resources after a test run. """
|
||||||
self.bus.notify(event.TestrunFinish(exitstatus=exitstatus,
|
self.bus.notify("testrunfinish",
|
||||||
excinfo=excinfo))
|
event.TestrunFinish(exitstatus=exitstatus, excinfo=excinfo))
|
||||||
self.bus.unsubscribe(self._processfailures)
|
|
||||||
#self.reporter.deactivate()
|
|
||||||
return self._failurelist
|
|
||||||
|
|
||||||
def getinitialitems(self, colitems):
|
def getinitialitems(self, colitems):
|
||||||
if colitems is None:
|
if colitems is None:
|
||||||
|
|
@ -127,7 +114,7 @@ class Session(object):
|
||||||
colitems = self.getinitialitems(colitems)
|
colitems = self.getinitialitems(colitems)
|
||||||
self.shouldstop = False
|
self.shouldstop = False
|
||||||
self.sessionstarts()
|
self.sessionstarts()
|
||||||
self.bus.notify(makehostup())
|
self.bus.notify("hostup", makehostup())
|
||||||
exitstatus = outcome.EXIT_OK
|
exitstatus = outcome.EXIT_OK
|
||||||
captured_excinfo = None
|
captured_excinfo = None
|
||||||
try:
|
try:
|
||||||
|
|
@ -136,24 +123,26 @@ class Session(object):
|
||||||
break
|
break
|
||||||
if not self.config.option.collectonly:
|
if not self.config.option.collectonly:
|
||||||
self.runtest(item)
|
self.runtest(item)
|
||||||
|
|
||||||
py.test.collect.Item._setupstate.teardown_all()
|
py.test.collect.Item._setupstate.teardown_all()
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
captured_excinfo = py.code.ExceptionInfo()
|
captured_excinfo = py.code.ExceptionInfo()
|
||||||
exitstatus = outcome.EXIT_INTERRUPTED
|
exitstatus = outcome.EXIT_INTERRUPTED
|
||||||
except:
|
except:
|
||||||
self.bus.notify(event.InternalException())
|
captured_excinfo = py.code.ExceptionInfo()
|
||||||
|
self.bus.notify("internalerror", event.InternalException(captured_excinfo))
|
||||||
exitstatus = outcome.EXIT_INTERNALERROR
|
exitstatus = outcome.EXIT_INTERNALERROR
|
||||||
if self._failurelist and exitstatus == 0:
|
if exitstatus == 0 and self._testsfailed:
|
||||||
exitstatus = outcome.EXIT_TESTSFAILED
|
exitstatus = outcome.EXIT_TESTSFAILED
|
||||||
self.sessionfinishes(exitstatus=exitstatus, excinfo=captured_excinfo)
|
self.sessionfinishes(exitstatus=exitstatus, excinfo=captured_excinfo)
|
||||||
return exitstatus
|
return exitstatus
|
||||||
|
|
||||||
def runpdb(self, excinfo):
|
def runpdb(self, excinfo):
|
||||||
py.__.test.custompdb.post_mortem(excinfo._excinfo[2])
|
from py.__.test.custompdb import post_mortem
|
||||||
|
post_mortem(excinfo._excinfo[2])
|
||||||
|
|
||||||
def runtest(self, item):
|
def runtest(self, item):
|
||||||
runner = item._getrunner()
|
runner = item._getrunner()
|
||||||
pdb = self.config.option.usepdb and self.runpdb or None
|
pdb = self.config.option.usepdb and self.runpdb or None
|
||||||
testrep = runner(item, pdb=pdb)
|
testrep = runner(item, pdb=pdb)
|
||||||
self.bus.notify(testrep)
|
self.bus.notify("itemtestreport", testrep)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,110 +1,73 @@
|
||||||
import py
|
import py
|
||||||
from suptest import assert_lines_contain_lines, FileCreation
|
|
||||||
|
|
||||||
pydir = py.path.local(py.__file__).dirpath()
|
pydir = py.path.local(py.__file__).dirpath()
|
||||||
pytestpath = pydir.join("bin", "py.test")
|
pytestpath = pydir.join("bin", "py.test")
|
||||||
EXPECTTIMEOUT=10.0
|
EXPECTTIMEOUT=10.0
|
||||||
|
|
||||||
def setup_module(mod):
|
class TestPyTest:
|
||||||
mod.modtmpdir = py.test.ensuretemp(mod.__name__)
|
def test_assertion_magic(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
class Result:
|
|
||||||
def __init__(self, ret, outlines, errlines):
|
|
||||||
self.ret = ret
|
|
||||||
self.outlines = outlines
|
|
||||||
self.errlines = errlines
|
|
||||||
|
|
||||||
class AcceptBase(FileCreation):
|
|
||||||
def popen(self, cmdargs, stdout, stderr, **kw):
|
|
||||||
if not hasattr(py.std, 'subprocess'):
|
|
||||||
py.test.skip("no subprocess module")
|
|
||||||
return py.std.subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw)
|
|
||||||
|
|
||||||
def run(self, *cmdargs):
|
|
||||||
cmdargs = map(str, cmdargs)
|
|
||||||
p1 = py.path.local("stdout")
|
|
||||||
p2 = py.path.local("stderr")
|
|
||||||
print "running", cmdargs, "curdir=", py.path.local()
|
|
||||||
popen = self.popen(cmdargs, stdout=p1.open("w"), stderr=p2.open("w"))
|
|
||||||
ret = popen.wait()
|
|
||||||
out, err = p1.readlines(cr=0), p2.readlines(cr=0)
|
|
||||||
if err:
|
|
||||||
for line in err:
|
|
||||||
print >>py.std.sys.stderr, line
|
|
||||||
return Result(ret, out, err)
|
|
||||||
|
|
||||||
def runpybin(self, scriptname, *args):
|
|
||||||
bindir = py.path.local(py.__file__).dirpath("bin")
|
|
||||||
if py.std.sys.platform == "win32":
|
|
||||||
script = bindir.join("win32", scriptname + ".cmd")
|
|
||||||
else:
|
|
||||||
script = bindir.join(scriptname)
|
|
||||||
assert script.check()
|
|
||||||
return self.run(script, *args)
|
|
||||||
|
|
||||||
def runpytest(self, *args):
|
|
||||||
return self.runpybin("py.test", *args)
|
|
||||||
|
|
||||||
def setup_method(self, method):
|
|
||||||
super(AcceptBase, self).setup_method(method)
|
|
||||||
self.old = self.tmpdir.chdir()
|
|
||||||
|
|
||||||
def teardown_method(self, method):
|
|
||||||
self.old.chdir()
|
|
||||||
|
|
||||||
class TestPyTest(AcceptBase):
|
|
||||||
def test_assertion_magic(self):
|
|
||||||
p = self.makepyfile(test_one="""
|
|
||||||
def test_this():
|
def test_this():
|
||||||
x = 0
|
x = 0
|
||||||
assert x
|
assert x
|
||||||
""")
|
""")
|
||||||
result = self.runpytest(p)
|
result = testdir.runpytest(p)
|
||||||
extra = assert_lines_contain_lines(result.outlines, [
|
extra = result.stdout.fnmatch_lines([
|
||||||
"> assert x",
|
"> assert x",
|
||||||
"E assert 0",
|
"E assert 0",
|
||||||
])
|
])
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
|
|
||||||
|
def test_collectonly_simple(self, testdir):
|
||||||
def test_collectonly_simple(self):
|
p = testdir.makepyfile("""
|
||||||
p = self.makepyfile(test_one="""
|
|
||||||
def test_func1():
|
def test_func1():
|
||||||
pass
|
pass
|
||||||
class TestClass:
|
class TestClass:
|
||||||
def test_method(self):
|
def test_method(self):
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
result = self.runpytest("--collectonly", p)
|
result = testdir.runpytest("--collectonly", p)
|
||||||
err = "".join(result.errlines)
|
stderr = result.stderr.str().strip()
|
||||||
assert err.strip().startswith("inserting into sys.path")
|
assert stderr.startswith("inserting into sys.path")
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
extra = assert_lines_contain_lines(result.outlines, py.code.Source("""
|
extra = result.stdout.fnmatch_lines(py.code.Source("""
|
||||||
<Module 'test_one.py'>
|
<Module '*.py'>
|
||||||
<Function 'test_func1'*>
|
<Function 'test_func1'*>
|
||||||
<Class 'TestClass'>
|
<Class 'TestClass'>
|
||||||
<Instance '()'>
|
<Instance '()'>
|
||||||
<Function 'test_method'*>
|
<Function 'test_method'*>
|
||||||
""").strip())
|
""").strip())
|
||||||
|
|
||||||
def test_nested_import_error(self):
|
def test_collectonly_error(self, testdir):
|
||||||
p = self.makepyfile(
|
p = testdir.makepyfile("import Errlkjqweqwe")
|
||||||
test_one="""
|
result = testdir.runpytest("--collectonly", p)
|
||||||
|
stderr = result.stderr.str().strip()
|
||||||
|
assert stderr.startswith("inserting into sys.path")
|
||||||
|
assert result.ret == 1
|
||||||
|
extra = result.stdout.fnmatch_lines(py.code.Source("""
|
||||||
|
<Module '*.py'>
|
||||||
|
*ImportError*
|
||||||
|
!!!*failures*!!!
|
||||||
|
*test_collectonly_error.py:1*
|
||||||
|
""").strip())
|
||||||
|
|
||||||
|
|
||||||
|
def test_nested_import_error(self, testdir):
|
||||||
|
p = testdir.makepyfile("""
|
||||||
import import_fails
|
import import_fails
|
||||||
def test_this():
|
def test_this():
|
||||||
assert import_fails.a == 1
|
assert import_fails.a == 1
|
||||||
""",
|
""")
|
||||||
import_fails="import does_not_work"
|
testdir.makepyfile(import_fails="import does_not_work")
|
||||||
)
|
result = testdir.runpytest(p)
|
||||||
result = self.runpytest(p)
|
extra = result.stdout.fnmatch_lines([
|
||||||
extra = assert_lines_contain_lines(result.outlines, [
|
|
||||||
"> import import_fails",
|
"> import import_fails",
|
||||||
"E ImportError: No module named does_not_work",
|
"E ImportError: No module named does_not_work",
|
||||||
])
|
])
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
|
|
||||||
def test_skipped_reasons(self):
|
def test_skipped_reasons(self, testdir):
|
||||||
p1 = self.makepyfile(
|
testdir.makepyfile(
|
||||||
test_one="""
|
test_one="""
|
||||||
from conftest import doskip
|
from conftest import doskip
|
||||||
def setup_function(func):
|
def setup_function(func):
|
||||||
|
|
@ -125,34 +88,34 @@ class TestPyTest(AcceptBase):
|
||||||
py.test.skip('test')
|
py.test.skip('test')
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = self.runpytest()
|
result = testdir.runpytest()
|
||||||
extra = assert_lines_contain_lines(result.outlines, [
|
extra = result.stdout.fnmatch_lines([
|
||||||
"*test_one.py ss",
|
"*test_one.py ss",
|
||||||
"*test_two.py - Skipped*",
|
"*test_two.py S",
|
||||||
"___* skipped test summary *_",
|
"___* skipped test summary *_",
|
||||||
"*conftest.py:3: *3* Skipped: 'test'",
|
"*conftest.py:3: *3* Skipped: 'test'",
|
||||||
])
|
])
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
def test_deselected(self):
|
def test_deselected(self, testdir):
|
||||||
p1 = self.makepyfile(test_one="""
|
testpath = testdir.makepyfile("""
|
||||||
def test_one():
|
def test_one():
|
||||||
pass
|
pass
|
||||||
def test_two():
|
def test_two():
|
||||||
pass
|
pass
|
||||||
def test_three():
|
def test_three():
|
||||||
pass
|
pass
|
||||||
""",
|
"""
|
||||||
)
|
)
|
||||||
result = self.runpytest("-k", "test_two:")
|
result = testdir.runpytest("-k", "test_two:", testpath)
|
||||||
extra = assert_lines_contain_lines(result.outlines, [
|
extra = result.stdout.fnmatch_lines([
|
||||||
"*test_one.py ..",
|
"*test_deselected.py ..",
|
||||||
"=* 1 test*deselected by 'test_two:'*=",
|
"=* 1 test*deselected by 'test_two:'*=",
|
||||||
])
|
])
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
def test_no_skip_summary_if_failure(self):
|
def test_no_skip_summary_if_failure(self, testdir):
|
||||||
p1 = self.makepyfile(test_one="""
|
testdir.makepyfile("""
|
||||||
import py
|
import py
|
||||||
def test_ok():
|
def test_ok():
|
||||||
pass
|
pass
|
||||||
|
|
@ -161,12 +124,12 @@ class TestPyTest(AcceptBase):
|
||||||
def test_skip():
|
def test_skip():
|
||||||
py.test.skip("dontshow")
|
py.test.skip("dontshow")
|
||||||
""")
|
""")
|
||||||
result = self.runpytest()
|
result = testdir.runpytest()
|
||||||
assert str(result.outlines).find("skip test summary") == -1
|
assert result.stdout.str().find("skip test summary") == -1
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
|
|
||||||
def test_passes(self):
|
def test_passes(self, testdir):
|
||||||
p1 = self.makepyfile(test_one="""
|
p1 = testdir.makepyfile("""
|
||||||
def test_passes():
|
def test_passes():
|
||||||
pass
|
pass
|
||||||
class TestClass:
|
class TestClass:
|
||||||
|
|
@ -175,33 +138,32 @@ class TestPyTest(AcceptBase):
|
||||||
""")
|
""")
|
||||||
old = p1.dirpath().chdir()
|
old = p1.dirpath().chdir()
|
||||||
try:
|
try:
|
||||||
result = self.runpytest()
|
result = testdir.runpytest()
|
||||||
finally:
|
finally:
|
||||||
old.chdir()
|
old.chdir()
|
||||||
extra = assert_lines_contain_lines(result.outlines, [
|
extra = result.stdout.fnmatch_lines([
|
||||||
"test_one.py ..",
|
"test_passes.py ..",
|
||||||
"* failures: no failures*",
|
"* 2 pass*",
|
||||||
])
|
])
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
|
|
||||||
def test_header_trailer_info(self):
|
def test_header_trailer_info(self, testdir):
|
||||||
p1 = self.makepyfile(test_one="""
|
p1 = testdir.makepyfile("""
|
||||||
def test_passes():
|
def test_passes():
|
||||||
pass
|
pass
|
||||||
""")
|
""")
|
||||||
result = self.runpytest()
|
result = testdir.runpytest()
|
||||||
verinfo = ".".join(map(str, py.std.sys.version_info[:3]))
|
verinfo = ".".join(map(str, py.std.sys.version_info[:3]))
|
||||||
extra = assert_lines_contain_lines(result.outlines, [
|
extra = result.stdout.fnmatch_lines([
|
||||||
"*===== test session starts ====*",
|
"*===== test session starts ====*",
|
||||||
"*localhost* %s %s - Python %s*" %(
|
"*localhost* %s %s - Python %s*" %(
|
||||||
py.std.sys.platform, py.std.sys.executable, verinfo),
|
py.std.sys.platform, py.std.sys.executable, verinfo),
|
||||||
"*test_one.py .",
|
"*test_header_trailer_info.py .",
|
||||||
"=* 1/1 passed + 0 skips in *.[0-9][0-9] seconds *=",
|
"=* 1 passed in *.[0-9][0-9] seconds *=",
|
||||||
"=* no failures :)*=",
|
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_traceback_failure(self):
|
def test_traceback_failure(self, testdir):
|
||||||
p1 = self.makepyfile(test_fail="""
|
p1 = testdir.makepyfile("""
|
||||||
def g():
|
def g():
|
||||||
return 2
|
return 2
|
||||||
def f(x):
|
def f(x):
|
||||||
|
|
@ -209,16 +171,16 @@ class TestPyTest(AcceptBase):
|
||||||
def test_onefails():
|
def test_onefails():
|
||||||
f(3)
|
f(3)
|
||||||
""")
|
""")
|
||||||
result = self.runpytest(p1)
|
result = testdir.runpytest(p1)
|
||||||
assert_lines_contain_lines(result.outlines, [
|
result.stdout.fnmatch_lines([
|
||||||
"*test_fail.py F",
|
"*test_traceback_failure.py F",
|
||||||
"====* FAILURES *====",
|
"====* FAILURES *====",
|
||||||
"____*____",
|
"____*____",
|
||||||
"",
|
"",
|
||||||
" def test_onefails():",
|
" def test_onefails():",
|
||||||
"> f(3)",
|
"> f(3)",
|
||||||
"",
|
"",
|
||||||
"*test_fail.py:6: ",
|
"*test_*.py:6: ",
|
||||||
"_ _ _ *",
|
"_ _ _ *",
|
||||||
#"",
|
#"",
|
||||||
" def f(x):",
|
" def f(x):",
|
||||||
|
|
@ -226,11 +188,11 @@ class TestPyTest(AcceptBase):
|
||||||
"E assert 3 == 2",
|
"E assert 3 == 2",
|
||||||
"E + where 2 = g()",
|
"E + where 2 = g()",
|
||||||
"",
|
"",
|
||||||
"*test_fail.py:4: AssertionError"
|
"*test_traceback_failure.py:4: AssertionError"
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_capturing_outerr(self):
|
def test_capturing_outerr(self, testdir):
|
||||||
p1 = self.makepyfile(test_one="""
|
p1 = testdir.makepyfile("""
|
||||||
import sys
|
import sys
|
||||||
def test_capturing():
|
def test_capturing():
|
||||||
print 42
|
print 42
|
||||||
|
|
@ -240,52 +202,35 @@ class TestPyTest(AcceptBase):
|
||||||
print >>sys.stderr, 2
|
print >>sys.stderr, 2
|
||||||
raise ValueError
|
raise ValueError
|
||||||
""")
|
""")
|
||||||
result = self.runpytest(p1)
|
result = testdir.runpytest(p1)
|
||||||
assert_lines_contain_lines(result.outlines, [
|
result.stdout.fnmatch_lines([
|
||||||
"*test_one.py .F",
|
"*test_capturing_outerr.py .F",
|
||||||
"====* FAILURES *====",
|
"====* FAILURES *====",
|
||||||
"____*____",
|
"____*____",
|
||||||
"*test_one.py:8: ValueError",
|
"*test_capturing_outerr.py:8: ValueError",
|
||||||
"*--- Captured stdout ---*",
|
"*--- Captured stdout ---*",
|
||||||
"1",
|
"1",
|
||||||
"*--- Captured stderr ---*",
|
"*--- Captured stderr ---*",
|
||||||
"2",
|
"2",
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_showlocals(self):
|
def test_showlocals(self, testdir):
|
||||||
p1 = self.makepyfile(test_one="""
|
p1 = testdir.makepyfile("""
|
||||||
def test_showlocals():
|
def test_showlocals():
|
||||||
x = 3
|
x = 3
|
||||||
y = "x" * 5000
|
y = "x" * 5000
|
||||||
assert 0
|
assert 0
|
||||||
""")
|
""")
|
||||||
result = self.runpytest(p1, '-l')
|
result = testdir.runpytest(p1, '-l')
|
||||||
assert_lines_contain_lines(result.outlines, [
|
result.stdout.fnmatch_lines([
|
||||||
#"_ _ * Locals *",
|
#"_ _ * Locals *",
|
||||||
"x* = 3",
|
"x* = 3",
|
||||||
"y* = 'xxxxxx*"
|
"y* = 'xxxxxx*"
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_doctest_simple_failing(self):
|
|
||||||
p = self.maketxtfile(doc="""
|
|
||||||
>>> i = 0
|
|
||||||
>>> i + 1
|
|
||||||
2
|
|
||||||
""")
|
|
||||||
result = self.runpytest(p)
|
|
||||||
assert_lines_contain_lines(result.outlines, [
|
|
||||||
'001 >>> i = 0',
|
|
||||||
'002 >>> i + 1',
|
|
||||||
'Expected:',
|
|
||||||
" 2",
|
|
||||||
"Got:",
|
|
||||||
" 1",
|
|
||||||
"*doc.txt:2: DocTestFailure"
|
|
||||||
])
|
|
||||||
|
|
||||||
def test_dist_testing(self):
|
def test_dist_testing(self, testdir):
|
||||||
p1 = self.makepyfile(
|
p1 = testdir.makepyfile("""
|
||||||
test_one="""
|
|
||||||
import py
|
import py
|
||||||
def test_fail0():
|
def test_fail0():
|
||||||
assert 0
|
assert 0
|
||||||
|
|
@ -300,22 +245,20 @@ class TestPyTest(AcceptBase):
|
||||||
dist_hosts = ['localhost'] * 3
|
dist_hosts = ['localhost'] * 3
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = self.runpytest(p1, '-d')
|
result = testdir.runpytest(p1, '-d')
|
||||||
assert_lines_contain_lines(result.outlines, [
|
result.stdout.fnmatch_lines([
|
||||||
"HOSTUP: localhost*Python*",
|
"HOSTUP: localhost*Python*",
|
||||||
#"HOSTUP: localhost*Python*",
|
#"HOSTUP: localhost*Python*",
|
||||||
#"HOSTUP: localhost*Python*",
|
#"HOSTUP: localhost*Python*",
|
||||||
"*1/3 passed + 1 skip*",
|
"*2 failed, 1 passed, 1 skipped*",
|
||||||
"*failures: 2*",
|
|
||||||
])
|
])
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
|
|
||||||
def test_dist_tests_with_crash(self):
|
def test_dist_tests_with_crash(self, testdir):
|
||||||
if not hasattr(py.std.os, 'kill'):
|
if not hasattr(py.std.os, 'kill'):
|
||||||
py.test.skip("no os.kill")
|
py.test.skip("no os.kill")
|
||||||
|
|
||||||
p1 = self.makepyfile(
|
p1 = testdir.makepyfile("""
|
||||||
test_one="""
|
|
||||||
import py
|
import py
|
||||||
def test_fail0():
|
def test_fail0():
|
||||||
assert 0
|
assert 0
|
||||||
|
|
@ -335,34 +278,33 @@ class TestPyTest(AcceptBase):
|
||||||
dist_hosts = ['localhost'] * 3
|
dist_hosts = ['localhost'] * 3
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
result = self.runpytest(p1, '-d')
|
result = testdir.runpytest(p1, '-d')
|
||||||
assert_lines_contain_lines(result.outlines, [
|
result.stdout.fnmatch_lines([
|
||||||
"*localhost*Python*",
|
"*localhost*Python*",
|
||||||
"*localhost*Python*",
|
"*localhost*Python*",
|
||||||
"*localhost*Python*",
|
"*localhost*Python*",
|
||||||
"HostDown*localhost*TERMINATED*",
|
"HostDown*localhost*TERMINATED*",
|
||||||
"*1/4 passed + 1 skip*",
|
"*3 failed, 1 passed, 1 skipped*"
|
||||||
"*failures: 3*",
|
|
||||||
])
|
])
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
|
|
||||||
def test_keyboard_interrupt(self):
|
def test_keyboard_interrupt(self, testdir):
|
||||||
p1 = self.makepyfile(test_one="""
|
p1 = testdir.makepyfile("""
|
||||||
import py
|
import py
|
||||||
def test_fail():
|
def test_fail():
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
def test_inter():
|
def test_inter():
|
||||||
raise KeyboardInterrupt()
|
raise KeyboardInterrupt()
|
||||||
""")
|
""")
|
||||||
result = self.runpytest(p1)
|
result = testdir.runpytest(p1)
|
||||||
assert_lines_contain_lines(result.outlines, [
|
result.stdout.fnmatch_lines([
|
||||||
#"*test_inter() INTERRUPTED",
|
#"*test_inter() INTERRUPTED",
|
||||||
"*KEYBOARD INTERRUPT*",
|
"*KEYBOARD INTERRUPT*",
|
||||||
"*0/1 passed*",
|
"*1 failed*",
|
||||||
])
|
])
|
||||||
|
|
||||||
def test_verbose_reporting(self):
|
def test_verbose_reporting(self, testdir):
|
||||||
p1 = self.makepyfile(test_one="""
|
p1 = testdir.makepyfile("""
|
||||||
import py
|
import py
|
||||||
def test_fail():
|
def test_fail():
|
||||||
raise ValueError()
|
raise ValueError()
|
||||||
|
|
@ -376,20 +318,20 @@ class TestPyTest(AcceptBase):
|
||||||
assert x == 1
|
assert x == 1
|
||||||
yield check, 0
|
yield check, 0
|
||||||
""")
|
""")
|
||||||
result = self.runpytest(p1, '-v')
|
result = testdir.runpytest(p1, '-v')
|
||||||
assert_lines_contain_lines(result.outlines, [
|
result.stdout.fnmatch_lines([
|
||||||
"*test_one.py:2: test_fail*FAIL",
|
"*test_verbose_reporting.py:2: test_fail*FAIL",
|
||||||
"*test_one.py:4: test_pass*PASS",
|
"*test_verbose_reporting.py:4: test_pass*PASS",
|
||||||
"*test_one.py:7: TestClass.test_skip*SKIP",
|
"*test_verbose_reporting.py:7: TestClass.test_skip*SKIP",
|
||||||
"*test_one.py:10: test_gen*FAIL",
|
"*test_verbose_reporting.py:10: test_gen*FAIL",
|
||||||
])
|
])
|
||||||
assert result.ret == 1
|
assert result.ret == 1
|
||||||
|
|
||||||
class TestInteractive(AcceptBase):
|
class TestInteractive:
|
||||||
def getspawn(self):
|
def getspawn(self, tmpdir):
|
||||||
pexpect = py.test.importorskip("pexpect")
|
pexpect = py.test.importorskip("pexpect")
|
||||||
def spawn(cmd):
|
def spawn(cmd):
|
||||||
return pexpect.spawn(cmd, logfile=self.tmpdir.join("spawn.out").open("w"))
|
return pexpect.spawn(cmd, logfile=tmpdir.join("spawn.out").open("w"))
|
||||||
return spawn
|
return spawn
|
||||||
|
|
||||||
def requirespexpect(self, version_needed):
|
def requirespexpect(self, version_needed):
|
||||||
|
|
@ -398,45 +340,43 @@ class TestInteractive(AcceptBase):
|
||||||
if ver < version_needed:
|
if ver < version_needed:
|
||||||
py.test.skip("pexpect version %s needed" %(".".join(map(str, version_needed))))
|
py.test.skip("pexpect version %s needed" %(".".join(map(str, version_needed))))
|
||||||
|
|
||||||
def test_pdb_interaction(self):
|
def test_pdb_interaction(self, testdir):
|
||||||
self.requirespexpect((2,3))
|
self.requirespexpect((2,3))
|
||||||
spawn = self.getspawn()
|
spawn = self.getspawn(testdir.tmpdir)
|
||||||
self.makepyfile(test_one="""
|
p1 = testdir.makepyfile("""
|
||||||
def test_1():
|
def test_1():
|
||||||
#hello
|
i = 0
|
||||||
assert 1 == 0
|
assert i == 1
|
||||||
""")
|
""")
|
||||||
|
|
||||||
child = spawn("%s %s --pdb test_one.py" % (py.std.sys.executable,
|
child = spawn("%s %s --pdb %s" % (py.std.sys.executable, pytestpath, p1))
|
||||||
pytestpath))
|
|
||||||
child.timeout = EXPECTTIMEOUT
|
child.timeout = EXPECTTIMEOUT
|
||||||
child.expect(".*def test_1.*")
|
#child.expect(".*def test_1.*")
|
||||||
child.expect(".*hello.*")
|
child.expect(".*i = 0.*")
|
||||||
child.expect("(Pdb)")
|
child.expect("(Pdb)")
|
||||||
child.sendeof()
|
child.sendeof()
|
||||||
child.expect("failures: 1")
|
child.expect("1 failed")
|
||||||
if child.isalive():
|
if child.isalive():
|
||||||
child.wait()
|
child.wait()
|
||||||
|
|
||||||
def test_simple_looponfailing_interaction(self):
|
def test_simple_looponfailing_interaction(self, testdir):
|
||||||
spawn = self.getspawn()
|
spawn = self.getspawn(testdir.tmpdir)
|
||||||
test_one = self.makepyfile(test_one="""
|
p1 = testdir.makepyfile("""
|
||||||
def test_1():
|
def test_1():
|
||||||
assert 1 == 0
|
assert 1 == 0
|
||||||
""")
|
""")
|
||||||
test_one.setmtime(test_one.mtime() - 5.0)
|
p1.setmtime(p1.mtime() - 50.0)
|
||||||
child = spawn("%s %s --looponfailing test_one.py" % (py.std.sys.executable,
|
child = spawn("%s %s --looponfailing %s" % (py.std.sys.executable, pytestpath, p1))
|
||||||
str(pytestpath)))
|
|
||||||
child.timeout = EXPECTTIMEOUT
|
child.timeout = EXPECTTIMEOUT
|
||||||
child.expect("assert 1 == 0")
|
child.expect("assert 1 == 0")
|
||||||
child.expect("test_one.py:")
|
child.expect("test_simple_looponfailing_interaction.py:")
|
||||||
child.expect("failures: 1")
|
child.expect("1 failed")
|
||||||
child.expect("waiting for changes")
|
child.expect("waiting for changes")
|
||||||
test_one.write(py.code.Source("""
|
p1.write(py.code.Source("""
|
||||||
def test_1():
|
def test_1():
|
||||||
assert 1 == 1
|
assert 1 == 1
|
||||||
"""))
|
"""))
|
||||||
child.expect("MODIFIED.*test_one.py", timeout=4.0)
|
child.expect("MODIFIED.*test_simple_looponfailing_interaction.py", timeout=4.0)
|
||||||
child.expect("failures: no failures", timeout=5.0)
|
child.expect("1 passed", timeout=5.0)
|
||||||
child.kill(15)
|
child.kill(15)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
pytest_plugins = "pytest_xfail", "pytest_pytester", "pytest_tmpdir"
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue