[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:
hpk 2009-02-27 11:18:27 +01:00
parent 1c85d7fe9a
commit c17a09adaf
117 changed files with 6079 additions and 4370 deletions

View File

@ -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()

View File

@ -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.

View File

@ -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

View File

@ -1,7 +1,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.execnet`_: ad-hoc code distribution to SSH, Socket and local sub processes
@ -26,8 +27,8 @@ version = "1.0.0a1"
initpkg(__name__,
description = "pylib and py.test: agile development and test support library",
revision = int('$LastChangedRevision: 58385 $'.split(':')[1][:-1]),
lastchangedate = '$LastChangedDate: 2008-09-23 16:28:13 +0200 (Tue, 23 Sep 2008) $',
revision = int('$LastChangedRevision: 62211 $'.split(':')[1][:-1]),
lastchangedate = '$LastChangedDate: 2009-02-27 11:18:27 +0100 (Fri, 27 Feb 2009) $',
version = version,
url = "http://pylib.org",
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",
long_description = globals()['__doc__'],
classifiers = [
"Development Status :: 4 - Beta",
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Operating System :: POSIX",
@ -53,6 +54,12 @@ initpkg(__name__,
# EXPORTED API
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
'cmdline.pytest' : ('./cmdline/pytest.py', 'main',),
'cmdline.pyrest' : ('./cmdline/pyrest.py', 'main',),
@ -64,7 +71,9 @@ initpkg(__name__,
# helpers for use from test functions or collectors
'test.__doc__' : ('./test/__init__.py', '__doc__'),
'test._PytestPlugins' : ('./test/pytestplugin.py', 'PytestPlugins'),
'test.raises' : ('./test/outcome.py', 'raises'),
'test.keywords' : ('./test/outcome.py', 'keywords',),
'test.deprecated_call' : ('./test/outcome.py', 'deprecated_call'),
'test.skip' : ('./test/outcome.py', 'skip'),
'test.importorskip' : ('./test/outcome.py', 'importorskip'),
@ -83,7 +92,6 @@ initpkg(__name__,
'test.collect.File' : ('./test/collect.py', 'File'),
'test.collect.Item' : ('./test/collect.py', 'Item'),
'test.collect.Module' : ('./test/pycollect.py', 'Module'),
'test.collect.DoctestFile' : ('./test/pycollect.py', 'DoctestFile'),
'test.collect.Class' : ('./test/pycollect.py', 'Class'),
'test.collect.Instance' : ('./test/pycollect.py', 'Instance'),
'test.collect.Generator' : ('./test/pycollect.py', 'Generator'),
@ -187,3 +195,7 @@ initpkg(__name__,
'compat.subprocess' : ('./compat/subprocess.py', '*'),
})
import py
py._com.pyplugins.consider_env()

150
py/_com.py Normal file
View File

@ -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()

View File

@ -8,7 +8,6 @@ from py.__.apigen import htmlgen
from py.__.apigen import linker
from py.__.apigen import project
from py.__.apigen.tracer.docstorage import pkg_to_dict
from py.__.doc.conftest import get_apigenpath
from layout import LayoutPage
@ -26,9 +25,9 @@ def get_documentable_items_pkgdir(pkgdir):
def get_documentable_items(pkgdir):
pkgname, pkgdict = get_documentable_items_pkgdir(pkgdir)
from py.__.execnet.channel import Channel
pkgdict['execnet.Channel'] = Channel
Channel.__apigen_hide_from_nav__ = True
#from py.__.execnet.channel import Channel
#pkgdict['execnet.Channel'] = Channel
#Channel.__apigen_hide_from_nav__ = True
return pkgname, pkgdict
def sourcedirfilter(p):
@ -36,7 +35,7 @@ def sourcedirfilter(p):
not p.basename.startswith('.') and
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
l = linker.TempLinker()
@ -44,8 +43,7 @@ def build(pkgdir, dsa, capture):
proj = project.Project()
# output dir
from py.__.conftest import option
targetdir = get_apigenpath()
targetdir = proj.apigenpath
targetdir.ensure(dir=True)
# find out what to build

View File

@ -1,10 +1,8 @@
import py
Option = py.test.config.Option
option = py.test.config.addoptions("apigen test options",
Option('', '--webcheck',
class ConftestPlugin:
def pytest_addoption(self, parser):
parser.addoption('--webcheck',
action="store_true", dest="webcheck", default=False,
help="run XHTML validation tests"
),
)
)

View File

@ -430,8 +430,9 @@ class ApiPageBuilder(AbstractPageBuilder):
relpath = get_rel_sourcepath(self.projroot, sourcefile, sourcefile)
text = 'source: %s' % (relpath,)
if is_in_pkg:
href = self.linker.get_lazyhref(sourcefile,
self.get_anchor(func))
#href = self.linker.get_lazyhref(sourcefile,
# self.get_anchor(func))
href = self.linker.get_lazyhref(sourcefile) #
csource = H.SourceSnippet(text, href, colored)
cslinks = self.build_callsites(dotted_name)
snippet = H.FunctionDescription(localname, argdesc, docstring,
@ -464,8 +465,8 @@ class ApiPageBuilder(AbstractPageBuilder):
if sourcefile[-1] in ['o', 'c']:
sourcefile = sourcefile[:-1]
sourcelink = H.div(H.a('view source',
href=self.linker.get_lazyhref(sourcefile,
self.get_anchor(cls))))
href=self.linker.get_lazyhref(sourcefile) #, self.get_anchor(cls)
))
snippet = H.ClassDescription(
# XXX bases HTML

View File

@ -6,7 +6,6 @@
import py
from py.__.doc import confrest
from py.__.apigen import linker
from py.__.doc.conftest import get_apigenpath, get_docpath
here = py.magic.autopath().dirpath()
@ -25,7 +24,7 @@ class LayoutPage(confrest.PyPage):
def get_relpath(self):
return linker.relpath(self.targetpath.strpath,
get_apigenpath().strpath) + '/'
self.project.apigenpath.strpath) + '/'
def set_content(self, contentel):
self.contentspace.append(contentel)

View File

@ -8,14 +8,18 @@
import py
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
this takes care of storing information on the first pass, and building
pages + indexes on the second
"""
def __init__(self):
def __init__(self, *args, **kwargs):
confrest.Project.__init__(self, *args, **kwargs)
self.content_items = {}
def add_item(self, path, content):

View File

@ -15,7 +15,6 @@ from py.__.apigen.tracer.permastore import PermaDocStorage
import pickle
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.transform import HTMLHandler
# XXX: UUuuuuuuuuuuuuuuuuuuuuuuu, dangerous import
@ -186,9 +185,11 @@ class TestRest(object):
py.test.skip('skipping rest generation because docutils is '
'not installed (this is a partial skip, the rest '
'of the test was successful)')
for path in tempdir.listdir('*.txt'):
for item, arg1, arg2, arg3 in genlinkchecks(path):
item(arg1, arg2, arg3)
py.test.skip("partial skip: find a nice way to re-use pytest_restdoc's genlinkchecks")
# XXX find a nice way check pytest_restdoc's genlinkchecks()
#for path in tempdir.listdir('*.txt'):
# for item, arg1, arg2, arg3 in genlinkchecks(path):
# item(arg1, arg2, arg3)
def test_generation_simple_api(self):
ds = self.get_filled_docstorage()

View File

@ -8,11 +8,8 @@ from py.__.apigen.tracer.tracer import Tracer
from py.__.apigen.layout import LayoutPage
from py.__.apigen.project import Project
from py.__.test.web import webcheck
from py.__.apigen.conftest import option
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):
currpos = -1
for s in seq:
@ -84,7 +81,7 @@ def _checkhtml(htmlstring):
if isinstance(htmlstring, unicode):
htmlstring = htmlstring.encode('UTF-8', 'replace')
assert isinstance(htmlstring, str)
if option.webcheck:
if py.test.config.option.webcheck:
webcheck.check_html(htmlstring)
else:
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)
funchtml = self.base.join('api/main.SomeClass.html').read()
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)
def test_build_namespace_pages(self):

View File

@ -50,7 +50,7 @@ if __name__ == '__main__':
if apigendir.check():
print apigendir, "exists, not re-generating - remove to trigger regeneration"
else:
sysexec('%(env)s %(pytest)s --apigen=%(pypath)s/apigen/apigen.py py' % locals())
sysexec('%(env)s %(pytest)s py' % locals())
print
print "*" * 30, "static generation", "*" * 30
sysexec('%(env)s %(pytest)s --forcegen %(pypath)s/doc' % locals())

View File

@ -1,18 +1,18 @@
from py.__.test.testing import suptest
from py.__.test.testing.acceptance_test import AcceptBase
class TestPyLookup(AcceptBase):
def test_basic(self):
p = self.makepyfile(hello="def x(): pass")
result = self.runpybin("py.lookup", "pass")
suptest.assert_lines_contain_lines(result.outlines,
pytest_plugins = "pytest_pytester"
class TestPyLookup:
def test_basic(self, testdir):
p = testdir.makepyfile(hello="def x(): pass")
result = testdir.runpybin("py.lookup", "pass")
result.stdout.fnmatch_lines(
['%s:*def x(): pass' %(p.basename)]
)
def test_search_in_filename(self):
p = self.makepyfile(hello="def x(): pass")
result = self.runpybin("py.lookup", "hello")
suptest.assert_lines_contain_lines(result.outlines,
def test_search_in_filename(self, testdir):
p = testdir.makepyfile(hello="def x(): pass")
result = testdir.runpybin("py.lookup", "hello")
result.stdout.fnmatch_lines(
['*%s:*' %(p.basename)]
)

View File

@ -1,41 +1,18 @@
#pythonexecutables = ('python2.2', 'python2.3',)
#pythonexecutable = 'python2.2'
dist_rsync_roots = ['.'] # XXX
# in the future we want to be able to say here:
#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
pytest_plugins = 'pytest_doctest', 'pytest_pytester', 'pytest_restdoc'
import py
Option = py.test.config.Option
option = py.test.config.addoptions("execnet options",
Option('-S', '',
action="store", dest="sshtarget", default=None,
class PylibTestPlugin:
def pytest_addoption(self, parser):
group = parser.addgroup("pylib", "py lib testing options")
group.addoption('--sshhost',
action="store", dest="sshhost", default=None,
help=("target to run tests requiring ssh, e.g. "
"user@codespeak.net")),
Option('', '--apigenpath',
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',
"user@codespeak.net"))
group.addoption('--runslowtests',
action="store_true", dest="runslowtests", default=False,
help="run slow tests"),
)
help="run slow tests")
ConftestPlugin = PylibTestPlugin
dist_rsync_roots = ['.']

View File

@ -1,7 +1,6 @@
import py
from py.__.misc.rest import convert_rest_html, strip_html_header
from py.__.misc.difftime import worded_time
from py.__.doc.conftest import get_apigenpath, get_docpath
from py.__.apigen.linker import relpath
html = py.xml.html
@ -25,13 +24,13 @@ class Page(object):
self.fill()
def a_docref(self, name, relhtmlpath):
docpath = self.project.get_docpath()
docpath = self.project.docpath
return html.a(name, class_="menu",
href=relpath(self.targetpath.strpath,
docpath.join(relhtmlpath).strpath))
def a_apigenref(self, name, relhtmlpath):
apipath = get_apigenpath()
apipath = self.project.apigenpath
return html.a(name, class_="menu",
href=relpath(self.targetpath.strpath,
apipath.join(relhtmlpath).strpath))
@ -107,8 +106,6 @@ def getrealname(username):
class Project:
mydir = py.magic.autopath().dirpath()
# string for url, path for local file
stylesheet = mydir.join('style.css')
title = "py lib"
prefix_title = "" # we have a logo already containing "py lib"
encoding = 'latin1'
@ -119,31 +116,53 @@ class Project:
href="http://codespeak.net"))
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):
return unicode(txtpath.read(), encoding)
def get_docpath(self):
return get_docpath()
def get_htmloutputpath(self, txtpath):
docpath = self.get_docpath()
reloutputpath = txtpath.new(ext='.html').relto(self.mydir)
return docpath.join(reloutputpath)
reloutputpath = txtpath.new(ext='.html').relto(self.sourcepath)
return self.docpath.join(reloutputpath)
def process(self, txtpath):
encoding = self.encoding
content = self.get_content(txtpath, encoding)
docpath = self.get_docpath()
outputpath = self.get_htmloutputpath(txtpath)
stylesheet = self.stylesheet
if isinstance(self.stylesheet, py.path.local):
if not docpath.join(stylesheet.basename).check():
if isinstance(stylesheet, py.path.local):
if not self.docpath.join(stylesheet.basename).check():
docpath.ensure(dir=True)
stylesheet.copy(docpath)
stylesheet = relpath(outputpath.strpath,
docpath.join(stylesheet.basename).strpath)
self.docpath.join(stylesheet.basename).strpath)
content = convert_rest_html(content, txtpath,
stylesheet=stylesheet, encoding=encoding)

View File

@ -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

View File

@ -16,8 +16,6 @@ py lib contact and communication
.. _`merlinux.eu`: http://merlinux.eu
.. _`zope3`: http://zope3.zwiki.org/
.. _twisted: http://www.twistedmatrix.org
.. _future: future.html
.. _`get an account`:

View File

@ -2,14 +2,13 @@
import py
failure_demo = py.magic.autopath().dirpath('failure_demo.py')
from py.__.test.testing import suptest
from py.__.test import event
pytest_plugins = "pytest_pytester"
def test_failure_demo_fails_properly():
sorter = suptest.events_from_cmdline([failure_demo])
def test_failure_demo_fails_properly(testdir):
sorter = testdir.inline_run(failure_demo)
passed, skipped, failed = sorter.countoutcomes()
assert passed == 0
assert failed == 20, failed
colreports = sorter.get(event.CollectionReport)
colreports = sorter.getnamed("collectionreport")
failed = len([x.failed for x in colreports])
assert failed == 5

View File

@ -105,10 +105,7 @@ be directly usable for such linux-filesystem glue code.
In other words, implementing a `memoryfs`_ or a `dictfs`_ would
give you two things for free: a filesystem mountable at kernel level
as well as a uniform "path" object allowing you to access your
filesystem in convenient ways. (At some point it might
even become interesting to think about interfacing to
`reiserfs v4 features`_ at the Filesystem level but that
is a can of subsequent worms).
filesystem in convenient ways.
Also interesting to check out is Will McGugan's work on
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
.. _`pylufs`: http://codespeak.net/svn/user/arigo/hack/pylufs/
.. _`pyfuse`: http://codespeak.net/svn/user/arigo/hack/pyfuse/
.. _`reiserfs v4 features`: http://www.namesys.com/v4/v4.html
Integrate interactive completion

View File

@ -270,3 +270,5 @@ Customizing execution of Functions
function with the given (usually empty set of) arguments.
.. _`py-dev mailing list`: http://codespeak.net/mailman/listinfo/py-dev

View File

@ -52,7 +52,7 @@ solutions.
Some example usage of :api:`py.path.svnurl`::
.. >>> 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')
>>> info = url.info()
>>> info.kind
@ -64,7 +64,7 @@ Some example usage of :api:`py.path.svnurl`::
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')
>>> wc = py.path.svnwc(temp.join('svnwc'))
>>> 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
>>> p2.relto(p1).replace(sep, '/') # os-specific path sep in the string
'baz/qux'
>>> p2.bestrelpath(p1)
'../..'
>>> p2.join(p2.bestrelpath(p1)) == p1
True
>>> p3 = p1 / 'baz/qux' # the / operator allows joining, too
>>> p2 == p3
True
@ -178,7 +182,7 @@ Setting svn-properties
As an example of 'uncommon' methods, we'll show how to read and write
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.propset('foo', 'bar')
@ -196,7 +200,7 @@ SVN authentication
Some uncommon functionality can also be provided as extensions, such as SVN
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,
... interactive=False)
>>> wc.auth = auth

67
py/doc/pytest-plugins.txt Normal file
View File

@ -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

View File

@ -142,23 +142,18 @@ To make it easier to distinguish the generated tests it is possible to specify a
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
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
and where the word "test_simple" equals the start of one part of the
path leading up to the test item. Directory and file basenames as well
as function, class and function/method names each form a possibly
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
will run all tests matching (or not matching) the
"test_simple" keyword. Note that you need to quote
the keyword if "-" is recognized as an indicator
for a commandline option. Lastly, you may use
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"
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
---------------------------------------------------

View File

@ -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')")

View File

@ -168,5 +168,4 @@ complete the probably request-specific serialization of
your Tags. Hum, it's probably harder to explain this than to
actually code it :-)
.. _Nevow: http://www.divmod.org/projects/nevow
.. _`py.test`: test.html

View File

@ -2,7 +2,6 @@ from __future__ import generators
import os, sys, time, signal
import py
from py.__.execnet import gateway
from py.__.conftest import option
mypath = py.magic.autopath()
from StringIO import StringIO
@ -247,7 +246,10 @@ class BasicRemoteExecution:
channel.waitclose(TESTTIMEOUT)
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.
l = []
channel = self.gw.remote_exec(source='''
@ -278,7 +280,7 @@ class BasicRemoteExecution:
return subchannel
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()
def test_channel_endmarker_callback(self):
@ -568,16 +570,16 @@ class TestSocketGateway(SocketGatewaySetup, BasicRemoteExecution):
class TestSshGateway(BasicRemoteExecution):
def setup_class(cls):
if option.sshtarget is None:
py.test.skip("no known ssh target, use -S to set one")
cls.gw = py.execnet.SshGateway(option.sshtarget)
if py.test.config.option.sshhost is None:
py.test.skip("no known ssh target, use --sshhost to set one")
cls.gw = py.execnet.SshGateway(py.test.config.option.sshhost)
def test_sshconfig_functional(self):
tmpdir = py.test.ensuretemp("test_sshconfig")
ssh_config = tmpdir.join("ssh_config")
ssh_config.write(
"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)
assert gw._cmd.find("-F") != -1
assert gw._cmd.find(str(ssh_config)) != -1
@ -585,7 +587,7 @@ class TestSshGateway(BasicRemoteExecution):
gw.exit()
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):
py.test.raises(IOError,

View File

@ -64,12 +64,6 @@ class Package(object):
def _resolve(self, extpyish):
""" resolve a combined filesystem/python extpy-ish path. """
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('./'), \
"%r is not an implementation path (XXX)" % (extpyish,)
implmodule = self._loadimpl(fspath[:-3])
@ -166,15 +160,18 @@ def setmodule(modpath, module):
sys.modules[modpath] = module
# ---------------------------------------------------
# Virtual Module Object
# API Module Object
# ---------------------------------------------------
class Module(ModuleType):
class ApiModule(ModuleType):
def __init__(self, pkg, name):
self.__pkg__ = pkg
self.__name__ = name
self.__map__ = {}
def __repr__(self):
return '<ApiModule %r>' % (self.__name__,)
def __getattr__(self, name):
if '*' in self.__map__:
extpy = self.__map__['*'][0], name
@ -209,9 +206,6 @@ class Module(ModuleType):
except (AttributeError, TypeError):
pass
def __repr__(self):
return '<Module %r>' % (self.__name__, )
def getdict(self):
# force all the content of the module to be loaded when __dict__ is read
dictdescr = ModuleType.__dict__['__dict__']
@ -254,7 +248,7 @@ def initpkg(pkgname, exportdefs, **kw):
previous = current
current += '.' + name
if current not in seen:
seen[current] = mod = Module(pkg, current)
seen[current] = mod = ApiModule(pkg, current)
setattr(seen[previous], name, mod)
setmodule(current, mod)
@ -272,3 +266,12 @@ def initpkg(pkgname, exportdefs, **kw):
for mod, pypart, extpy in deferred_imports:
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)

View File

@ -71,3 +71,11 @@ def strip_html_header(string, encoding='utf8'):
break
uni = match.group(1)
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')

View File

@ -52,7 +52,7 @@ class TestBuildcostAccess(BasicCacheAPITest):
class TestAging(BasicCacheAPITest):
maxsecs = 0.02
maxsecs = 0.10
cache = AgingCache(maxentries=128, maxseconds=maxsecs)
def test_cache_eviction(self):

233
py/misc/testing/test_com.py Normal file
View File

@ -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}]

View File

@ -12,20 +12,20 @@ def checksubpackage(name):
assert getattr(obj, '__map__') == {}
def test_dir():
from py.__.initpkg import Module
from py.__.initpkg import ApiModule
for name in dir(py):
if name == 'magic': # greenlets don't work everywhere, we don't care here
continue
if not name.startswith('_'):
yield checksubpackage, name
from py.initpkg import Module
from py.initpkg import ApiModule
glob = []
class MyModule(Module):
class MyModule(ApiModule):
def __init__(self, *args):
glob.append(self.__dict__)
assert isinstance(glob[-1], (dict, type(None)))
Module.__init__(self, *args)
ApiModule.__init__(self, *args)
def test_early__dict__access():
mymod = MyModule("whatever", "myname")
@ -68,7 +68,10 @@ def test_importall():
base.join('execnet', 'script'),
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':
continue
relpath = p.new(ext='').relto(base)
@ -255,3 +258,8 @@ class TestRealModule:
# help(std.path)
# #assert False
def test_autoimport():
from py.initpkg import autoimport
py.std.os.environ['AUTOTEST_AUTOIMPORT'] = "nonexistmodule"
py.test.raises(ImportError, "autoimport('autotest')")

View File

@ -1,20 +1,20 @@
import py
from py.__.misc.warn import WarningBus
from py.__.misc.warn import WarningPlugin
mypath = py.magic.autopath()
class TestWarningBus:
class TestWarningPlugin:
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.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")
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):
self.wb.warn("again")
@ -27,19 +27,16 @@ class TestWarningBus:
assert str(warning) == warning.msg
def test_stacklevel(self):
l = []
self.wb.subscribe(l.append)
def f():
self.wb.warn("x", stacklevel=2)
# 5
# 6
# 3
# 4
f()
lno = self.test_stacklevel.im_func.func_code.co_firstlineno + 7
warning = l[0]
lno = self.test_stacklevel.im_func.func_code.co_firstlineno + 5
warning = self.warnings[0]
assert warning.lineno == lno
def test_forwarding_to_warnings_module(self):
self.wb._setforwarding()
py.test.deprecated_call(self.wb.warn, "x")
def test_apiwarn(self):
@ -47,7 +44,6 @@ class TestWarningBus:
warning = self.warnings[0]
assert warning.msg == "xxx (since version 3.0)"
def test_APIWARN():
def test_default():
from py.__.misc.warn import APIWARN
wb = APIWARN.im_self
assert wb._forward in wb._eventbus._subscribers
assert py._com.pyplugins.isregistered(APIWARN.im_self)

View File

@ -1,5 +1,4 @@
import py, sys
from py.__.test.event import EventBus
class Warning(py.std.exceptions.DeprecationWarning):
def __init__(self, msg, path, lineno):
@ -11,19 +10,16 @@ class Warning(py.std.exceptions.DeprecationWarning):
def __str__(self):
return self.msg
class WarningBus(object):
def __init__(self):
self._eventbus = EventBus()
# XXX probably only apiwarn() + py._com.pyplugins forwarding
# warn_explicit is actually needed
def subscribe(self, callable):
self._eventbus.subscribe(callable)
def unsubscribe(self, callable):
self._eventbus.unsubscribe(callable)
def _setforwarding(self):
self._eventbus.subscribe(self._forward)
def _forward(self, warning):
class WarningPlugin(object):
def __init__(self, bus):
self.bus = bus
bus.register(self)
def pyevent_WARNING(self, warning):
# forward to python warning system
py.std.warnings.warn_explicit(warning, category=Warning,
filename=str(warning.path),
lineno=warning.lineno,
@ -66,9 +62,8 @@ class WarningBus(object):
filename = module
path = py.path.local(filename)
warning = Warning(msg, path, lineno)
self._eventbus.notify(warning)
self.bus.notify("WARNING", warning)
# singleton api warner for py lib
apiwarner = WarningBus()
apiwarner._setforwarding()
apiwarner = WarningPlugin(py._com.pyplugins)
APIWARN = apiwarner.apiwarn

View File

@ -152,6 +152,30 @@ class PathBase(object):
return strself[len(strrelpath):]
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):
""" return a root-first list of all ancestor directories
plus the path itself.

View File

@ -3,7 +3,6 @@ from py.path import SvnAuth
import svntestbase
from threading import Thread
import time
from py.__.conftest import option
def make_repo_auth(repo, userdata):
""" write config to repo
@ -251,7 +250,7 @@ class TestSvnURLAuth(object):
class SvnAuthFunctionalTestBase(object):
def setup_class(cls):
svntestbase.getsvnbin()
if not option.runslowtests:
if not py.test.config.option.runslowtests:
py.test.skip('skipping slow functional tests - use --runslowtests '
'to override')

View File

@ -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 parse_wcinfotime
from py.__.path.svn import svncommon
from py.__.conftest import option
if sys.platform != 'win32':
def normpath(p):
@ -157,7 +156,7 @@ class TestWCSvnCommandPath(CommonSvnTests):
self.root.revert(rec=1)
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 '
'to override')
wc = self.root
@ -177,7 +176,7 @@ class TestWCSvnCommandPath(CommonSvnTests):
assert [x.basename for x in s.conflict] == ['conflictsamplefile']
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 '
'to override')
otherrepo, otherwc = getrepowc('externalrepo', 'externalwc')

View File

@ -120,6 +120,18 @@ class CommonPathTests(object):
assert self.root.check(notrelto=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):
l1=self.root.join("bcde")
l2=self.root.join("b")

View File

@ -3,7 +3,7 @@
"""
from py.__.rest.rst import *
from py.__.doc.conftest import restcheck
from py.__.misc.rest import process as restcheck
import traceback
tempdir = py.test.ensuretemp('rest')

View File

@ -9,9 +9,11 @@ def main(args=None):
if args is None:
args = py.std.sys.argv[1:]
config = py.test.config
config.parse(args)
config.parse(args)
config.pytestplugins.configure(config)
session = config.initsession()
exitstatus = session.main()
config.pytestplugins.unconfigure(config)
raise SystemExit(exitstatus)
def warn_about_missing_assertion():

View File

@ -53,7 +53,6 @@ class SetupState(object):
col = self.stack.pop()
col.teardown()
for col in needed_collectors[len(self.stack):]:
#print "setting up", col
col.setup()
self.stack.append(col)
@ -67,7 +66,7 @@ class ReprMetaInfo(object):
params = self.__dict__.copy()
if self.fspath:
if basedir is not None:
params['fspath'] = getrelpath(basedir, self.fspath)
params['fspath'] = basedir.bestrelpath(self.fspath)
if self.lineno is not None:
params['lineno'] = self.lineno + 1
@ -108,6 +107,7 @@ class Node(object):
self._config = config
self.fspath = getattr(parent, 'fspath', None)
#
# note to myself: Pickling is uh.
#
@ -172,15 +172,17 @@ class Node(object):
setattr(self, attrname, res)
return res
def listchain(self):
""" return list of all parent collectors up to self. """
def listchain(self, rootfirst=False):
""" return list of all parent collectors up to self,
starting form root of collection tree. """
l = [self]
while 1:
x = l[-1]
if x.parent is not None:
l.append(x.parent)
else:
l.reverse()
if not rootfirst:
l.reverse()
return l
def listnames(self):
@ -200,6 +202,53 @@ class Node(object):
cur = next
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):
return [self.name]
@ -284,7 +333,6 @@ class Node(object):
return repr
repr_failure = _repr_failure_py
shortfailurerepr = "F"
class Collector(Node):
@ -298,7 +346,7 @@ class Collector(Node):
"""
Directory = configproperty('Directory')
Module = configproperty('Module')
DoctestFile = configproperty('DoctestFile')
#DoctestFile = configproperty('DoctestFile')
def collect(self):
""" returns a list of children (items and collectors)
@ -407,41 +455,45 @@ class Directory(FSCollector):
return l
l = []
for path in self.fspath.listdir(sort=True):
res = self.consider(path, usefilters=True)
res = self.consider(path)
if res is not None:
l.append(res)
if isinstance(res, (list, tuple)):
l.extend(res)
else:
l.append(res)
return l
def consider(self, path, usefilters=True):
def consider(self, path):
if path.check(file=1):
return self.consider_file(path, usefilters=usefilters)
return self.consider_file(path)
elif path.check(dir=1):
return self.consider_dir(path, usefilters=usefilters)
return self.consider_dir(path)
def consider_file(self, path, usefilters=True):
ext = path.ext
pb = path.purebasename
if not usefilters or pb.startswith("test_") or pb.endswith("_test"):
if ext == ".py":
return self.Module(path, parent=self)
elif ext == ".txt":
return self.DoctestFile(path, parent=self)
def consider_file(self, path):
res = self._config.pytestplugins.call_each(
'pytest_collect_file', path=path, parent=self)
l = []
# throw out identical modules
for x in res:
if x not in l:
l.append(x)
return l
def consider_dir(self, path, usefilters=True):
if not usefilters or self.recfilter(path):
# not use self.Directory here as
# dir/conftest.py shall be able to
# define Directory(dir) already
Directory = self._config.getvalue('Directory', path)
return Directory(path, parent=self)
def collect_by_name(self, name):
""" get a child with the given name. """
res = super(Directory, self).collect_by_name(name)
if res is None:
p = self.fspath.join(name)
res = self.consider(p, usefilters=False)
return res
def consider_dir(self, path, usefilters=None):
if usefilters is not None:
APIWARN("0.99", "usefilters argument not needed")
if not self.recfilter(path):
# check if cmdline specified this dir or a subdir
for arg in self._config.args:
if path == arg or arg.relto(path):
break
else:
return
# not use self.Directory here as
# dir/conftest.py shall be able to
# define Directory(dir) already
Directory = self._config.getvalue('Directory', path)
return Directory(path, parent=self)
from py.__.test.runner import basic_run_report, forked_run_report
class Item(Node):
@ -479,27 +531,6 @@ class Item(Node):
def runtest(self):
""" 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():
APIWARN("1.0",
"implement collector.collect() instead of "

View File

@ -2,9 +2,9 @@ from __future__ import generators
import py
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
basetemp = None
@ -26,14 +26,26 @@ class CmdOptions(object):
class Config(object):
""" central bus for dealing with configuration/initialization data. """
Option = optparse.Option
Option = py.compat.optparse.Option # deprecated
_initialized = False
def __init__(self):
def __init__(self, pytestplugins=None):
self.option = CmdOptions()
self._parser = optparse.OptionParser(
usage="usage: %prog [options] [query] [filenames of tests]")
self._conftest = Conftest()
self._parser = parseopt.Parser(
usage="usage: %prog [options] [file_or_dir] [file_or_dir] [...]",
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):
""" parse cmdline arguments into this config object.
@ -42,15 +54,14 @@ class Config(object):
assert not self._initialized, (
"can only parse cmdline args at most once per Config object")
self._initialized = True
adddefaultoptions(self)
self._conftest.setinitial(args)
args = [str(x) for x in args]
cmdlineoption, args = self._parser.parse_args(args)
self.option.__dict__.update(vars(cmdlineoption))
self.pytestplugins.consider_env()
self.pytestplugins.do_addoption(self._parser)
args = self._parser.parse_setoption(args, self.option)
if not args:
args.append(py.std.os.getcwd())
self.topdir = gettopdir(args)
self.args = args
self.args = [py.path.local(x) for x in args]
# config objects are usually pickled across system
# barriers but they contain filesystem paths.
@ -62,11 +73,15 @@ class Config(object):
self._repr = repr
def _initafterpickle(self, topdir):
self.__init__()
self.__init__(
#issue1
#pytestplugins=py.test._PytestPlugins(py._com.pyplugins)
)
self._initialized = True
self.topdir = py.path.local(topdir)
self._mergerepr(self._repr)
del self._repr
self.pytestplugins.configure(config=self)
def _makerepr(self):
l = []
@ -87,6 +102,9 @@ class Config(object):
self.option = cmdlineopts
self._conftest.setinitial(self.args)
def getcolitems(self):
return [self.getfsnode(arg) for arg in self.args]
def getfsnode(self, path):
path = py.path.local(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
Dir = self._conftest.rget("Directory", pkgpath)
col = Dir(pkgpath, config=self)
names = path.relto(col.fspath).split(path.sep)
return col._getitembynames(names)
return col._getfsnode(path)
def getvalue_pathlist(self, name, path=None):
""" return a matching value, which needs to be sequence
@ -119,24 +136,14 @@ class Config(object):
""" add a named group of options to the current testing session.
This function gets invoked during testing session initialization.
"""
for spec in specs:
for shortopt in spec._short_opts:
if not shortopt.isupper():
raise ValueError(
"custom options must be capital letter "
"got %r" %(spec,)
)
return self._addoptions(groupname, *specs)
APIWARN("1.0", "define plugins to add options", stacklevel=2)
group = self._parser.addgroup(groupname)
for opt in specs:
group._addoption_instance(opt)
return self.option
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:
if hasattr(opt, 'default') and opt.dest:
if not hasattr(self.option, opt.dest):
setattr(self.option, opt.dest, opt.default)
return self.option
def addoption(self, *optnames, **attrs):
return self._parser.addoption(*optnames, **attrs)
def getvalue(self, name, path=None):
""" return 'name' value looked up from the 'options'
@ -150,30 +157,21 @@ class Config(object):
except AttributeError:
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):
""" return an initialized session object. """
cls = self._getsessionclass()
cls = self._getestdirclass()
session = cls(self)
session.fixoptions()
session.reporter = self.initreporter(session.bus)
return session
def _getsessionclass(self):
def _getestdirclass(self):
""" return Session class determined from cmdline options
and looked up in initial config modules.
"""
if self.option.session is not None:
return self._conftest.rget(self.option.session)
else:
name = self._getsessionname()
name = self._getestdirname()
try:
return self._conftest.rget(name)
except KeyError:
@ -182,7 +180,7 @@ class Config(object):
mod = __import__(importpath, None, None, '__doc__')
return getattr(mod, name)
def _getsessionname(self):
def _getestdirname(self):
""" return default session name as determined from options. """
if self.option.collectonly:
name = 'Session'
@ -221,42 +219,10 @@ class Config(object):
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
config_per_process = Config()
config_per_process = Config(
pytestplugins=py.test._PytestPlugins(py._com.pyplugins)
)
# default import paths for sessions

View File

@ -9,8 +9,9 @@ class Conftest(object):
conftest.py files may result in added cmdline options.
XXX
"""
def __init__(self, path=None):
def __init__(self, path=None, onimport=None):
self._path2confmods = {}
self._onimport = onimport
if path is not None:
self.setinitial([path])
@ -37,11 +38,11 @@ class Conftest(object):
except KeyError:
dp = path.dirpath()
if dp == path:
return [importconfig(defaultconftestpath)]
return [self.importconftest(defaultconftestpath)]
clist = self.getconftestmodules(dp)
conftestpath = path.join("conftest.py")
if conftestpath.check(file=1):
clist.append(importconfig(conftestpath))
clist.append(self.importconftest(conftestpath))
self._path2confmods[path] = clist
# be defensive: avoid changes from caller side to
# affect us by always returning a copy of the actual list
@ -61,15 +62,17 @@ class Conftest(object):
continue
raise KeyError, name
def importconfig(configpath):
# We could have used caching here, but it's redundant since
# they're cached on path anyway, so we use it only when doing rget_path
assert configpath.check(), configpath
if not configpath.dirpath('__init__.py').check(file=1):
# HACK: we don't want any "globally" imported conftest.py,
# prone to conflicts and subtle problems
modname = str(configpath).replace('.', configpath.sep)
mod = configpath.pyimport(modname=modname)
else:
mod = configpath.pyimport()
return mod
def importconftest(self, conftestpath):
# Using caching here looks redundant since ultimately
# sys.modules caches already
assert conftestpath.check(), conftestpath
if not conftestpath.dirpath('__init__.py').check(file=1):
# HACK: we don't want any "globally" imported conftest.py,
# prone to conflicts and subtle problems
modname = str(conftestpath).replace('.', conftestpath.sep)
mod = conftestpath.pyimport(modname=modname)
else:
mod = conftestpath.pyimport()
if self._onimport:
self._onimport(mod)
return mod

View File

@ -1,7 +1,7 @@
import py
Module = py.test.collect.Module
DoctestFile = py.test.collect.DoctestFile
#DoctestFile = py.test.collect.DoctestFile
Directory = py.test.collect.Directory
Class = py.test.collect.Class
Generator = py.test.collect.Generator
@ -10,13 +10,16 @@ Instance = py.test.collect.Instance
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
#dist_hosts: needs to be provided by user
#dist_rsync_roots: might be provided by user, if not present or None,
# whole pkgdir will be rsynced
# XXX deprecated dist_remotepython = None
dist_taskspernode = 15
dist_boxed = False
if hasattr(py.std.os, 'nice'):
@ -25,97 +28,3 @@ else:
dist_nicelevel = 0
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")
)

View File

@ -8,11 +8,10 @@ import py
from py.__.test import event
import py.__.test.custompdb
from py.__.test.dsession.hostmanage import HostManager
Item = (py.test.collect.Item, py.test.collect.Item)
Collector = (py.test.collect.Collector, py.test.collect.Collector)
Item = py.test.collect.Item
Collector = py.test.collect.Collector
from py.__.test.runner import basic_run_report, basic_collect_report
from py.__.test.session import Session
from py.__.test.runner import OutcomeRepr
from py.__.test import outcome
import Queue
@ -42,7 +41,6 @@ class DSession(Session):
self.host2pending = {}
self.item2host = {}
self._testsfailed = False
self.trace = config.maketrace("dsession.log")
def fixoptions(self):
""" check, fix and determine conflicting options. """
@ -79,11 +77,13 @@ class DSession(Session):
def main(self, colitems=None):
colitems = self.getinitialitems(colitems)
self.bus.notify(event.TestrunStart())
#self.bus.notify(event.TestrunStart())
self.sessionstarts()
self.setup_hosts()
exitstatus = self.loop(colitems)
self.bus.notify(event.TestrunFinish(exitstatus=exitstatus))
#self.bus.notify(event.TestrunFinish(exitstatus=exitstatus))
self.teardown_hosts()
self.sessionfinishes()
return exitstatus
def loop_once(self, loopstate):
@ -96,28 +96,34 @@ class DSession(Session):
# we use a timeout here so that control-C gets through
while 1:
try:
ev = self.queue.get(timeout=2.0)
eventcall = self.queue.get(timeout=2.0)
break
except Queue.Empty:
continue
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)
if ev.failed:
loopstate.testsfailed = True
elif isinstance(ev, event.CollectionReport):
elif eventname == "collectionreport":
if ev.passed:
colitems.extend(ev.result)
elif isinstance(ev, event.HostUp):
elif eventname == "hostup":
self.addhost(ev.host)
elif isinstance(ev, event.HostDown):
elif eventname == "hostdown":
pending = self.removehost(ev.host)
if pending:
crashitem = pending.pop(0)
self.handle_crashitem(crashitem, ev.host)
colitems.extend(pending)
elif isinstance(ev, event.RescheduleItems):
elif eventname == "rescheduleitems":
colitems.extend(ev.items)
loopstate.dowork = False # avoid busywait
@ -132,9 +138,10 @@ class DSession(Session):
def loop_once_shutdown(self, loopstate):
# once we are in shutdown mode we dont send
# events other than HostDown upstream
ev = self.queue.get()
if isinstance(ev, event.HostDown):
self.bus.notify(ev)
eventname, args, kwargs = self.queue.get()
if eventname == "hostdown":
ev, = args
self.bus.notify("hostdown", ev)
self.removehost(ev.host)
if not self.host2pending:
# finished
@ -155,7 +162,7 @@ class DSession(Session):
except KeyboardInterrupt:
exitstatus = outcome.EXIT_INTERRUPTED
except:
self.bus.notify(event.InternalException())
self.bus.notify("internalerror", event.InternalException())
exitstatus = outcome.EXIT_INTERNALERROR
if exitstatus == 0 and self._testsfailed:
exitstatus = outcome.EXIT_TESTSFAILED
@ -173,7 +180,6 @@ class DSession(Session):
pending = self.host2pending.pop(host)
for item in pending:
del self.item2host[item]
self.trace("removehost %s, pending=%r" %(host.hostid, pending))
return pending
def triggertesting(self, colitems):
@ -183,11 +189,13 @@ class DSession(Session):
if isinstance(next, Item):
senditems.append(next)
else:
ev = basic_collect_report(next)
self.bus.notify(event.CollectionStart(next))
self.queue.put(ev)
self.bus.notify("collectionstart", event.CollectionStart(next))
self.queueevent("collectionreport", basic_collect_report(next))
self.senditems(senditems)
def queueevent(self, eventname, *args, **kwargs):
self.queue.put((eventname, args, kwargs))
def senditems(self, tosend):
if not tosend:
return
@ -196,38 +204,36 @@ class DSession(Session):
if room > 0:
sending = tosend[:room]
host.node.sendlist(sending)
self.trace("sent to host %s: %r" %(host.hostid, sending))
for item in sending:
#assert item not in self.item2host, (
# "sending same item %r to multiple hosts "
# "not implemented" %(item,))
self.item2host[item] = host
self.bus.notify(event.ItemStart(item, host))
self.bus.notify("itemstart", event.ItemStart(item, host))
pending.extend(sending)
tosend[:] = tosend[room:] # update inplace
if not tosend:
break
if tosend:
# 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):
if item not in self.item2host:
raise AssertionError(item, self.item2host)
host = self.item2host.pop(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):
longrepr = "%r CRASHED THE HOST %r" %(item, host)
outcome = OutcomeRepr(when="execute", shortrepr="c", longrepr=longrepr)
rep = event.ItemTestReport(item, failed=outcome)
self.bus.notify(rep)
longrepr = "!!! Host %r crashed during running of test %r" %(host, item)
rep = event.ItemTestReport(item, when="???", excinfo=longrepr)
self.bus.notify("itemtestreport", rep)
def setup_hosts(self):
""" setup any neccessary resources ahead of the test run. """
self.hm = HostManager(self)
self.hm.setup_hosts(notify=self.queue.put)
self.hm.setup_hosts(putevent=self.queue.put)
def teardown_hosts(self):
""" teardown any resources after a test run. """

View File

@ -148,7 +148,7 @@ class HostManager(object):
def prepare_gateways(self):
for host in self.hosts:
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):
self.prepare_gateways()
@ -164,16 +164,14 @@ class HostManager(object):
for host in self.hosts:
rsync.add_target_host(host, destrelpath)
rsync.send(raises=False)
self.session.bus.notify(event.RsyncFinished())
self.session.bus.notify("rsyncfinished", event.RsyncFinished())
def setup_hosts(self, notify=None):
if notify is None:
notify = self.session.bus.notify
def setup_hosts(self, putevent):
self.init_rsync()
for host in self.hosts:
host.node = MasterNode(host,
self.session.config,
notify)
putevent)
#
# helpers

View File

@ -8,16 +8,19 @@ from py.__.test.dsession.mypickle import PickleChannel
class MasterNode(object):
ENDMARK = -1
def __init__(self, host, config, notify):
def __init__(self, host, config, putevent):
self.host = host
self.config = config
self.notify = notify
self.putevent = putevent
self.channel = install_slave(host, config)
self.channel.setcallback(self.callback, endmarker=self.ENDMARK)
self._down = False
def notify(self, eventname, *args, **kwargs):
self.putevent((eventname, args, kwargs))
def callback(self, ev):
""" this gets called for each item we receive from
def callback(self, eventcall):
""" this gets called for each object we receive from
the other side and if the channel closes.
Note that the callback runs in the receiver
@ -25,24 +28,27 @@ class MasterNode(object):
avoid raising exceptions or doing heavy work.
"""
try:
if ev == self.ENDMARK:
if eventcall == self.ENDMARK:
err = self.channel._getremoteerror()
if not self._down:
if not err:
err = "TERMINATED"
self.notify(event.HostDown(self.host, err))
self.notify("hostdown", event.HostDown(self.host, err))
return
if ev is None:
elif eventcall is None:
self._down = True
self.notify(event.HostDown(self.host, None))
self.notify("hostdown", event.HostDown(self.host, None))
return
except KeyboardInterrupt:
raise
except:
excinfo = py.code.ExceptionInfo()
print "!" * 20, excinfo
ev = event.InternalException(excinfo)
self.notify(ev)
self.notify("internalerror", event.InternalException(excinfo))
else:
# XXX we need to have the proper event name
eventname, args, kwargs = eventcall
self.notify(eventname, *args, **kwargs)
def send(self, item):
assert item is not None
@ -101,24 +107,22 @@ class SlaveNode(object):
def __repr__(self):
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):
from py.__.test.dsession.hostmanage import makehostup
channel = self.channel
self.host = host = channel.receive()
channel.send(makehostup(host))
self.trace = self.config.maketrace(host.hostid)
self.trace("initialized")
self.sendevent("hostup", makehostup(host))
try:
while 1:
task = channel.receive()
self.trace("received", task)
self.config.bus.notify("masterslave_receivedtask", task)
if task is None: # shutdown
channel.send(None)
self.trace("shutting down, send None to", channel)
self.channel.send(None)
break
if isinstance(task, list):
for item in task:
@ -128,15 +132,10 @@ class SlaveNode(object):
except KeyboardInterrupt:
raise
except:
rep = event.InternalException()
self.trace("sending back internal exception report, breaking loop")
channel.send(rep)
self.sendevent("internalerror", event.InternalException())
raise
else:
self.trace("normal shutdown")
def runtest(self, item):
runner = item._getrunner()
testrep = runner(item)
self.channel.send(testrep)
self.trace("sent back testreport", testrep)
self.sendevent("itemtestreport", testrep)

View File

@ -1,4 +1,3 @@
from py.__.test.testing.suptest import InlineCollection
from py.__.test.dsession.dsession import DSession, LoopState
from py.__.test.dsession.hostmanage import Host, makehostup
from py.__.test.runner import basic_collect_report
@ -24,17 +23,17 @@ def dumpqueue(queue):
while queue.qsize():
print queue.get()
class TestDSession(InlineCollection):
def test_fixoptions(self):
config = self.parseconfig("--exec=xxx")
class TestDSession:
def test_fixoptions(self, testdir):
config = testdir.parseconfig("--exec=xxx")
config.initsession().fixoptions()
assert config.option.numprocesses == 1
config = self.parseconfig("--exec=xxx", '-n3')
config = testdir.parseconfig("--exec=xxx", '-n3')
config.initsession().fixoptions()
assert config.option.numprocesses == 3
def test_add_remove_host(self):
item = self.getitem("def test_func(): pass")
def test_add_remove_host(self, testdir):
item = testdir.getitem("def test_func(): pass")
rep = run(item)
session = DSession(item._config)
host = Host("localhost")
@ -48,8 +47,8 @@ class TestDSession(InlineCollection):
assert item not in session.item2host
py.test.raises(Exception, "session.removehost(host)")
def test_senditems_removeitems(self):
item = self.getitem("def test_func(): pass")
def test_senditems_removeitems(self, testdir):
item = testdir.getitem("def test_func(): pass")
rep = run(item)
session = DSession(item._config)
host = Host("localhost")
@ -62,19 +61,20 @@ class TestDSession(InlineCollection):
assert not session.host2pending[host]
assert not session.item2host
def test_triggertesting_collect(self):
modcol = self.getmodulecol("""
def test_triggertesting_collect(self, testdir):
modcol = testdir.getmodulecol("""
def test_func():
pass
""")
session = DSession(modcol._config)
session.triggertesting([modcol])
rep = session.queue.get(block=False)
assert isinstance(rep, event.CollectionReport)
name, args, kwargs = session.queue.get(block=False)
assert name == 'collectionreport'
rep, = args
assert len(rep.result) == 1
def test_triggertesting_item(self):
item = self.getitem("def test_func(): pass")
def test_triggertesting_item(self, testdir):
item = testdir.getitem("def test_func(): pass")
session = DSession(item._config)
host1 = Host("localhost")
host1.node = MockNode()
@ -89,51 +89,52 @@ class TestDSession(InlineCollection):
assert host2_sent == [item] * session.MAXITEMSPERHOST
assert session.host2pending[host1] == host1_sent
assert session.host2pending[host2] == host2_sent
ev = session.queue.get(block=False)
assert isinstance(ev, event.RescheduleItems)
name, args, kwargs = session.queue.get(block=False)
assert name == "rescheduleitems"
ev, = args
assert ev.items == [item]
def test_keyboardinterrupt(self):
item = self.getitem("def test_func(): pass")
def test_keyboardinterrupt(self, testdir):
item = testdir.getitem("def test_func(): pass")
session = DSession(item._config)
def raise_(timeout=None): raise KeyboardInterrupt()
session.queue.get = raise_
exitstatus = session.loop([])
assert exitstatus == outcome.EXIT_INTERRUPTED
def test_internalerror(self):
item = self.getitem("def test_func(): pass")
def test_internalerror(self, testdir):
item = testdir.getitem("def test_func(): pass")
session = DSession(item._config)
def raise_(): raise ValueError()
session.queue.get = raise_
exitstatus = session.loop([])
assert exitstatus == outcome.EXIT_INTERNALERROR
def test_rescheduleevent(self):
item = self.getitem("def test_func(): pass")
def test_rescheduleevent(self, testdir):
item = testdir.getitem("def test_func(): pass")
session = DSession(item._config)
host1 = Host("localhost")
host1.node = MockNode()
session.addhost(host1)
ev = event.RescheduleItems([item])
loopstate = LoopState([])
session.queue.put(ev)
session.queueevent("rescheduleitems", ev)
session.loop_once(loopstate)
# check that RescheduleEvents are not immediately
# rescheduled if there are no hosts
assert loopstate.dowork == False
session.queue.put(event.NOP())
session.queueevent("anonymous", event.NOP())
session.loop_once(loopstate)
session.queue.put(event.NOP())
session.queueevent("anonymous", event.NOP())
session.loop_once(loopstate)
assert host1.node.sent == [[item]]
session.queue.put(run(item))
session.queueevent("itemtestreport", run(item))
session.loop_once(loopstate)
assert loopstate.shuttingdown
assert not loopstate.testsfailed
def test_no_hosts_remaining_for_tests(self):
item = self.getitem("def test_func(): pass")
def test_no_hosts_remaining_for_tests(self, testdir):
item = testdir.getitem("def test_func(): pass")
# setup a session with one host
session = DSession(item._config)
host1 = Host("localhost")
@ -142,7 +143,7 @@ class TestDSession(InlineCollection):
# setup a HostDown event
ev = event.HostDown(host1, None)
session.queue.put(ev)
session.queueevent("hostdown", ev)
loopstate = LoopState([item])
loopstate.dowork = False
@ -150,8 +151,8 @@ class TestDSession(InlineCollection):
dumpqueue(session.queue)
assert loopstate.exitstatus == outcome.EXIT_NOHOSTS
def test_hostdown_causes_reschedule_pending(self):
modcol = self.getmodulecol("""
def test_hostdown_causes_reschedule_pending(self, testdir, EventRecorder):
modcol = testdir.getmodulecol("""
def test_crash():
assert 0
def test_fail():
@ -172,41 +173,39 @@ class TestDSession(InlineCollection):
session.senditems([item1, item2])
host = session.item2host[item1]
ev = event.HostDown(host, None)
session.queue.put(ev)
events = [] ; session.bus.subscribe(events.append)
session.queueevent("hostdown", ev)
evrec = EventRecorder(session.bus)
loopstate = LoopState([])
session.loop_once(loopstate)
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.colitem == item1
assert str(testrep.outcome.longrepr).find("CRASHED") != -1
assert str(testrep.outcome.longrepr).find(host.hostname) != -1
assert str(testrep.longrepr).find("crashed") != -1
assert str(testrep.longrepr).find(host.hostname) != -1
def test_hostup_adds_to_available(self):
item = self.getitem("def test_func(): pass")
def test_hostup_adds_to_available(self, testdir):
item = testdir.getitem("def test_func(): pass")
# setup a session with two hosts
session = DSession(item._config)
host1 = Host("localhost")
hostup = makehostup(host1)
session.queue.put(hostup)
session.queueevent("hostup", hostup)
loopstate = LoopState([item])
loopstate.dowork = False
assert len(session.host2pending) == 0
session.loop_once(loopstate)
assert len(session.host2pending) == 1
def test_event_propagation(self):
item = self.getitem("def test_func(): pass")
def test_event_propagation(self, testdir, EventRecorder):
item = testdir.getitem("def test_func(): pass")
session = DSession(item._config)
ev = event.NOP()
events = [] ; session.bus.subscribe(events.append)
session.queue.put(ev)
evrec = EventRecorder(session.bus)
session.queueevent("NOPevent", 42)
session.loop_once(LoopState([]))
assert events[0] == ev
assert evrec.getfirstnamed('NOPevent')
def runthrough(self, item):
session = DSession(item._config)
@ -215,31 +214,31 @@ class TestDSession(InlineCollection):
session.addhost(host1)
loopstate = LoopState([item])
session.queue.put(event.NOP())
session.queueevent("NOP")
session.loop_once(loopstate)
assert host1.node.sent == [[item]]
ev = run(item)
session.queue.put(ev)
session.queueevent("itemtestreport", ev)
session.loop_once(loopstate)
assert loopstate.shuttingdown
session.queue.put(event.HostDown(host1, None))
session.queueevent("hostdown", event.HostDown(host1, None))
session.loop_once(loopstate)
dumpqueue(session.queue)
return session, loopstate.exitstatus
def test_exit_completed_tests_ok(self):
item = self.getitem("def test_func(): pass")
def test_exit_completed_tests_ok(self, testdir):
item = testdir.getitem("def test_func(): pass")
session, exitstatus = self.runthrough(item)
assert exitstatus == outcome.EXIT_OK
def test_exit_completed_tests_fail(self):
item = self.getitem("def test_func(): 0/0")
def test_exit_completed_tests_fail(self, testdir):
item = testdir.getitem("def test_func(): 0/0")
session, exitstatus = self.runthrough(item)
assert exitstatus == outcome.EXIT_TESTSFAILED
def test_exit_on_first_failing(self):
modcol = self.getmodulecol("""
def test_exit_on_first_failing(self, testdir):
modcol = testdir.getmodulecol("""
def test_fail():
assert 0
def test_pass():
@ -258,33 +257,32 @@ class TestDSession(InlineCollection):
# run tests ourselves and produce reports
ev1 = run(items[0])
ev2 = run(items[1])
session.queue.put(ev1) # a failing one
session.queue.put(ev2)
session.queueevent("itemtestreport", ev1) # a failing one
session.queueevent("itemtestreport", ev2)
# now call the loop
loopstate = LoopState(items)
session.loop_once(loopstate)
assert loopstate.testsfailed
assert loopstate.shuttingdown
def test_shuttingdown_filters_events(self):
item = self.getitem("def test_func(): pass")
def test_shuttingdown_filters_events(self, testdir, EventRecorder):
item = testdir.getitem("def test_func(): pass")
session = DSession(item._config)
host = Host("localhost")
session.addhost(host)
loopstate = LoopState([])
loopstate.shuttingdown = True
l = []
session.bus.subscribe(l.append)
session.queue.put(run(item))
evrec = EventRecorder(session.bus)
session.queueevent("itemtestreport", run(item))
session.loop_once(loopstate)
assert not l
assert not evrec.getfirstnamed("hostdown")
ev = event.HostDown(host)
session.queue.put(ev)
session.queueevent("hostdown", ev)
session.loop_once(loopstate)
assert l == [ev]
assert evrec.getfirstnamed('hostdown') == ev
def test_filteritems(self):
modcol = self.getmodulecol("""
def test_filteritems(self, testdir, EventRecorder):
modcol = testdir.getmodulecol("""
def test_fail():
assert 0
def test_pass():
@ -296,42 +294,42 @@ class TestDSession(InlineCollection):
dsel = session.filteritems([modcol])
assert dsel == [modcol]
items = modcol.collect()
events = [] ; session.bus.subscribe(events.append)
evrec = EventRecorder(session.bus)
remaining = session.filteritems(items)
assert remaining == []
ev = events[-1]
assert isinstance(ev, event.Deselected)
evname, ev = evrec.events[-1]
assert evname == "deselected"
assert ev.items == items
modcol._config.option.keyword = "test_fail"
remaining = session.filteritems(items)
assert remaining == [items[0]]
ev = events[-1]
assert isinstance(ev, event.Deselected)
evname, ev = evrec.events[-1]
assert evname == "deselected"
assert ev.items == [items[1]]
def test_hostdown_shutdown_after_completion(self):
item = self.getitem("def test_func(): pass")
def test_hostdown_shutdown_after_completion(self, testdir):
item = testdir.getitem("def test_func(): pass")
session = DSession(item._config)
host = Host("localhost")
host.node = MockNode()
session.addhost(host)
session.senditems([item])
session.queue.put(run(item))
session.queueevent("itemtestreport", run(item))
loopstate = LoopState([])
session.loop_once(loopstate)
assert host.node._shutdown is True
assert loopstate.exitstatus is None, "loop did not wait for hostdown"
assert loopstate.shuttingdown
session.queue.put(event.HostDown(host, None))
session.queueevent("hostdown", event.HostDown(host, None))
session.loop_once(loopstate)
assert loopstate.exitstatus == 0
def test_nopending_but_collection_remains(self):
modcol = self.getmodulecol("""
def test_nopending_but_collection_remains(self, testdir):
modcol = testdir.getmodulecol("""
def test_fail():
assert 0
def test_pass():
@ -347,10 +345,10 @@ class TestDSession(InlineCollection):
session.senditems([item1])
# host2pending will become empty when the loop sees
# the report
session.queue.put(run(item1))
session.queueevent("itemtestreport", run(item1))
# but we have a collection pending
session.queue.put(colreport)
session.queueevent("collectionreport", colreport)
loopstate = LoopState([])
session.loop_once(loopstate)

View File

@ -3,74 +3,48 @@
"""
import py
from py.__.test import event
from py.__.test.dsession.dsession import DSession
from py.__.test.dsession.hostmanage import HostManager, Host
from py.__.test.testing import suptest
from test_masterslave import EventQueue
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):
def test_dist_no_disthost(self):
config = self.parseconfig(self.tmpdir, '-d')
class TestAsyncFunctional:
def test_dist_no_disthost(self, testdir):
config = testdir.parseconfig(testdir.tmpdir, '-d')
py.test.raises(SystemExit, "config.initsession()")
def test_session_eventlog_dist(self):
self.makepyfile(conftest="dist_hosts=['localhost']\n")
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="""
def test_conftest_options(self, testdir):
testdir.makepyfile(conftest="""
print "importing conftest"
import py
Option = py.test.config.Option
option = py.test.config.addoptions("someopt",
Option('', '--forcegen', action="store_true", dest="forcegen", default=False))
""")
self.makepyfile(__init__="#")
p1 = self.makepyfile(test_one="""
Option('--someopt', action="store_true", dest="someopt", default=False))
""",
)
p1 = testdir.makepyfile("""
def test_1():
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.option.forcegen", conftest.option.forcegen
assert conftest.option.forcegen
""")
print "test_1: conftest.option.someopt", conftest.option.someopt
assert conftest.option.someopt
""", __init__="#")
print p1
config = self.parseconfig('-n1', p1, '--forcegen')
config = py.test.config._reparse(['-n1', p1, '--someopt'])
dsession = DSession(config)
readevent = eventreader(dsession)
eq = EventQueue(config.bus)
dsession.main()
ev = readevent(event.ItemTestReport)
ev, = eq.geteventargs("itemtestreport")
if not ev.passed:
print ev.outcome.longrepr
print ev
assert 0
def test_dist_some_tests(self):
self.makepyfile(conftest="dist_hosts=['localhost']\n")
p1 = self.makepyfile(test_one="""
def test_dist_some_tests(self, testdir):
testdir.makepyfile(conftest="dist_hosts=['localhost']\n")
p1 = testdir.makepyfile(test_one="""
def test_1():
pass
def test_x():
@ -79,22 +53,22 @@ class TestAsyncFunctional(suptest.InlineCollection):
def test_fail():
assert 0
""")
config = self.parseconfig('-d', p1)
config = testdir.parseconfig('-d', p1)
dsession = DSession(config)
readevent = eventreader(dsession)
eq = EventQueue(config.bus)
dsession.main([config.getfsnode(p1)])
ev = readevent(event.ItemTestReport)
ev, = eq.geteventargs("itemtestreport")
assert ev.passed
ev = readevent(event.ItemTestReport)
ev, = eq.geteventargs("itemtestreport")
assert ev.skipped
ev = readevent(event.ItemTestReport)
ev, = eq.geteventargs("itemtestreport")
assert ev.failed
# see that the host is really down
ev = readevent(event.HostDown)
ev, = eq.geteventargs("hostdown")
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")
destdir = py.test.ensuretemp("example_dist_destdir")
subdir = "sub_example_dist"
@ -124,28 +98,26 @@ class TestAsyncFunctional(suptest.InlineCollection):
assert config.topdir == tmpdir
assert not tmpdir.join("__init__.py").check()
dist = DSession(config)
sorter = suptest.events_from_session(dist)
testevents = sorter.get(event.ItemTestReport)
sorter = testdir.inline_runsession(dist)
testevents = sorter.getnamed('itemtestreport')
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.skipped]) == 0
def test_nice_level(self):
def test_nice_level(self, testdir):
""" Tests if nice level behaviour is ok """
if not hasattr(os, 'nice'):
py.test.skip("no os.nice() available")
self.makepyfile(conftest="""
testdir.makepyfile(conftest="""
dist_hosts=['localhost']
dist_nicelevel = 10
""")
p1 = self.makepyfile(test_nice="""
p1 = testdir.makepyfile("""
def test_nice():
import os
assert os.nice(0) == 10
""")
config = self.parseconfig('-d', p1)
session = config.initsession()
events = suptest.events_from_session(session)
ev = events.getreport('test_nice')
evrec = testdir.inline_run('-d', p1)
ev = evrec.getreport('test_nice')
assert ev.passed

View File

@ -3,23 +3,15 @@
"""
import py
from py.__.test.testing import suptest
from py.__.test.dsession.hostmanage import HostRSync, Host, HostManager, gethosts
from py.__.test.dsession.hostmanage import sethomedir, gethomedir, getpath_relto_home
from py.__.test import event
class TmpWithSourceDest(suptest.FileCreation):
def setup_method(self, method):
super(TmpWithSourceDest, self).setup_method(method)
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")
class TestHost:
def _gethostinfo(self, testdir, relpath=""):
exampledir = testdir.mkdir("gethostinfo")
if relpath:
exampledir = exampledir.join(relpath)
assert not exampledir.check()
hostinfo = Host("localhost:%s" % exampledir)
return hostinfo
@ -62,8 +54,8 @@ class TestHost(suptest.FileCreation):
py.test.raises((py.process.cmdexec.Error, IOError, EOFError),
host.initgateway)
def test_remote_has_homedir_as_currentdir(self):
host = self._gethostinfo()
def test_remote_has_homedir_as_currentdir(self, testdir):
host = self._gethostinfo(testdir)
old = py.path.local.get_temproot().chdir()
try:
host.initgateway()
@ -104,10 +96,10 @@ class TestHost(suptest.FileCreation):
assert l[0] == host.python
def test_initgateway_ssh_and_remotepath(self):
from py.__.conftest import option
if not option.sshtarget:
hostspec = py.test.config.option.sshhost
if not hostspec:
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
# as the remotepath is the default location
# and may be used in the real world
@ -125,18 +117,23 @@ class TestHost(suptest.FileCreation):
res = channel.receive()
assert res == host.gw_remotepath
class TestSyncing(TmpWithSourceDest):
def _gethostinfo(self):
hostinfo = Host("localhost:%s" % self.dest)
def pytest_pyfuncarg_source(pyfuncitem):
return py.test.ensuretemp(pyfuncitem.getmodpath()).mkdir("source")
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
def test_hrsync_filter(self):
self.source.ensure("dir", "file.txt")
self.source.ensure(".svn", "entries")
self.source.ensure(".somedotfile", "moreentries")
self.source.ensure("somedir", "editfile~")
syncer = HostRSync(self.source)
l = list(self.source.visit(rec=syncer.filter,
def test_hrsync_filter(self, source, dest):
source.ensure("dir", "file.txt")
source.ensure(".svn", "entries")
source.ensure(".somedotfile", "moreentries")
source.ensure("somedir", "editfile~")
syncer = HostRSync(source)
l = list(source.visit(rec=syncer.filter,
fil=syncer.filter))
assert len(l) == 3
basenames = [x.basename for x in l]
@ -144,10 +141,10 @@ class TestSyncing(TmpWithSourceDest):
assert 'file.txt' in basenames
assert 'somedir' in basenames
def test_hrsync_localhost_inplace(self):
def test_hrsync_localhost_inplace(self, source, dest):
h1 = Host("localhost")
events = []
rsync = HostRSync(self.source)
rsync = HostRSync(source)
h1.initgateway()
rsync.add_target_host(h1, notify=events.append)
assert events
@ -161,24 +158,24 @@ class TestSyncing(TmpWithSourceDest):
if isinstance(x, event.HostRSyncRootReady)]
assert len(l) == 1
ev = l[0]
assert ev.root == self.source
assert ev.root == source
assert ev.host == h1
def test_hrsync_one_host(self):
h1 = self._gethostinfo()
def test_hrsync_one_host(self, source, dest):
h1 = self._gethostinfo(dest)
finished = []
rsync = HostRSync(self.source)
rsync = HostRSync(source)
h1.initgateway()
rsync.add_target_host(h1)
self.source.join("hello.py").write("world")
source.join("hello.py").write("world")
rsync.send()
assert self.dest.join("hello.py").check()
assert dest.join("hello.py").check()
def test_hrsync_same_host_twice(self):
h1 = self._gethostinfo()
h2 = self._gethostinfo()
def test_hrsync_same_host_twice(self, source, dest):
h1 = self._gethostinfo(dest)
h2 = self._gethostinfo(dest)
finished = []
rsync = HostRSync(self.source)
rsync = HostRSync(source)
l = []
h1.initgateway()
h2.initgateway()
@ -187,87 +184,87 @@ class TestSyncing(TmpWithSourceDest):
res2 = rsync.add_target_host(h2)
assert not res2
class TestHostManager(TmpWithSourceDest):
def gethostmanager(self, dist_hosts, dist_rsync_roots=None):
class TestHostManager:
def gethostmanager(self, source, dist_hosts, dist_rsync_roots=None):
l = ["dist_hosts = %r" % dist_hosts]
if dist_rsync_roots:
l.append("dist_rsync_roots = %r" % dist_rsync_roots)
self.source.join("conftest.py").write("\n".join(l))
config = py.test.config._reparse([self.source])
assert config.topdir == self.source
source.join("conftest.py").write("\n".join(l))
config = py.test.config._reparse([source])
assert config.topdir == source
session = config.initsession()
hm = HostManager(session)
assert hm.hosts
return hm
def test_hostmanager_custom_hosts(self):
session = py.test.config._reparse([self.source]).initsession()
def test_hostmanager_custom_hosts(self, source, dest):
session = py.test.config._reparse([source]).initsession()
hm = HostManager(session, hosts=[1,2,3])
assert hm.hosts == [1,2,3]
def test_hostmanager_init_rsync_topdir(self):
dir2 = self.source.ensure("dir1", "dir2", dir=1)
def test_hostmanager_init_rsync_topdir(self, source, dest):
dir2 = source.ensure("dir1", "dir2", dir=1)
dir2.ensure("hello")
hm = self.gethostmanager(
dist_hosts = ["localhost:%s" % self.dest]
hm = self.gethostmanager(source,
dist_hosts = ["localhost:%s" % dest]
)
assert hm.session.config.topdir == self.source
assert hm.session.config.topdir == source
hm.init_rsync()
dest = self.dest.join(self.source.basename)
dest = dest.join(source.basename)
assert dest.join("dir1").check()
assert dest.join("dir1", "dir2").check()
assert dest.join("dir1", "dir2", 'hello').check()
def test_hostmanager_init_rsync_topdir_explicit(self):
dir2 = self.source.ensure("dir1", "dir2", dir=1)
def test_hostmanager_init_rsync_topdir_explicit(self, source, dest):
dir2 = source.ensure("dir1", "dir2", dir=1)
dir2.ensure("hello")
hm = self.gethostmanager(
dist_hosts = ["localhost:%s" % self.dest],
dist_rsync_roots = [str(self.source)]
hm = self.gethostmanager(source,
dist_hosts = ["localhost:%s" % dest],
dist_rsync_roots = [str(source)]
)
assert hm.session.config.topdir == self.source
assert hm.session.config.topdir == source
hm.init_rsync()
dest = self.dest.join(self.source.basename)
dest = dest.join(source.basename)
assert dest.join("dir1").check()
assert dest.join("dir1", "dir2").check()
assert dest.join("dir1", "dir2", 'hello').check()
def test_hostmanager_init_rsync_roots(self):
dir2 = self.source.ensure("dir1", "dir2", dir=1)
self.source.ensure("dir1", "somefile", dir=1)
def test_hostmanager_init_rsync_roots(self, source, dest):
dir2 = source.ensure("dir1", "dir2", dir=1)
source.ensure("dir1", "somefile", dir=1)
dir2.ensure("hello")
self.source.ensure("bogusdir", "file")
self.source.join("conftest.py").write(py.code.Source("""
source.ensure("bogusdir", "file")
source.join("conftest.py").write(py.code.Source("""
dist_rsync_roots = ['dir1/dir2']
"""))
session = py.test.config._reparse([self.source]).initsession()
session = py.test.config._reparse([source]).initsession()
hm = HostManager(session,
hosts=[Host("localhost:" + str(self.dest))])
hosts=[Host("localhost:" + str(dest))])
hm.init_rsync()
assert self.dest.join("dir2").check()
assert not self.dest.join("dir1").check()
assert not self.dest.join("bogus").check()
assert dest.join("dir2").check()
assert not dest.join("dir1").check()
assert not dest.join("bogus").check()
def test_hostmanager_rsync_ignore(self):
dir2 = self.source.ensure("dir1", "dir2", dir=1)
dir5 = self.source.ensure("dir5", "dir6", "bogus")
dirf = self.source.ensure("dir5", "file")
def test_hostmanager_rsync_ignore(self, source, dest):
dir2 = source.ensure("dir1", "dir2", dir=1)
dir5 = source.ensure("dir5", "dir6", "bogus")
dirf = source.ensure("dir5", "file")
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']
"""))
session = py.test.config._reparse([self.source]).initsession()
session = py.test.config._reparse([source]).initsession()
hm = HostManager(session,
hosts=[Host("localhost:" + str(self.dest))])
hosts=[Host("localhost:" + str(dest))])
hm.init_rsync()
assert self.dest.join("dir1").check()
assert not self.dest.join("dir1", "dir2").check()
assert self.dest.join("dir5","file").check()
assert not self.dest.join("dir6").check()
assert dest.join("dir1").check()
assert not dest.join("dir1", "dir2").check()
assert dest.join("dir5","file").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)]
session = py.test.config._reparse([self.source]).initsession()
session = py.test.config._reparse([source]).initsession()
hm = HostManager(session, hosts=hosts)
hm.init_rsync()
for host in hosts:
@ -275,28 +272,31 @@ class TestHostManager(TmpWithSourceDest):
assert host.gw_remotepath is None
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)]
session = py.test.config._reparse([self.source]).initsession()
session = py.test.config._reparse([source]).initsession()
hm = HostManager(session, hosts=hosts)
queue = py.std.Queue.Queue()
hm.setup_hosts(notify=queue.put)
hm.setup_hosts(putevent=queue.put)
for host in hm.hosts:
ev = queue.get(timeout=2.0)
assert isinstance(ev, event.HostUp)
eventcall = queue.get(timeout=2.0)
name, args, kwargs = eventcall
assert name == "hostup"
for host in hm.hosts:
host.node.shutdown()
for host in hm.hosts:
ev = queue.get(timeout=2.0)
assert isinstance(ev, event.HostDown)
eventcall = queue.get(timeout=2.0)
name, args, kwargs = eventcall
print name, args, kwargs
assert name == "hostdown"
def XXXtest_ssh_rsync_samehost_twice(self):
#XXX we have no easy way to have a temp directory remotely!
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")
host1 = Host("%s" % (option.sshtarget, ))
host2 = Host("%s" % (option.sshtarget, ))
host1 = Host("%s" % (option.sshhost, ))
host2 = Host("%s" % (option.sshhost, ))
hm = HostManager(config, hosts=[host1, host2])
events = []
hm.init_rsync(events.append)

View File

@ -2,99 +2,116 @@
import py
from py.__.test.dsession.masterslave import MasterNode
from py.__.test.dsession.hostmanage import Host
from py.__.test import event
from py.__.test.testing import suptest
class TestMasterSlaveConnection(suptest.InlineCollection):
def getevent(self, eventtype=event.ItemTestReport, timeout=2.0):
class EventQueue:
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 = []
while 1:
try:
ev = self.queue.get(timeout=timeout)
eventcall = self.queue.get(timeout=timeout)
except py.std.Queue.Empty:
print "node channel", self.node.channel
print "remoteerror", self.node.channel._getremoteerror()
#print "node channel", self.node.channel
#print "remoteerror", self.node.channel._getremoteerror()
print "seen events", events
raise IOError("did not see %r events" % (eventtype))
raise IOError("did not see %r events" % (eventname))
else:
if isinstance(ev, eventtype):
return ev
events.append(ev)
name, args, kwargs = eventcall
assert isinstance(name, str)
if name == eventname:
return args
events.append(name)
def setup_method(self, method):
super(TestMasterSlaveConnection, self).setup_method(method)
self.makepyfile(__init__="")
self.config = self.parseconfig(self.tmpdir)
class MySetup:
def __init__(self, pyfuncitem):
self.pyfuncitem = pyfuncitem
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.host = Host("localhost")
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()
return self.node
def getitem(self, source):
kw = {"test_" + self.tmpdir.basename: py.code.Source(source).strip()}
path = self.makepyfile(**kw)
fscol = self.config.getfsnode(path)
return fscol.collect_by_name("test_func")
def finalize(self):
if hasattr(self, 'host'):
print "exiting:", self.host.gw
self.host.gw.exit()
def getitems(self, source):
kw = {"test_" + self.tmpdir.basename: py.code.Source(source).strip()}
path = self.makepyfile(**kw)
fscol = self.config.getfsnode(path)
return fscol.collect()
def teardown_method(self, method):
print "at teardown:", self.node.channel
#if not self.node.channel.isclosed():
# self.node.shutdown()
self.host.gw.exit()
def pytest_pyfuncarg_mysetup(pyfuncitem):
mysetup = MySetup(pyfuncitem)
pyfuncitem.addfinalizer(mysetup.finalize)
return mysetup
def test_crash_invalid_item(self):
self.node.send(123) # invalid item
ev = self.getevent(event.HostDown)
assert ev.host == self.host
class TestMasterSlaveConnection:
def test_crash_invalid_item(self, mysetup):
node = mysetup.makenode()
node.send(123) # invalid item
ev, = mysetup.geteventargs("hostdown")
assert ev.host == mysetup.host
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'):
py.test.skip("no os.kill")
item = self.getitem("""
item = testdir.getitem("""
def test_func():
import os
os.kill(os.getpid(), 15)
""")
self.node.send(item)
ev = self.getevent(event.HostDown)
assert ev.host == self.host
node = mysetup.makenode(item._config)
node.send(item)
ev, = mysetup.geteventargs("hostdown")
assert ev.host == mysetup.host
assert str(ev.error).find("TERMINATED") != -1
def test_node_down(self):
self.node.shutdown()
ev = self.getevent(event.HostDown)
assert ev.host == self.host
def test_node_down(self, mysetup):
node = mysetup.makenode()
node.shutdown()
ev, = mysetup.geteventargs("hostdown")
assert ev.host == mysetup.host
assert not ev.error
self.node.callback(self.node.ENDMARK)
node.callback(node.ENDMARK)
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):
item = self.getitem("def test_func(): pass")
self.node.channel.close()
py.test.raises(IOError, "self.node.send(item)")
#ev = self.getevent(event.InternalException)
def test_send_on_closed_channel(self, testdir, mysetup):
item = testdir.getitem("def test_func(): pass")
node = mysetup.makenode(item._config)
node.channel.close()
py.test.raises(IOError, "node.send(item)")
#ev = self.geteventargs(event.InternalException)
#assert ev.excinfo.errisinstance(IOError)
def test_send_one(self):
item = self.getitem("def test_func(): pass")
self.node.send(item)
ev = self.getevent()
def test_send_one(self, testdir, mysetup):
item = testdir.getitem("def test_func(): pass")
node = mysetup.makenode(item._config)
node.send(item)
ev, = mysetup.geteventargs("itemtestreport")
assert ev.passed
assert ev.colitem == item
#assert event.item == item
#assert event.item is not item
def test_send_some(self):
items = self.getitems("""
def test_send_some(self, testdir, mysetup):
items = testdir.getitems("""
def test_pass():
pass
def test_fail():
@ -103,13 +120,14 @@ class TestMasterSlaveConnection(suptest.InlineCollection):
import py
py.test.skip("x")
""")
node = mysetup.makenode(items[0]._config)
for item in items:
self.node.send(item)
node.send(item)
for outcome in "passed failed skipped".split():
ev = self.getevent()
ev, = mysetup.geteventargs("itemtestreport")
assert getattr(ev, outcome)
self.node.sendlist(items)
node.sendlist(items)
for outcome in "passed failed skipped".split():
ev = self.getevent()
ev, = mysetup.geteventargs("itemtestreport")
assert getattr(ev, outcome)

View File

@ -6,24 +6,6 @@ import py
import time
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):
def __repr__(self):
l = ["%s=%s" %(key, value)
@ -75,33 +57,73 @@ class Deselected(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):
longrepr = self.outcome.longrepr
longrepr = self.longrepr
if hasattr(longrepr, 'toterminal'):
longrepr.toterminal(out)
else:
out.line(str(longrepr))
class ItemTestReport(BaseReport):
""" 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):
def __init__(self, collector):
self.collector = collector
class CollectionReport(BaseReport):
""" Collection Report. """
def __init__(self, colitem, result, **kwargs):
super(CollectionReport, self).__init__(colitem, **kwargs)
self.result = result
skipped = failed = passed = False
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):
def __init__(self, failreports, rootdirs):
@ -151,3 +173,12 @@ class HostRSyncRootReady(BaseEvent):
self.host = host
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)
#

View File

@ -13,7 +13,6 @@ import py
from py.__.test.session import Session
from py.__.test.outcome import Failed, Passed, Skipped
from py.__.test.dsession.mypickle import PickleChannel
from py.__.test.report.terminal import TerminalReporter
from py.__.test import event
from py.__.test.looponfail import util
@ -83,8 +82,8 @@ class RemoteControl(object):
from py.__.test.looponfail.remote import slave_runsession
from py.__.test.dsession import masterslave
config = masterslave.receive_and_send_pickled_config(channel)
width, hasmarkup = channel.receive()
slave_runsession(channel, config, width, hasmarkup)
fullwidth, hasmarkup = channel.receive()
slave_runsession(channel, config, fullwidth, hasmarkup)
""", stdout=out, stderr=out)
channel = PickleChannel(channel)
masterslave.send_and_receive_pickled_config(
@ -117,7 +116,7 @@ class RemoteControl(object):
finally:
self.ensure_teardown()
def slave_runsession(channel, config, width, hasmarkup):
def slave_runsession(channel, config, fullwidth, hasmarkup):
""" we run this on the other side. """
if config.option.debug:
def DEBUG(*args):
@ -134,8 +133,10 @@ def slave_runsession(channel, config, width, hasmarkup):
DEBUG("SLAVE: initsession()")
session = config.initsession()
session.reporter._tw.hasmarkup = hasmarkup
session.reporter._tw.fullwidth = width
# XXX configure the reporter object's terminal writer more directly
# XXX and write a test for this remote-terminal setting logic
config.pytest_terminal_hasmarkup = hasmarkup
config.pytest_terminal_fullwidth = fullwidth
if trails:
colitems = []
for trail in trails:
@ -148,19 +149,18 @@ def slave_runsession(channel, config, width, hasmarkup):
else:
colitems = None
session.shouldclose = channel.isclosed
#def sendevent(ev):
# channel.send(ev)
#session.bus.subscribe(sendevent)
failreports = []
def recordfailures(ev):
if isinstance(ev, event.BaseReport):
class Failures(list):
def pyevent_itemtestreport(self, ev):
if ev.failed:
failreports.append(ev)
session.bus.subscribe(recordfailures)
self.append(ev)
pyevent_collectionreport = pyevent_itemtestreport
failreports = Failures()
session.bus.register(failreports)
DEBUG("SLAVE: starting session.main()")
session.main(colitems)
session.bus.unsubscribe(recordfailures)
ev = event.LooponfailingInfo(failreports, [config.topdir])
session.bus.notify(ev)
ev = event.LooponfailingInfo(list(failreports), [config.topdir])
session.bus.notify("looponfailinginfo", ev)
channel.send([x.colitem._totrail() for x in failreports if x.failed])

View File

@ -1,33 +1,16 @@
import py
from py.__.test.testing import suptest
from py.__.test.looponfail.remote import LooponfailingSession, LoopState, RemoteControl
from py.__.test import event
def getevent(l, evtype):
result = getevents(l, evtype)
if not result:
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 = []
class TestRemoteControl:
def test_nofailures(self, testdir):
item = testdir.getitem("def test_func(): pass\n")
control = RemoteControl(item._config)
control.setup()
failures = control.runsession()
assert not failures
def test_failures(self):
item = self.getitem("def test_func(): assert 0\n")
def test_failures_somewhere(self, testdir):
item = testdir.getitem("def test_func(): assert 0\n")
control = RemoteControl(item._config)
control.setup()
failures = control.runsession()
@ -38,8 +21,8 @@ class TestRemoteControl(suptest.InlineCollection):
failures = control.runsession(failures)
assert not failures
def test_failure_change(self):
modcol = self.getitem("""
def test_failure_change(self, testdir):
modcol = testdir.getitem("""
def test_func():
assert 0
""")
@ -62,9 +45,9 @@ class TestRemoteControl(suptest.InlineCollection):
assert failures
assert str(failures).find("test_new") != -1
class TestLooponFailing(suptest.InlineCollection):
def test_looponfailing_from_fail_to_ok(self):
modcol = self.getmodulecol("""
class TestLooponFailing:
def test_looponfailing_from_fail_to_ok(self, testdir):
modcol = testdir.getmodulecol("""
def test_one():
x = 0
assert x == 1
@ -88,8 +71,8 @@ class TestLooponFailing(suptest.InlineCollection):
session.loop_once(loopstate)
assert not loopstate.colitems
def test_looponfailing_from_one_to_two_tests(self):
modcol = self.getmodulecol("""
def test_looponfailing_from_one_to_two_tests(self, testdir):
modcol = testdir.getmodulecol("""
def test_one():
assert 0
""")
@ -113,8 +96,8 @@ class TestLooponFailing(suptest.InlineCollection):
session.loop_once(loopstate)
assert len(loopstate.colitems) == 1
def test_looponfailing_removed_test(self):
modcol = self.getmodulecol("""
def test_looponfailing_removed_test(self, testdir):
modcol = testdir.getmodulecol("""
def test_one():
assert 0
def test_two():

View File

@ -1,9 +1,8 @@
import py
from py.__.test.looponfail.util import StatRecorder, EventRecorder
from py.__.test import event
from py.__.test.looponfail.util import StatRecorder
def test_filechange():
tmp = py.test.ensuretemp("test_filechange")
def test_filechange(tmpdir):
tmp = tmpdir
hello = tmp.ensure("hello.py")
sd = StatRecorder([tmp])
changed = sd.check()
@ -35,8 +34,8 @@ def test_filechange():
changed = sd.check()
assert changed
def test_pycremoval():
tmp = py.test.ensuretemp("test_pycremoval")
def test_pycremoval(tmpdir):
tmp = tmpdir
hello = tmp.ensure("hello.py")
sd = StatRecorder([tmp])
changed = sd.check()
@ -52,8 +51,8 @@ def test_pycremoval():
assert not pycfile.check()
def test_waitonchange():
tmp = py.test.ensuretemp("test_waitonchange")
def test_waitonchange(tmpdir):
tmp = tmpdir
sd = StatRecorder([tmp])
wp = py._thread.WorkerPool(1)
@ -62,29 +61,4 @@ def test_waitonchange():
tmp.ensure("newfile.py")
reply.get(timeout=0.5)
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()

View File

@ -52,19 +52,3 @@ class StatRecorder:
self.statcache = newstat
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)

View File

@ -139,6 +139,13 @@ def deprecated_call(func, *args, **kwargs):
raise AssertionError("%r did not produce DeprecationWarning" %(func,))
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
EXIT_OK = 0

92
py/test/parseopt.py Normal file
View File

@ -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)

View File

@ -0,0 +1 @@
#

View File

@ -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

View File

@ -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",
])

View File

@ -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."),

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -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",
])

View File

@ -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)

View File

@ -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")

View File

@ -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

View File

@ -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

View File

@ -36,7 +36,7 @@ class PyobjMixin(object):
def _getobj(self):
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. """
chain = self.listchain()
chain.reverse()
@ -46,10 +46,12 @@ class PyobjMixin(object):
continue
name = node.name
if isinstance(node, Module):
if stopatmodule:
break
assert name.endswith(".py")
name = name[:-3]
if stopatmodule:
if includemodule:
parts.append(name)
break
parts.append(name)
parts.reverse()
s = ".".join(parts)
@ -136,14 +138,18 @@ class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
warnoldcollect()
return self.join(name)
def makeitem(self, name, obj, usefilters=True):
if (not usefilters or self.classnamefilter(name)) and \
def makeitem(self, name, obj):
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):
res = self._deprecated_join(name)
if res is not None:
return res
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)
if res is not None:
return res
@ -159,14 +165,23 @@ class Module(py.test.collect.File, PyCollectorMixin):
return super(Module, self).collect()
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):
if not self._config.option.nomagic:
#print "*" * 20, "INVOKE assertion", self
py.magic.invoke(assertion=1)
if hasattr(self.obj, 'setup_module'):
self.obj.setup_module(self.obj)
mod = self.obj
self._config.pytestplugins.register(mod)
if hasattr(mod, 'setup_module'):
self.obj.setup_module(mod)
def teardown(self):
if hasattr(self.obj, 'teardown_module'):
@ -174,6 +189,7 @@ class Module(py.test.collect.File, PyCollectorMixin):
if not self._config.option.nomagic:
#print "*" * 20, "revoke assertion", self
py.magic.revoke(assertion=1)
self._config.pytestplugins.unregister(self.obj)
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):
super(Function, self).__init__(name, parent, config=config)
self._finalizers = []
self._args = args
if callobj is not _dummy:
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):
""" execute the given test function. """
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):
try:
@ -326,50 +385,6 @@ class Function(FunctionMixin, py.test.collect.Item):
def __ne__(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

123
py/test/pytestplugin.py Normal file
View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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 !!!
""")

View File

@ -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)

View File

@ -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))

View File

@ -9,12 +9,14 @@
import py, os, sys
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
import py.__.test.custompdb
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):
self.colitem = colitem
self.getcapture = colitem._config._getcapture
@ -46,35 +48,18 @@ class RobustRun(object):
excinfo = py.code.ExceptionInfo()
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):
def setup(self):
self.colitem._setupstate.prepare(self.colitem)
def teardown(self):
self.colitem._setupstate.teardown_exact(self.colitem)
def execute(self):
#self.colitem.config.pytestplugins.pre_execute(self.colitem)
self.colitem.runtest()
#self.colitem.config.pytestplugins.post_execute(self.colitem)
def makereport(self, res, when, excinfo, outerr):
if excinfo:
kw = self.getkw(when, excinfo, outerr)
else:
kw = {'passed': OutcomeRepr(when, '.', "")}
testrep = event.ItemTestReport(self.colitem, **kw)
testrep = event.ItemTestReport(self.colitem, excinfo, when, outerr)
if self.pdb and testrep.failed:
tw = py.io.TerminalWriter()
testrep.toterminal(tw)
@ -89,26 +74,13 @@ class CollectorRunner(RobustRun):
def execute(self):
return self.colitem._memocollect()
def makereport(self, res, when, excinfo, outerr):
if excinfo:
kw = self.getkw(when, excinfo, outerr)
else:
kw = {'passed': OutcomeRepr(when, '', "")}
return event.CollectionReport(self.colitem, res, **kw)
return event.CollectionReport(self.colitem, res, excinfo, when, outerr)
NORESULT = object()
#
# 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):
return CollectorRunner(item).run()
@ -137,13 +109,12 @@ def forked_run_report(item, pdb=None):
else:
if result.exitstatus == EXITSTATUS_TESTEXIT:
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()
longrepr = [
("X", "CRASHED"),
("%s:%s: CRASHED with signal %d" %(path, lineno, result.signal)),
]
outcomerepr = OutcomeRepr(when="???", shortrepr="X", longrepr=longrepr)
return event.ItemTestReport(item, failed=outcomerepr)
return event.ItemTestReport(item, excinfo=longrepr, when="???")

View File

@ -7,14 +7,10 @@
import py
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()
from py.__.test.outcome import Exit
Item = (py.test.collect.Item, py.test.collect.Item)
Collector = (py.test.collect.Collector, py.test.collect.Collector)
# imports used for genitems()
Item = py.test.collect.Item
Collector = py.test.collect.Collector
from runner import basic_collect_report
from py.__.test.dsession.hostmanage import makehostup
@ -25,20 +21,10 @@ class Session(object):
"""
def __init__(self, config):
self.config = config
self.bus = EventBus()
self.bus = config.bus # shortcut
self.bus.register(self)
self._testsfailed = 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):
""" check, fix and determine conflicting options. """
@ -56,19 +42,24 @@ class Session(object):
""" yield Items from iterating over the given colitems. """
while colitems:
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):
remaining = self.filteritems([next])
if remaining:
self.bus.notify(event.ItemStart(next))
notify("itemstart", event.ItemStart(next))
yield next
else:
assert isinstance(next, Collector)
self.bus.notify(event.CollectionStart(next))
notify("collectionstart", event.CollectionStart(next))
ev = basic_collect_report(next)
if ev.passed:
for x in self.genitems(ev.result, keywordexpr):
yield x
self.bus.notify(ev)
notify("collectionreport", ev)
def filteritems(self, colitems):
""" return items to process (some may be deselected)"""
@ -86,7 +77,7 @@ class Session(object):
continue
remaining.append(colitem)
if deselected:
self.bus.notify(event.Deselected(deselected, ))
self.bus.notify("deselected", event.Deselected(deselected, ))
if self.config.option.keyword.endswith(":"):
self._nomatch = True
return remaining
@ -98,23 +89,19 @@ class Session(object):
def sessionstarts(self):
""" setup any neccessary resources ahead of the test run. """
self.bus.notify(event.TestrunStart())
self._failurelist = []
self.bus.subscribe(self._processfailures)
def _processfailures(self, ev):
if isinstance(ev, event.BaseReport) and ev.failed:
self._failurelist.append(ev)
if self.config.option.exitfirst:
self.shouldstop = True
self.bus.notify("testrunstart", event.TestrunStart())
def pyevent_itemtestreport(self, rep):
if rep.failed:
self._testsfailed = True
if self.config.option.exitfirst:
self.shouldstop = True
pyevent_collectionreport = pyevent_itemtestreport
def sessionfinishes(self, exitstatus=0, excinfo=None):
""" teardown any resources after a test run. """
self.bus.notify(event.TestrunFinish(exitstatus=exitstatus,
excinfo=excinfo))
self.bus.unsubscribe(self._processfailures)
#self.reporter.deactivate()
return self._failurelist
self.bus.notify("testrunfinish",
event.TestrunFinish(exitstatus=exitstatus, excinfo=excinfo))
def getinitialitems(self, colitems):
if colitems is None:
@ -127,7 +114,7 @@ class Session(object):
colitems = self.getinitialitems(colitems)
self.shouldstop = False
self.sessionstarts()
self.bus.notify(makehostup())
self.bus.notify("hostup", makehostup())
exitstatus = outcome.EXIT_OK
captured_excinfo = None
try:
@ -136,24 +123,26 @@ class Session(object):
break
if not self.config.option.collectonly:
self.runtest(item)
py.test.collect.Item._setupstate.teardown_all()
except KeyboardInterrupt:
captured_excinfo = py.code.ExceptionInfo()
exitstatus = outcome.EXIT_INTERRUPTED
except:
self.bus.notify(event.InternalException())
captured_excinfo = py.code.ExceptionInfo()
self.bus.notify("internalerror", event.InternalException(captured_excinfo))
exitstatus = outcome.EXIT_INTERNALERROR
if self._failurelist and exitstatus == 0:
if exitstatus == 0 and self._testsfailed:
exitstatus = outcome.EXIT_TESTSFAILED
self.sessionfinishes(exitstatus=exitstatus, excinfo=captured_excinfo)
return exitstatus
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):
runner = item._getrunner()
pdb = self.config.option.usepdb and self.runpdb or None
testrep = runner(item, pdb=pdb)
self.bus.notify(testrep)
self.bus.notify("itemtestreport", testrep)

View File

@ -1,110 +1,73 @@
import py
from suptest import assert_lines_contain_lines, FileCreation
pydir = py.path.local(py.__file__).dirpath()
pytestpath = pydir.join("bin", "py.test")
EXPECTTIMEOUT=10.0
def setup_module(mod):
mod.modtmpdir = py.test.ensuretemp(mod.__name__)
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="""
class TestPyTest:
def test_assertion_magic(self, testdir):
p = testdir.makepyfile("""
def test_this():
x = 0
assert x
""")
result = self.runpytest(p)
extra = assert_lines_contain_lines(result.outlines, [
result = testdir.runpytest(p)
extra = result.stdout.fnmatch_lines([
"> assert x",
"E assert 0",
])
assert result.ret == 1
def test_collectonly_simple(self):
p = self.makepyfile(test_one="""
def test_collectonly_simple(self, testdir):
p = testdir.makepyfile("""
def test_func1():
pass
class TestClass:
def test_method(self):
pass
""")
result = self.runpytest("--collectonly", p)
err = "".join(result.errlines)
assert err.strip().startswith("inserting into sys.path")
result = testdir.runpytest("--collectonly", p)
stderr = result.stderr.str().strip()
assert stderr.startswith("inserting into sys.path")
assert result.ret == 0
extra = assert_lines_contain_lines(result.outlines, py.code.Source("""
<Module 'test_one.py'>
extra = result.stdout.fnmatch_lines(py.code.Source("""
<Module '*.py'>
<Function 'test_func1'*>
<Class 'TestClass'>
<Instance '()'>
<Function 'test_method'*>
""").strip())
def test_nested_import_error(self):
p = self.makepyfile(
test_one="""
def test_collectonly_error(self, testdir):
p = testdir.makepyfile("import Errlkjqweqwe")
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
def test_this():
assert import_fails.a == 1
""",
import_fails="import does_not_work"
)
result = self.runpytest(p)
extra = assert_lines_contain_lines(result.outlines, [
""")
testdir.makepyfile(import_fails="import does_not_work")
result = testdir.runpytest(p)
extra = result.stdout.fnmatch_lines([
"> import import_fails",
"E ImportError: No module named does_not_work",
])
assert result.ret == 1
def test_skipped_reasons(self):
p1 = self.makepyfile(
def test_skipped_reasons(self, testdir):
testdir.makepyfile(
test_one="""
from conftest import doskip
def setup_function(func):
@ -125,34 +88,34 @@ class TestPyTest(AcceptBase):
py.test.skip('test')
"""
)
result = self.runpytest()
extra = assert_lines_contain_lines(result.outlines, [
result = testdir.runpytest()
extra = result.stdout.fnmatch_lines([
"*test_one.py ss",
"*test_two.py - Skipped*",
"*test_two.py S",
"___* skipped test summary *_",
"*conftest.py:3: *3* Skipped: 'test'",
])
assert result.ret == 0
def test_deselected(self):
p1 = self.makepyfile(test_one="""
def test_deselected(self, testdir):
testpath = testdir.makepyfile("""
def test_one():
pass
def test_two():
pass
def test_three():
pass
""",
"""
)
result = self.runpytest("-k", "test_two:")
extra = assert_lines_contain_lines(result.outlines, [
"*test_one.py ..",
result = testdir.runpytest("-k", "test_two:", testpath)
extra = result.stdout.fnmatch_lines([
"*test_deselected.py ..",
"=* 1 test*deselected by 'test_two:'*=",
])
assert result.ret == 0
def test_no_skip_summary_if_failure(self):
p1 = self.makepyfile(test_one="""
def test_no_skip_summary_if_failure(self, testdir):
testdir.makepyfile("""
import py
def test_ok():
pass
@ -161,12 +124,12 @@ class TestPyTest(AcceptBase):
def test_skip():
py.test.skip("dontshow")
""")
result = self.runpytest()
assert str(result.outlines).find("skip test summary") == -1
result = testdir.runpytest()
assert result.stdout.str().find("skip test summary") == -1
assert result.ret == 1
def test_passes(self):
p1 = self.makepyfile(test_one="""
def test_passes(self, testdir):
p1 = testdir.makepyfile("""
def test_passes():
pass
class TestClass:
@ -175,33 +138,32 @@ class TestPyTest(AcceptBase):
""")
old = p1.dirpath().chdir()
try:
result = self.runpytest()
result = testdir.runpytest()
finally:
old.chdir()
extra = assert_lines_contain_lines(result.outlines, [
"test_one.py ..",
"* failures: no failures*",
extra = result.stdout.fnmatch_lines([
"test_passes.py ..",
"* 2 pass*",
])
assert result.ret == 0
def test_header_trailer_info(self):
p1 = self.makepyfile(test_one="""
def test_header_trailer_info(self, testdir):
p1 = testdir.makepyfile("""
def test_passes():
pass
""")
result = self.runpytest()
result = testdir.runpytest()
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 ====*",
"*localhost* %s %s - Python %s*" %(
py.std.sys.platform, py.std.sys.executable, verinfo),
"*test_one.py .",
"=* 1/1 passed + 0 skips in *.[0-9][0-9] seconds *=",
"=* no failures :)*=",
"*test_header_trailer_info.py .",
"=* 1 passed in *.[0-9][0-9] seconds *=",
])
def test_traceback_failure(self):
p1 = self.makepyfile(test_fail="""
def test_traceback_failure(self, testdir):
p1 = testdir.makepyfile("""
def g():
return 2
def f(x):
@ -209,16 +171,16 @@ class TestPyTest(AcceptBase):
def test_onefails():
f(3)
""")
result = self.runpytest(p1)
assert_lines_contain_lines(result.outlines, [
"*test_fail.py F",
result = testdir.runpytest(p1)
result.stdout.fnmatch_lines([
"*test_traceback_failure.py F",
"====* FAILURES *====",
"____*____",
"",
" def test_onefails():",
"> f(3)",
"",
"*test_fail.py:6: ",
"*test_*.py:6: ",
"_ _ _ *",
#"",
" def f(x):",
@ -226,11 +188,11 @@ class TestPyTest(AcceptBase):
"E assert 3 == 2",
"E + where 2 = g()",
"",
"*test_fail.py:4: AssertionError"
"*test_traceback_failure.py:4: AssertionError"
])
def test_capturing_outerr(self):
p1 = self.makepyfile(test_one="""
def test_capturing_outerr(self, testdir):
p1 = testdir.makepyfile("""
import sys
def test_capturing():
print 42
@ -240,52 +202,35 @@ class TestPyTest(AcceptBase):
print >>sys.stderr, 2
raise ValueError
""")
result = self.runpytest(p1)
assert_lines_contain_lines(result.outlines, [
"*test_one.py .F",
result = testdir.runpytest(p1)
result.stdout.fnmatch_lines([
"*test_capturing_outerr.py .F",
"====* FAILURES *====",
"____*____",
"*test_one.py:8: ValueError",
"*test_capturing_outerr.py:8: ValueError",
"*--- Captured stdout ---*",
"1",
"*--- Captured stderr ---*",
"2",
])
def test_showlocals(self):
p1 = self.makepyfile(test_one="""
def test_showlocals(self, testdir):
p1 = testdir.makepyfile("""
def test_showlocals():
x = 3
y = "x" * 5000
assert 0
""")
result = self.runpytest(p1, '-l')
assert_lines_contain_lines(result.outlines, [
result = testdir.runpytest(p1, '-l')
result.stdout.fnmatch_lines([
#"_ _ * Locals *",
"x* = 3",
"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):
p1 = self.makepyfile(
test_one="""
def test_dist_testing(self, testdir):
p1 = testdir.makepyfile("""
import py
def test_fail0():
assert 0
@ -300,22 +245,20 @@ class TestPyTest(AcceptBase):
dist_hosts = ['localhost'] * 3
"""
)
result = self.runpytest(p1, '-d')
assert_lines_contain_lines(result.outlines, [
result = testdir.runpytest(p1, '-d')
result.stdout.fnmatch_lines([
"HOSTUP: localhost*Python*",
#"HOSTUP: localhost*Python*",
#"HOSTUP: localhost*Python*",
"*1/3 passed + 1 skip*",
"*failures: 2*",
"*2 failed, 1 passed, 1 skipped*",
])
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'):
py.test.skip("no os.kill")
p1 = self.makepyfile(
test_one="""
p1 = testdir.makepyfile("""
import py
def test_fail0():
assert 0
@ -335,34 +278,33 @@ class TestPyTest(AcceptBase):
dist_hosts = ['localhost'] * 3
"""
)
result = self.runpytest(p1, '-d')
assert_lines_contain_lines(result.outlines, [
result = testdir.runpytest(p1, '-d')
result.stdout.fnmatch_lines([
"*localhost*Python*",
"*localhost*Python*",
"*localhost*Python*",
"HostDown*localhost*TERMINATED*",
"*1/4 passed + 1 skip*",
"*failures: 3*",
"*3 failed, 1 passed, 1 skipped*"
])
assert result.ret == 1
def test_keyboard_interrupt(self):
p1 = self.makepyfile(test_one="""
def test_keyboard_interrupt(self, testdir):
p1 = testdir.makepyfile("""
import py
def test_fail():
raise ValueError()
def test_inter():
raise KeyboardInterrupt()
""")
result = self.runpytest(p1)
assert_lines_contain_lines(result.outlines, [
result = testdir.runpytest(p1)
result.stdout.fnmatch_lines([
#"*test_inter() INTERRUPTED",
"*KEYBOARD INTERRUPT*",
"*0/1 passed*",
"*1 failed*",
])
def test_verbose_reporting(self):
p1 = self.makepyfile(test_one="""
def test_verbose_reporting(self, testdir):
p1 = testdir.makepyfile("""
import py
def test_fail():
raise ValueError()
@ -376,20 +318,20 @@ class TestPyTest(AcceptBase):
assert x == 1
yield check, 0
""")
result = self.runpytest(p1, '-v')
assert_lines_contain_lines(result.outlines, [
"*test_one.py:2: test_fail*FAIL",
"*test_one.py:4: test_pass*PASS",
"*test_one.py:7: TestClass.test_skip*SKIP",
"*test_one.py:10: test_gen*FAIL",
result = testdir.runpytest(p1, '-v')
result.stdout.fnmatch_lines([
"*test_verbose_reporting.py:2: test_fail*FAIL",
"*test_verbose_reporting.py:4: test_pass*PASS",
"*test_verbose_reporting.py:7: TestClass.test_skip*SKIP",
"*test_verbose_reporting.py:10: test_gen*FAIL",
])
assert result.ret == 1
class TestInteractive(AcceptBase):
def getspawn(self):
class TestInteractive:
def getspawn(self, tmpdir):
pexpect = py.test.importorskip("pexpect")
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
def requirespexpect(self, version_needed):
@ -398,45 +340,43 @@ class TestInteractive(AcceptBase):
if ver < 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))
spawn = self.getspawn()
self.makepyfile(test_one="""
spawn = self.getspawn(testdir.tmpdir)
p1 = testdir.makepyfile("""
def test_1():
#hello
assert 1 == 0
i = 0
assert i == 1
""")
child = spawn("%s %s --pdb test_one.py" % (py.std.sys.executable,
pytestpath))
child = spawn("%s %s --pdb %s" % (py.std.sys.executable, pytestpath, p1))
child.timeout = EXPECTTIMEOUT
child.expect(".*def test_1.*")
child.expect(".*hello.*")
#child.expect(".*def test_1.*")
child.expect(".*i = 0.*")
child.expect("(Pdb)")
child.sendeof()
child.expect("failures: 1")
child.expect("1 failed")
if child.isalive():
child.wait()
def test_simple_looponfailing_interaction(self):
spawn = self.getspawn()
test_one = self.makepyfile(test_one="""
def test_simple_looponfailing_interaction(self, testdir):
spawn = self.getspawn(testdir.tmpdir)
p1 = testdir.makepyfile("""
def test_1():
assert 1 == 0
""")
test_one.setmtime(test_one.mtime() - 5.0)
child = spawn("%s %s --looponfailing test_one.py" % (py.std.sys.executable,
str(pytestpath)))
p1.setmtime(p1.mtime() - 50.0)
child = spawn("%s %s --looponfailing %s" % (py.std.sys.executable, pytestpath, p1))
child.timeout = EXPECTTIMEOUT
child.expect("assert 1 == 0")
child.expect("test_one.py:")
child.expect("failures: 1")
child.expect("test_simple_looponfailing_interaction.py:")
child.expect("1 failed")
child.expect("waiting for changes")
test_one.write(py.code.Source("""
p1.write(py.code.Source("""
def test_1():
assert 1 == 1
"""))
child.expect("MODIFIED.*test_one.py", timeout=4.0)
child.expect("failures: no failures", timeout=5.0)
child.expect("MODIFIED.*test_simple_looponfailing_interaction.py", timeout=4.0)
child.expect("1 passed", timeout=5.0)
child.kill(15)

View File

@ -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