diff --git a/py/__init__.py b/py/__init__.py index 3e582729d..dd0d1d03c 100644 --- a/py/__init__.py +++ b/py/__init__.py @@ -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) diff --git a/py/compat/conftest.py b/py/compat/conftest.py index fccc62bce..444089a72 100644 --- a/py/compat/conftest.py +++ b/py/compat/conftest.py @@ -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") diff --git a/py/doc/TODO.txt b/py/doc/TODO.txt index 9007ce8c5..b521ca843 100644 --- a/py/doc/TODO.txt +++ b/py/doc/TODO.txt @@ -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) ================================= diff --git a/py/doc/conftest.py b/py/doc/conftest.py index 07803898a..aa8747abb 100644 --- a/py/doc/conftest.py +++ b/py/doc/conftest.py @@ -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): diff --git a/py/doc/example/pytest/test_failures.py b/py/doc/example/pytest/test_failures.py index f26957c20..f692af61c 100644 --- a/py/doc/example/pytest/test_failures.py +++ b/py/doc/example/pytest/test_failures.py @@ -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 diff --git a/py/doc/test_conftest.py b/py/doc/test_conftest.py index 479392167..40eb13053 100644 --- a/py/doc/test_conftest.py +++ b/py/doc/test_conftest.py @@ -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 diff --git a/py/green/conftest.py b/py/green/conftest.py index 753f85c3d..63e5718f1 100644 --- a/py/green/conftest.py +++ b/py/green/conftest.py @@ -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() diff --git a/py/test/collect.py b/py/test/collect.py index d2041a357..f5ef307ad 100644 --- a/py/test/collect.py +++ b/py/test/collect.py @@ -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): diff --git a/py/test/pycollect.py b/py/test/pycollect.py index e35c93354..01469a7cd 100644 --- a/py/test/pycollect.py +++ b/py/test/pycollect.py @@ -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 diff --git a/py/test/testing/test_collect.py b/py/test/testing/test_collect.py index f277e67dc..54bc93261 100644 --- a/py/test/testing/test_collect.py +++ b/py/test/testing/test_collect.py @@ -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