From ba6eca8da44d7c14ef17e2f6ce567a7d26805692 Mon Sep 17 00:00:00 2001 From: hpk Date: Sat, 4 Apr 2009 21:06:20 +0200 Subject: [PATCH] [svn r63628] * shuffle SetupState and fixture handling into runner.py * introduce a itemsetupreport and new setupitem/teardownitem methods. * more tests --HG-- branch : trunk --- py/test/config.py | 29 +------- py/test/plugin/api.py | 3 + py/test/plugin/pytest_pytester.py | 32 +++++---- py/test/runner.py | 72 ++++++++++++++++++- ...up_nested.py => test_fixture_and_setup.py} | 71 ++++++++++++++++++ 5 files changed, 162 insertions(+), 45 deletions(-) rename py/test/testing/{test_setup_nested.py => test_fixture_and_setup.py} (65%) diff --git a/py/test/config.py b/py/test/config.py index e33ae08de..9e37f6a52 100644 --- a/py/test/config.py +++ b/py/test/config.py @@ -3,6 +3,7 @@ from conftesthandle import Conftest from py.__.test import parseopt from py.__.misc.warn import APIWARN +from py.__.test.runner import SetupState def ensuretemp(string, dir=1): """ return temporary directory path with @@ -312,34 +313,6 @@ def gettopdir(args): else: return pkgdir.dirpath() -class SetupState(object): - """ shared state for setting up/tearing down test items or collectors. """ - def __init__(self): - self.stack = [] - - def teardown_all(self): - while self.stack: - col = self.stack.pop() - col.teardown() - - def teardown_exact(self, item): - if self.stack and self.stack[-1] == item: - col = self.stack.pop() - col.teardown() - - def prepare(self, colitem): - """ setup objects along the collector chain to the test-method - Teardown any unneccessary previously setup objects.""" - - needed_collectors = colitem.listchain() - while self.stack: - if self.stack == needed_collectors[:len(self.stack)]: - break - col = self.stack.pop() - col.teardown() - for col in needed_collectors[len(self.stack):]: - col.setup() - self.stack.append(col) # this is the one per-process instance of py.test configuration config_per_process = Config( diff --git a/py/test/plugin/api.py b/py/test/plugin/api.py index 305878e13..b8b6d9965 100644 --- a/py/test/plugin/api.py +++ b/py/test/plugin/api.py @@ -85,6 +85,9 @@ class Events: def pyevent__itemtestreport(self, event): """ test has been run. """ + def pyevent__itemsetupreport(self, rep): + """ test has been run. """ + def pyevent__deselected(self, items): """ collected items that were deselected (by keyword). """ diff --git a/py/test/plugin/pytest_pytester.py b/py/test/plugin/pytest_pytester.py index b728a384a..6031116bb 100644 --- a/py/test/plugin/pytest_pytester.py +++ b/py/test/plugin/pytest_pytester.py @@ -74,6 +74,11 @@ class TmpTestdir: if hasattr(self, '_olddir'): self._olddir.chdir() + def geteventrecorder(self, config): + evrec = EventRecorder(config.bus) + self.pyfuncitem.addfinalizer(lambda: config.bus.unregister(evrec)) + return evrec + def chdir(self): old = self.tmpdir.chdir() if not hasattr(self, '_olddir'): @@ -174,9 +179,12 @@ class TmpTestdir: 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 + moditems = modcol.collect() + for item in modcol.collect(): + if item.name == funcname: + return item + else: + assert 0, "%r item not found in module:\n%s" %(funcname, source) def getitems(self, source): modcol = self.getmodulecol(source) @@ -284,12 +292,13 @@ class EventRecorder(object): for i, event in py.builtin.enumerate(self.events): if event.name == name: del self.events[i] - return event + eventparser = self.geteventparser(name) + return eventparser(*event.args, **event.kwargs) raise KeyError("popevent: %r not found in %r" %(name, self.events)) def getevents(self, eventname): """ return list of ParsedEvent instances matching the given eventname. """ - method = self.geteventmethod(eventname) + method = self.geteventparser(eventname) l = [] for event in self.events: if event.name == eventname: @@ -297,23 +306,18 @@ class EventRecorder(object): l.append(pevent) return l - def geteventmethod(self, eventname): + def geteventparser(self, eventname): mname = "pyevent__" + eventname method = getattr(api.Events, mname) args, varargs, varkw, default = inspect.getargspec(method) assert args[0] == "self" args = args[1:] fspec = inspect.formatargspec(args, varargs, varkw, default) - source = """def %(mname)s%(fspec)s: - return ParsedEvent(locals())""" % locals() - print source - exec source + code = py.code.compile("""def %(mname)s%(fspec)s: + return ParsedEvent(locals())""" % locals()) + exec code return locals()[mname] - - def firstparsedevent(self, eventname): - return self.parsedevents(eventname)[0] - def get(self, cls): l = [] for event in self.events: diff --git a/py/test/runner.py b/py/test/runner.py index 6810eda1c..c073d131b 100644 --- a/py/test/runner.py +++ b/py/test/runner.py @@ -6,7 +6,7 @@ * and generating report events about it """ -import py, os, sys +import py from py.__.test.outcome import Exit, Skipped @@ -55,7 +55,6 @@ def basic_collect_report(collector): return CollectionReport(collector, res, excinfo, outerr) from cPickle import Pickler, Unpickler -from cStringIO import StringIO def forked_run_report(item, pdb=None): EXITSTATUS_TESTEXIT = 4 @@ -66,7 +65,7 @@ def forked_run_report(item, pdb=None): try: testrep = basic_run_report(item) except (KeyboardInterrupt, Exit): - os._exit(EXITSTATUS_TESTEXIT) + py.std.os._exit(EXITSTATUS_TESTEXIT) return ipickle.dumps(testrep) ff = py.process.ForkedFunc(runforked) @@ -164,3 +163,70 @@ class CollectionReport(BaseReport): else: out.line(str(longrepr)) +class ItemSetupReport(BaseReport): + """ Test Execution Report. """ + failed = passed = skipped = False + + def __init__(self, item, excinfo=None, outerr=None): + self.item = item + self.outerr = outerr + if not excinfo: + self.passed = True + else: + if excinfo.errisinstance(Skipped): + self.skipped = True + else: + self.failed = True + self.excrepr = item._repr_failure_py(excinfo, outerr) + +class SetupState(object): + """ shared state for setting up/tearing down test items or collectors. """ + def __init__(self): + self.stack = [] + + def teardown_all(self): + while self.stack: + col = self.stack.pop() + col.teardown() + + def teardown_exact(self, item): + if self.stack and self.stack[-1] == item: + col = self.stack.pop() + col.teardown() + + def prepare(self, colitem): + """ setup objects along the collector chain to the test-method + Teardown any unneccessary previously setup objects.""" + needed_collectors = colitem.listchain() + while self.stack: + if self.stack == needed_collectors[:len(self.stack)]: + break + col = self.stack.pop() + col.teardown() + for col in needed_collectors[len(self.stack):]: + col.setup() + self.stack.append(col) + + def fixturecall(self, callable, item): + excinfo = None + capture = item.config._getcapture() + try: + try: + callable(item) + except (KeyboardInterrupt, SystemExit): + raise + except: + excinfo = py.code.ExceptionInfo() + finally: + outerr = capture.reset() + if not excinfo: + return True + else: + rep = ItemSetupReport(item, excinfo, outerr) + item.config.pytestplugins.notify("itemsetupreport", rep) + + def setupitem(self, item): + return self.fixturecall(self.prepare, item) + + def teardownitem(self, item): + self.fixturecall(self.teardown_exact, item) diff --git a/py/test/testing/test_setup_nested.py b/py/test/testing/test_fixture_and_setup.py similarity index 65% rename from py/test/testing/test_setup_nested.py rename to py/test/testing/test_fixture_and_setup.py index 2e5b13faf..16ddca502 100644 --- a/py/test/testing/test_setup_nested.py +++ b/py/test/testing/test_fixture_and_setup.py @@ -138,3 +138,74 @@ def test_method_setup_uses_fresh_instances(testdir): assert not hasattr(self, 'world') """) sorter.assertoutcome(passed=4, failed=0) + +from py.__.test.config import SetupState + +class TestSetupState: + def test_setupitem_works(self, testdir): + item = testdir.getitem(""" + def setup_module(mod): + pass + def test_func(): + pass + """) + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + res = setup.setupitem(item) + assert res + + def test_setupitem_fails(self, testdir): + item = testdir.getitem(""" + def setup_module(mod): + print "world" + raise ValueError(42) + def test_func(): + pass + """) + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + res = setup.setupitem(item) + assert not res + rep = evrec.popevent("itemsetupreport").rep + assert rep.failed + assert not rep.skipped + assert rep.excrepr + assert "42" in str(rep.excrepr) + assert rep.outerr[0].find("world") != -1 + + def test_teardownitem_fails(self, testdir): + item = testdir.getitem(""" + def test_func(): + pass + def teardown_function(func): + print "13" + raise ValueError(25) + """) + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + res = setup.setupitem(item) + assert res + setup.teardownitem(item) + rep = evrec.popevent("itemsetupreport").rep + assert rep.item == item + assert rep.failed + assert not rep.passed + assert "13" in rep.outerr[0] + assert "25" in str(rep.excrepr) + + def test_setupitem_skips(self, testdir): + item = testdir.getitem(""" + import py + def setup_module(mod): + py.test.skip("17") + def test_func(): + pass + """) + evrec = testdir.geteventrecorder(item.config) + setup = SetupState() + setup.setupitem(item) + rep = evrec.popevent("itemsetupreport").rep + assert not rep.failed + assert rep.skipped + assert rep.excrepr + assert "17" in str(rep.excrepr)