[svn r57754] * introduce py.test.collect.File (File for py or non-py files)

* introduce py.test.collect.Collector.collect_by_name and
  special case it for Directories to allow specifying
  files that would otherwise be ignored because of filters.
* fix py/doc/conftest to work with new API
* refactor py/doc/test_conftest.py to use suptest helper
* avoid old APIs in some more places.

--HG--
branch : trunk
This commit is contained in:
hpk 2008-09-02 14:24:15 +02:00
parent 494ea31042
commit 561a14054c
10 changed files with 158 additions and 189 deletions

View File

@ -26,8 +26,8 @@ version = "1.0.0a1"
initpkg(__name__,
description = "pylib and py.test: agile development and test support library",
revision = int('$LastChangedRevision: 57551 $'.split(':')[1][:-1]),
lastchangedate = '$LastChangedDate: 2008-08-21 15:25:29 +0200 (Thu, 21 Aug 2008) $',
revision = int('$LastChangedRevision: 57754 $'.split(':')[1][:-1]),
lastchangedate = '$LastChangedDate: 2008-09-02 14:24:15 +0200 (Tue, 02 Sep 2008) $',
version = version,
url = "http://pylib.org",
download_url = "http://codespeak.net/py/0.9.2/download.html",
@ -78,12 +78,13 @@ initpkg(__name__,
# for customization of collecting/running tests
'test.collect.Collector' : ('./test/collect.py', 'Collector'),
'test.collect.Directory' : ('./test/collect.py', 'Directory'),
'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'),
'test.collect.Item' : ('./test/collect.py', 'Item'),
'test.collect.Function' : ('./test/pycollect.py', 'Function'),
# thread related API (still in early design phase)

View File

@ -1,5 +1,5 @@
import py
class Directory(py.test.collect.Directory):
def listdir(self):
py.test.skip("compat tests currently need to be run manually")
def collect(self):
py.test.skip("compat tests need to be run manually")

View File

@ -43,15 +43,13 @@ py.apigen
- make it work again
- py.apigen tool -> separate runtime-data collection and
web page generation. (see M750), provide "py.apigen" tool
- refactor html renderer to work on intermediate
data/files rather than on the live data
see apigen_refactorings.txt
- check out CodeInvestigator
http://codeinvestigator.googlepages.com/main
or other code that collects data from running a program
(in our case running the tests)
ld (review and shift to above)
=================================

View File

@ -110,12 +110,12 @@ def _checkskip(lpath, htmlpath=None):
#return [] # no need to rebuild
class ReSTSyntaxTest(py.test.collect.Item):
def execute(self):
def runtest(self):
mypath = self.fspath
restcheck(py.path.svnwc(mypath))
class DoctestText(py.test.collect.Item):
def execute(self):
def runtest(self):
s = self._normalize_linesep()
l = []
prefix = '.. >>> '
@ -158,20 +158,15 @@ class DoctestText(py.test.collect.Item):
return s
class LinkCheckerMaker(py.test.collect.Collector):
def listdir(self):
l = []
def collect(self):
l = []
for call, tryfn, path, lineno in genlinkchecks(self.fspath):
l.append("%s:%d" %(tryfn, lineno))
name = "%s:%d" %(tryfn, lineno)
l.append(
CheckLink(name, parent=self, args=(tryfn, path, lineno), callobj=call)
)
return l
def join(self, name):
i = name.rfind(':')
assert i != -1
jname, jlineno = name[:i], int(name[i+1:])
for call, tryfn, path, lineno in genlinkchecks(self.fspath):
if tryfn == jname and lineno == jlineno:
return CheckLink(name, parent=self, args=(tryfn, path, lineno), callobj=call)
class CheckLink(py.test.collect.Function):
def repr_metainfo(self):
return self.ReprMetaInfo(fspath=self.fspath, lineno=self._args[2],
@ -181,26 +176,17 @@ class CheckLink(py.test.collect.Function):
def teardown(self):
pass
class ReSTChecker(py.test.collect.Module):
class DocfileTests(py.test.collect.File):
DoctestText = DoctestText
ReSTSyntaxTest = ReSTSyntaxTest
LinkCheckerMaker = LinkCheckerMaker
def __repr__(self):
return py.test.collect.Collector.__repr__(self)
def setup(self):
pass
def teardown(self):
pass
def listdir(self):
return [self.fspath.basename, 'checklinks', 'doctest']
def join(self, name):
if name == self.fspath.basename:
return self.ReSTSyntaxTest(name, parent=self)
elif name == 'checklinks':
return LinkCheckerMaker(name, self)
elif name == 'doctest':
return self.DoctestText(name, self)
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):
@ -286,20 +272,13 @@ def localrefcheck(tryfn, path, lineno):
# hooking into py.test Directory collector's chain ...
class DocDirectory(py.test.collect.Directory):
ReSTChecker = ReSTChecker
def listdir(self):
results = super(DocDirectory, self).listdir()
DocfileTests = DocfileTests
def collect(self):
results = super(DocDirectory, self).collect()
for x in self.fspath.listdir('*.txt', sort=True):
results.append(x.basename)
results.append(self.DocfileTests(x, parent=self))
return results
def join(self, name):
if not name.endswith('.txt'):
return super(DocDirectory, self).join(name)
p = self.fspath.join(name)
if p.check(file=1):
return self.ReSTChecker(p, parent=self)
Directory = DocDirectory
def resolve_linkrole(name, text, check=True):

View File

@ -1,11 +1,15 @@
import py
failure_demo = py.magic.autopath().dirpath('failure_demo.py')
from py.__.doc.test_conftest import countoutcomes
from py.__.test.testing import suptest
from py.__.test import event
def test_failure_demo_fails_properly():
config = py.test.config._reparse([failure_demo])
session = config.initsession()
failed, passed, skipped = countoutcomes(session)
assert failed == 21
sorter = suptest.events_from_cmdline([failure_demo])
passed, skipped, failed = sorter.countoutcomes()
assert passed == 0
assert failed == 20, failed
colreports = sorter.get(event.CollectionReport)
failed = len([x.failed for x in colreports])
assert failed == 5

View File

@ -1,74 +1,80 @@
import py
from py.__.test import event
from py.__.test.testing import suptest
from py.__.doc import conftest as doc_conftest
def setup_module(mod):
mod.tmpdir = py.test.ensuretemp('docdoctest')
def countoutcomes(session):
l = []
session.bus.subscribe(l.append)
session.main()
session.bus.unsubscribe(l.append)
passed = failed = skipped = 0
for ev in l:
if isinstance(ev, event.ItemTestReport):
if ev.passed:
passed += 1
elif ev.skipped:
skipped += 1
else:
failed += 1
elif isinstance(ev, event.CollectionReport):
if ev.failed:
failed += 1
return failed, passed, skipped
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_extra_exec():
# XXX get rid of the next line:
py.magic.autopath().dirpath('conftest.py').copy(tmpdir.join('conftest.py'))
xtxt = tmpdir.join('y.txt')
xtxt.write(py.code.Source("""
hello::
.. >>> raise ValueError
>>> None
"""))
config = py.test.config._reparse([xtxt])
session = config.initsession()
failed, passed, skipped = countoutcomes(session)
assert failed == 1
def test_doctest_basic(self):
xtxt = self.maketxtfile(x="""
..
>>> from os.path import abspath
def test_doctest_basic():
# XXX get rid of the next line:
py.magic.autopath().dirpath('conftest.py').copy(tmpdir.join('conftest.py'))
hello world
xtxt = tmpdir.join('x.txt')
xtxt.write(py.code.Source("""
..
>>> from os.path import abspath
>>> assert abspath
>>> i=3
>>> print i
3
hello world
yes yes
>>> assert abspath
>>> i=3
>>> print i
3
>>> i
3
yes yes
end
""")
sorter = suptest.events_from_cmdline([xtxt])
passed, skipped, failed = sorter.countoutcomes()
assert failed == 0
assert passed + skipped == 2
>>> i
3
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
end
"""))
config = py.test.config._reparse([xtxt])
session = config.initsession()
failed, passed, skipped = countoutcomes(session)
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():
from py.__.doc.conftest import 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'
@ -76,45 +82,6 @@ def test_deindent():
assert deindent(' foo\n bar\n') == 'foo\n bar\n'
assert deindent(' foo\n bar\n') == ' foo\nbar\n'
def test_doctest_eol():
# XXX get rid of the next line:
py.magic.autopath().dirpath('conftest.py').copy(tmpdir.join('conftest.py'))
ytxt = tmpdir.join('y.txt')
ytxt.write(py.code.Source(".. >>> 1 + 1\r\n 2\r\n\r\n"))
config = py.test.config._reparse([ytxt])
session = config.initsession()
failed, passed, skipped = countoutcomes(session)
assert failed == 0
assert passed + skipped == 2
def test_doctest_indentation():
# XXX get rid of the next line:
py.magic.autopath().dirpath('conftest.py').copy(tmpdir.join('conftest.py'))
txt = tmpdir.join('foo.txt')
txt.write('..\n >>> print "foo\\n bar"\n foo\n bar\n')
config = py.test.config._reparse([txt])
session = config.initsession()
failed, passed, skipped = countoutcomes(session)
assert failed == 0
assert skipped + passed == 2
def test_js_ignore():
py.magic.autopath().dirpath('conftest.py').copy(tmpdir.join('conftest.py'))
tmpdir.ensure('__init__.py')
xtxt = tmpdir.join('x.txt')
xtxt.write(py.code.Source("""
`blah`_
.. _`blah`: javascript:some_function()
"""))
config = py.test.config._reparse([xtxt])
session = config.initsession()
failed, passed, skipped = countoutcomes(session)
assert failed == 0
assert skipped + passed == 3
def test_resolve_linkrole():
from py.__.doc.conftest import get_apigen_relpath

View File

@ -1,8 +1,8 @@
import py, os
class Directory(py.test.collect.Directory):
def listdir(self):
def collect(self):
if os.name == 'nt':
py.test.skip("Cannot test green layer on windows")
else:
return super(Directory, self).listdir()
return super(Directory, self).run()

View File

@ -189,7 +189,7 @@ class Node(object):
cur = self
for name in namelist:
if name:
next = cur.join(name)
next = cur.collect_by_name(name)
if next is None:
existingnames = [x.name for x in self._memocollect()]
msg = ("Collector %r does not have name %r "
@ -297,6 +297,12 @@ class Collector(Node):
"""
raise NotImplementedError("abstract")
def collect_by_name(self, name):
""" return a child matching the given name, else None. """
for colitem in self._memocollect():
if colitem.name == name:
return colitem
def repr_failure(self, excinfo, outerr):
""" represent a failure. """
return self._repr_failure_py(excinfo, outerr)
@ -336,12 +342,10 @@ class Collector(Node):
If the return value is None there is no such child.
"""
warnoldcollect()
for colitem in self._memocollect():
if colitem.name == name:
return colitem
return self.collect_by_name(name)
def multijoin(self, namelist):
""" DEPRECATED: return a list of colitems for the given namelist. """
""" DEPRECATED: return a list of child items matching the given namelist. """
warnoldcollect()
return [self.join(name) for name in namelist]
@ -380,6 +384,8 @@ class FSCollector(Collector):
name, parent = picklestate
self.__init__(parent.fspath.join(name), parent=parent)
class File(FSCollector):
""" base class for collecting tests from a file. """
class Directory(FSCollector):
def recfilter(self, path):
@ -398,7 +404,6 @@ class Directory(FSCollector):
return l
def consider(self, path, usefilters=True):
print "checking", path
if path.check(file=1):
return self.consider_file(path, usefilters=usefilters)
elif path.check(dir=1):
@ -421,14 +426,13 @@ class Directory(FSCollector):
Directory = self._config.getvalue('Directory', path)
return Directory(path, parent=self)
# **********************************************************************
# DEPRECATED METHODS
# **********************************************************************
def join(self, name):
""" get a child collector or item without using filters. """
p = self.fspath.join(name)
return self.consider(p, usefilters=False)
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
from py.__.test.runner import basic_run_report, forked_run_report
class Item(Node):

View File

@ -2,7 +2,7 @@
Python related collection nodes. Here is an example of
a tree of collectors and test items that this modules provides::
Module # FSCollector
Module # File
Class
Instance
Function
@ -12,13 +12,12 @@ a tree of collectors and test items that this modules provides::
Generator
Function
DoctestFile # FSCollector
DoctestFile # File
DoctestFileContent # acts as Item
"""
import py
from py.__.test.collect import Collector, FSCollector, Item, configproperty
from py.__.test.collect import warnoldcollect
from py.__.test.collect import configproperty, warnoldcollect
class PyobjMixin(object):
def obj():
@ -86,7 +85,7 @@ class PyobjMixin(object):
)
class PyCollectorMixin(PyobjMixin, Collector):
class PyCollectorMixin(PyobjMixin, py.test.collect.Collector):
Class = configproperty('Class')
Instance = configproperty('Instance')
Function = configproperty('Function')
@ -144,9 +143,7 @@ class PyCollectorMixin(PyobjMixin, Collector):
else:
return self.Function(name, parent=self)
class Module(FSCollector, PyCollectorMixin):
_stickyfailure = None
class Module(py.test.collect.File, PyCollectorMixin):
def collect(self):
if getattr(self.obj, 'disabled', 0):
return []
@ -169,7 +166,7 @@ class Module(FSCollector, PyCollectorMixin):
#print "*" * 20, "revoke assertion", self
py.magic.revoke(assertion=1)
class Class(PyCollectorMixin, Collector):
class Class(PyCollectorMixin, py.test.collect.Collector):
def collect(self):
if getattr(self.obj, 'disabled', 0):
@ -194,7 +191,7 @@ class Class(PyCollectorMixin, Collector):
def _getsortvalue(self):
return self.getfslineno()
class Instance(PyCollectorMixin, Collector):
class Instance(PyCollectorMixin, py.test.collect.Collector):
def _getobj(self):
return self.parent.obj()
def Function(self):
@ -259,7 +256,7 @@ class FunctionMixin(PyobjMixin):
shortfailurerepr = "F"
class Generator(FunctionMixin, PyCollectorMixin, Collector):
class Generator(FunctionMixin, PyCollectorMixin, py.test.collect.Collector):
def collect(self):
# test generators are collectors yet participate in
# the test-item setup and teardown protocol.
@ -285,7 +282,7 @@ class Generator(FunctionMixin, PyCollectorMixin, Collector):
# Test Items
#
_dummy = object()
class Function(FunctionMixin, Item):
class Function(FunctionMixin, py.test.collect.Item):
""" a Function Item is responsible for setting up
and executing a Python callable test object.
"""
@ -312,7 +309,7 @@ class Function(FunctionMixin, Item):
def __ne__(self, other):
return not self == other
class DoctestFile(FSCollector):
class DoctestFile(py.test.collect.File):
def collect(self):
return [DoctestFileContent(self.fspath.basename, parent=self)]
@ -328,7 +325,7 @@ class ReprFailDoctest(Repr):
tw.line(line)
self.reprlocation.toterminal(tw)
class DoctestFileContent(Item):
class DoctestFileContent(py.test.collect.Item):
def repr_failure(self, excinfo, outerr):
if excinfo.errisinstance(py.compat.doctest.DocTestFailure):
doctestfailure = excinfo.value

View File

@ -59,11 +59,31 @@ class TestCollect(suptest.InlineCollection):
def test_listnames_and__getitembynames(self):
modcol = self.getmodulecol("pass")
names = modcol.listnames()
dircol = py.test.collect.Directory(modcol._config.topdir, config=modcol._config)
dircol = modcol._config.getfsnode(modcol._config.topdir)
x = dircol._getitembynames(names)
assert modcol.name == x.name
assert modcol.name == x.name
def test_listnames_getitembynames_custom(self):
hello = self._makefile(".xxx", hello="world")
self.makepyfile(conftest="""
import py
class CustomFile(py.test.collect.File):
pass
class MyDirectory(py.test.collect.Directory):
def collect(self):
return [CustomFile(self.fspath.join("hello.xxx"), parent=self)]
Directory = MyDirectory
""")
config = self.parseconfig(hello)
node = config.getfsnode(hello)
assert isinstance(node, py.test.collect.File)
assert node.name == "hello.xxx"
names = node.listnames()[1:]
dircol = config.getfsnode(config.topdir)
node = dircol._getitembynames(names)
assert isinstance(node, py.test.collect.File)
def test_found_certain_testfiles(self):
p1 = self.makepyfile(test_found = "pass", found_test="pass")
col = py.test.collect.Directory(p1.dirpath(), config=dummyconfig)
@ -156,9 +176,9 @@ class TestCollect(suptest.InlineCollection):
def test_pass(): pass
def test_fail(): assert 0
""")
fn1 = modcol.join("test_pass")
fn1 = modcol.collect_by_name("test_pass")
assert isinstance(fn1, py.test.collect.Function)
fn2 = modcol.join("test_pass")
fn2 = modcol.collect_by_name("test_pass")
assert isinstance(fn2, py.test.collect.Function)
assert fn1 == fn2
@ -166,7 +186,7 @@ class TestCollect(suptest.InlineCollection):
assert cmp(fn1, fn2) == 0
assert hash(fn1) == hash(fn2)
fn3 = modcol.join("test_fail")
fn3 = modcol.collect_by_name("test_fail")
assert isinstance(fn3, py.test.collect.Function)
assert not (fn1 == fn3)
assert fn1 != fn3
@ -274,7 +294,6 @@ class TestCustomConftests(suptest.InlineCollection):
assert item.name == "hello.xxx"
assert item.__class__.__name__ == "CustomItem"
def test_module_file_not_found():
fn = tmpdir.join('nada','no')
col = py.test.collect.Module(fn, config=dummyconfig)
@ -513,7 +532,7 @@ class TestCollectorReprs(suptest.InlineCollection):
class TestClass:
def test_hello(self): pass
""")
classcol = modcol.join("TestClass")
classcol = modcol.collect_by_name("TestClass")
info = classcol.repr_metainfo()
assert info.fspath == modcol.fspath
assert info.lineno == 1
@ -527,7 +546,7 @@ class TestCollectorReprs(suptest.InlineCollection):
assert x
yield check, 3
""")
gencol = modcol.join("test_gen")
gencol = modcol.collect_by_name("test_gen")
info = gencol.repr_metainfo()
assert info.fspath == modcol.fspath
assert info.lineno == 1
@ -595,7 +614,7 @@ class TestPickling(suptest.InlineCollection):
def test_pickle_func(self):
modcol1 = self.getmodulecol("def test_one(): pass")
self.unifyconfig(modcol1._config)
item = modcol1.join("test_one")
item = modcol1.collect_by_name("test_one")
item2a = self.p1_to_p2(item)
assert item is not item2a # of course
assert item2a.name == item.name