From 9aed6ddcd04245b226baad5714ca02051bb28532 Mon Sep 17 00:00:00 2001 From: hpk Date: Tue, 7 Apr 2009 21:27:00 +0200 Subject: [PATCH] [svn r63805] first step towards having a dedicated test-run plugin, some more basics missing --HG-- branch : trunk --- py/test/plugin/pytest_runner.py | 273 ++++++++++++++++++++++++++++++++ py/test/runner.py | 21 --- py/test/testing/test_runner.py | 1 + 3 files changed, 274 insertions(+), 21 deletions(-) create mode 100644 py/test/plugin/pytest_runner.py diff --git a/py/test/plugin/pytest_runner.py b/py/test/plugin/pytest_runner.py new file mode 100644 index 000000000..3e1b319ad --- /dev/null +++ b/py/test/plugin/pytest_runner.py @@ -0,0 +1,273 @@ +import py + +class RunnerPlugin: + def pytest_configure(self, config): + config._setupstate = SetupState() + + def pytest_unconfigure(self, config): + config._setupstate.teardown_all() + + def pytest_item_setup_and_runtest(self, item): + setupstate = item.config._setupstate + call = item.config.guardedcall(lambda: setupstate.prepare(item)) + rep = ItemSetupReport(item, call.excinfo, call.outerr) + if call.excinfo: + item.config.pytestplugins.notify("itemsetupreport", rep) + else: + call = item.config.guardedcall(lambda: item.runtest()) + item.config.mc.pytest_item_runtest_finished( + item=item, excinfo=call.excinfo, outerr=call.outerr) + call = item.config.guardedcall(lambda: self.teardown_exact(item)) + if call.excinfo: + rep = ItemSetupReport(item, call.excinfo, call.outerr) + item.config.pytestplugins.notify("itemsetupreport", rep) + + def pytest_collector_collect(self, collector): + call = item.config.guardedcall(lambda x: collector._memocollect()) + return CollectReport(collector, res, excinfo, outerr) + +class BaseReport(object): + def __repr__(self): + l = ["%s=%s" %(key, value) + for key, value in self.__dict__.items()] + return "<%s %s>" %(self.__class__.__name__, " ".join(l),) + + def toterminal(self, out): + longrepr = self.longrepr + if hasattr(longrepr, 'toterminal'): + longrepr.toterminal(out) + else: + out.line(str(longrepr)) + +class ItemTestReport(BaseReport): + failed = passed = skipped = False + + def __init__(self, colitem, excinfo=None, when=None, outerr=None): + self.colitem = colitem + if colitem and when != "setup": + self.keywords = colitem.readkeywords() + else: + # if we fail during setup it might mean + # we are not able to access the underlying object + # this might e.g. happen if we are unpickled + # and our parent collector did not collect us + # (because it e.g. skipped for platform reasons) + self.keywords = {} + 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 CollectReport(BaseReport): + skipped = failed = passed = False + + def __init__(self, colitem, result, excinfo=None, outerr=None): + # XXX rename to collector + self.colitem = colitem + if not excinfo: + self.passed = True + self.result = result + else: + 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 + +class ItemSetupReport(BaseReport): + 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, []) + +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) + +# =============================================================================== +# +# plugin tests +# +# =============================================================================== + +def test_generic(plugintester): + plugintester.apicheck(RunnerPlugin()) + +class TestSetupState: + def test_setup_prepare(self, testdir): + modcol = testdir.getmodulecol(""" + def setup_function(function): + function.myfunc = function + def test_func1(): + pass + def test_func2(): + pass + def teardown_function(function): + del function.myfunc + """) + item1, item2 = modcol.collect() + setup = SetupState() + setup.prepare(item1) + assert item1.obj.myfunc == item1.obj + assert not hasattr(item2, 'myfunc') + + setup.prepare(item2) + assert item2.obj.myfunc == item2.obj + assert not hasattr(item1, 'myfunc') + + def test_setup_teardown_exact(self, testdir): + item = testdir.getitem(""" + def test_func(): + pass + def teardown_function(function): + function.tear = "down" + """) + setup = SetupState() + setup.prepare(item) + setup.teardown_exact(item) + assert item.obj.tear == "down" + + def test_setup_teardown_exact(self, testdir): + item = testdir.getitem(""" + def setup_module(mod): + mod.x = 1 + def setup_function(function): + function.y = 2 + def test_func(): + pass + def teardown_function(function): + del function.y + def teardown_module(mod): + del mod.x + """) + setup = SetupState() + setup.prepare(item) + assert item.obj.y == 2 + assert item.parent.obj.x == 1 + setup.teardown_all() + assert not hasattr(item.obj, 'y') + assert not hasattr(item.parent.obj, 'x') + +class TestRunnerPlugin: + disabled = True + def test_pytest_item_setup_and_runtest(self, testdir): + item = testdir.getitem("""def test_func(): pass""") + plugin = RunnerPlugin() + plugin.pytest_configure(item.config) + sorter = testdir.geteventrecorder(item.config) + plugin.pytest_item_setup_and_runtest(item) + rep = sorter.getreport("itemtestreport") + assert rep.passed + + +class TestSetupEvents: + disabled = True + + def test_setup_runtest_ok(self, testdir): + sorter = testdir.inline_runsource(""" + def setup_module(mod): + pass + def test_func(): + pass + """) + assert not sorter.getcalls("itemsetupreport") + + def test_setup_fails(self, testdir): + sorter = testdir.inline_runsource(""" + def setup_module(mod): + print "world" + raise ValueError(42) + def test_func(): + pass + """) + rep = sorter.popcall("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_teardown_fails(self, testdir): + sorter = testdir.inline_runsource(""" + def test_func(): + pass + def teardown_function(func): + print "13" + raise ValueError(25) + """) + rep = evrec.popcall("itemsetupreport").rep + assert rep.failed + assert rep.item == item + assert not rep.passed + assert "13" in rep.outerr[0] + assert "25" in str(rep.excrepr) + + def test_setup_skips(self, testdir): + sorter = testdir.inline_runsource(""" + import py + def setup_module(mod): + py.test.skip("17") + def test_func(): + pass + """) + rep = sorter.popcall("itemsetupreport") + assert not rep.failed + assert rep.skipped + assert rep.excrepr + assert "17" in str(rep.excrepr) + + diff --git a/py/test/runner.py b/py/test/runner.py index 22a6848b9..0b4d50bde 100644 --- a/py/test/runner.py +++ b/py/test/runner.py @@ -196,24 +196,3 @@ class SetupState(object): for col in needed_collectors[len(self.stack):]: col.setup() self.stack.append(col) - - def do_setup(self, item): - call = item.config.guardedcall(lambda: self.prepare(item)) - rep = ItemSetupReport(item, call.excinfo, call.outerr) - item.config.pytestplugins.notify("itemsetupreport", rep) - return not call.excinfo - - def do_teardown(self, item): - call = item.config.guardedcall(lambda: self.teardown_exact(item)) - if call.excinfo: - rep = ItemSetupReport(item, call.excinfo, call.outerr) - item.config.pytestplugins.notify("itemsetupreport", rep) - - def do_fixture_and_runtest(self, item): - """ setup fixture and perform actual item.runtest(). """ - if self.do_setup(item): - call = item.config.guardedcall(lambda: item.runtest()) - item.config.pytestplugins.notify( - "item_runtest_finished", - item=item, excinfo=call.excinfo, outerr=call.outerr) - self.do_teardown(item) diff --git a/py/test/testing/test_runner.py b/py/test/testing/test_runner.py index 5c5cf33bd..934993bf8 100644 --- a/py/test/testing/test_runner.py +++ b/py/test/testing/test_runner.py @@ -2,6 +2,7 @@ from py.__.test.config import SetupState class TestSetupState: + disabled = True def test_setup_ok(self, testdir): item = testdir.getitem(""" def setup_module(mod):