From 3767ce3269b30dd5e3c8937f8c95b170c66928ce Mon Sep 17 00:00:00 2001 From: holger krekel Date: Tue, 28 Sep 2010 16:08:31 +0200 Subject: [PATCH] move some code around, ending up with fewer files logically grouped --- testing/acceptance_test.py | 98 ++++++++++++++++++++++++++++++++ testing/test_dsession.py | 112 ------------------------------------- testing/test_looponfail.py | 61 ++++++++++++++++++++ testing/test_remote.py | 12 ++-- testing/test_util.py | 61 -------------------- tox.ini | 2 +- xdist/dsession.py | 7 ++- xdist/looponfail.py | 54 +++++++++++++++++- xdist/remote.py | 4 ++ xdist/slavemanage.py | 14 +++-- xdist/util.py | 53 ------------------ 11 files changed, 236 insertions(+), 242 deletions(-) delete mode 100644 testing/test_util.py delete mode 100644 xdist/util.py diff --git a/testing/acceptance_test.py b/testing/acceptance_test.py index 4ff3e33..43f12e9 100644 --- a/testing/acceptance_test.py +++ b/testing/acceptance_test.py @@ -272,3 +272,101 @@ class TestTerminalReporting: "> assert 0", "E assert 0", ]) + +def test_teardownfails_one_function(testdir): + p = testdir.makepyfile(""" + def test_func(): + pass + def teardown_function(function): + assert 0 + """) + result = testdir.runpytest(p, '-n1', '--tx=popen') + result.stdout.fnmatch_lines([ + "*def teardown_function(function):*", + "*1 passed*1 error*" + ]) + +@py.test.mark.xfail +def test_terminate_on_hangingnode(testdir): + p = testdir.makeconftest(""" + def pytest__teardown_final(session): + if session.nodeid == "my": # running on slave + import time + time.sleep(3) + """) + result = testdir.runpytest(p, '--dist=each', '--tx=popen//id=my') + assert result.duration < 2.0 + result.stdout.fnmatch_lines([ + "*killed*my*", + ]) + + + +def test_session_hooks(testdir): + testdir.makeconftest(""" + import sys + def pytest_sessionstart(session): + sys.pytestsessionhooks = session + def pytest_sessionfinish(session): + if hasattr(session.config, 'slaveinput'): + name = "slave" + else: + name = "master" + f = open(name, "w") + f.write("xy") + f.close() + # let's fail on the slave + if name == "slave": + raise ValueError(42) + """) + p = testdir.makepyfile(""" + import sys + def test_hello(): + assert hasattr(sys, 'pytestsessionhooks') + """) + result = testdir.runpytest(p, "--dist=each", "--tx=popen") + result.stdout.fnmatch_lines([ + "*ValueError*", + "*1 passed*", + ]) + assert not result.ret + d = result.parseoutcomes() + assert d['passed'] == 1 + assert testdir.tmpdir.join("slave").check() + assert testdir.tmpdir.join("master").check() + +def test_funcarg_teardown_failure(testdir): + p = testdir.makepyfile(""" + def pytest_funcarg__myarg(request): + def teardown(val): + raise ValueError(val) + return request.cached_setup(setup=lambda: 42, teardown=teardown, + scope="module") + def test_hello(myarg): + pass + """) + result = testdir.runpytest("--debug", p, "-n1") + result.stdout.fnmatch_lines([ + "*ValueError*42*", + "*1 passed*1 error*", + ]) + py.test.xfail("fix exitstatus handling") + assert result.ret + +def test_crashing_item(testdir): + p = testdir.makepyfile(""" + import py + import os + def test_crash(): + py.process.kill(os.getpid()) + def test_noncrash(): + pass + """) + result = testdir.runpytest("-n2", p) + result.stdout.fnmatch_lines([ + "*crashed*test_crash*", + "*1 failed*1 passed*" + ]) + + + diff --git a/testing/test_dsession.py b/testing/test_dsession.py index 748cf65..df6c437 100644 --- a/testing/test_dsession.py +++ b/testing/test_dsession.py @@ -429,115 +429,3 @@ class TestDSession: linecomp.assert_contains_lines([ "[X1,X2] rsyncing: hello", ]) - -def test_collected_function_causes_remote_skip(testdir): - sub = testdir.mkpydir("testing") - sub.join("test_module.py").write(py.code.Source(""" - import py - path = py.path.local(%r) - if path.check(): - path.remove() - else: - py.test.skip("remote skip") - def test_func(): - pass - def test_func2(): - pass - """ % str(sub.ensure("somefile")))) - result = testdir.runpytest('-v', '--dist=each', '--tx=popen') - result.stdout.fnmatch_lines([ - "*2 skipped*" - ]) - -def test_teardownfails_one_function(testdir): - p = testdir.makepyfile(""" - def test_func(): - pass - def teardown_function(function): - assert 0 - """) - result = testdir.runpytest(p, '--dist=each', '--tx=popen') - result.stdout.fnmatch_lines([ - "*def teardown_function(function):*", - "*1 passed*1 error*" - ]) - -@py.test.mark.xfail -def test_terminate_on_hangingnode(testdir): - p = testdir.makeconftest(""" - def pytest__teardown_final(session): - if session.nodeid == "my": # running on slave - import time - time.sleep(3) - """) - result = testdir.runpytest(p, '--dist=each', '--tx=popen//id=my') - assert result.duration < 2.0 - result.stdout.fnmatch_lines([ - "*killed*my*", - ]) - - - -def test_session_hooks(testdir): - testdir.makeconftest(""" - import sys - def pytest_sessionstart(session): - sys.pytestsessionhooks = session - def pytest_sessionfinish(session): - f = open(session.nodeid or "master", 'w') - f.write("xy") - f.close() - # let's fail on the slave - if session.nodeid: - raise ValueError(42) - """) - p = testdir.makepyfile(""" - import sys - def test_hello(): - assert hasattr(sys, 'pytestsessionhooks') - """) - result = testdir.runpytest(p, "--dist=each", "--tx=popen//id=my1") - result.stdout.fnmatch_lines([ - "*ValueError*", - "*1 passed*", - ]) - assert result.ret - d = result.parseoutcomes() - assert d['passed'] == 1 - assert testdir.tmpdir.join("my1").check() - assert testdir.tmpdir.join("master").check() - -def test_funcarg_teardown_failure(testdir): - p = testdir.makepyfile(""" - def pytest_funcarg__myarg(request): - def teardown(val): - raise ValueError(val) - return request.cached_setup(setup=lambda: 42, teardown=teardown, - scope="module") - def test_hello(myarg): - pass - """) - result = testdir.runpytest(p, "-n1") - assert result.ret - result.stdout.fnmatch_lines([ - "*ValueError*42*", - "*1 passed*1 error*", - ]) - -def test_crashing_item(testdir): - p = testdir.makepyfile(""" - import py - import os - def test_crash(): - py.process.kill(os.getpid()) - def test_noncrash(): - pass - """) - result = testdir.runpytest("-n2", p) - result.stdout.fnmatch_lines([ - "*crashed*test_crash*", - "*1 failed*1 passed*" - ]) - - - diff --git a/testing/test_looponfail.py b/testing/test_looponfail.py index 7592ad3..809ecb9 100644 --- a/testing/test_looponfail.py +++ b/testing/test_looponfail.py @@ -1,5 +1,65 @@ import py from xdist.looponfail import RemoteControl +from xdist.looponfail import StatRecorder + +class TestStatRecorder: + def test_filechange(self, tmpdir): + tmp = tmpdir + hello = tmp.ensure("hello.py") + sd = StatRecorder([tmp]) + changed = sd.check() + assert not changed + + hello.write("world") + changed = sd.check() + assert changed + + tmp.ensure("new.py") + changed = sd.check() + assert changed + + tmp.join("new.py").remove() + changed = sd.check() + assert changed + + tmp.join("a", "b", "c.py").ensure() + changed = sd.check() + assert changed + + tmp.join("a", "c.txt").ensure() + changed = sd.check() + assert changed + changed = sd.check() + assert not changed + + tmp.join("a").remove() + changed = sd.check() + assert changed + + def test_pycremoval(self, tmpdir): + tmp = tmpdir + hello = tmp.ensure("hello.py") + sd = StatRecorder([tmp]) + changed = sd.check() + assert not changed + + pycfile = hello + "c" + pycfile.ensure() + changed = sd.check() + assert not changed + + hello.write("world") + changed = sd.check() + assert not pycfile.check() + + def test_waitonchange(self, tmpdir, monkeypatch): + tmp = tmpdir + sd = StatRecorder([tmp]) + + l = [True, False] + monkeypatch.setattr(StatRecorder, 'check', lambda self: l.pop()) + sd.waitonchange(checkinterval=0.2) + assert not l class TestRemoteControl: def test_nofailures(self, testdir): @@ -174,3 +234,4 @@ def removepyc(path): pyc = path + "c" if pyc.check(): pyc.remove() + diff --git a/testing/test_remote.py b/testing/test_remote.py index f9ab7c4..9082dc1 100644 --- a/testing/test_remote.py +++ b/testing/test_remote.py @@ -83,7 +83,7 @@ class TestReportSerialization: for rep in reports: d = serialize_report(rep) check_marshallable(d) - newrep = unserialize_report(d) + newrep = unserialize_report("testreport", d) assert newrep.passed == rep.passed assert newrep.failed == rep.failed assert newrep.skipped == rep.skipped @@ -99,7 +99,7 @@ class TestReportSerialization: for rep in reports: d = serialize_report(rep) check_marshallable(d) - newrep = unserialize_report(d) + newrep = unserialize_report("collectreport", d) assert newrep.passed == rep.passed assert newrep.failed == rep.failed assert newrep.skipped == rep.skipped @@ -111,7 +111,7 @@ class TestReportSerialization: for rep in reports: d = serialize_report(rep) check_marshallable(d) - newrep = unserialize_report(d) + newrep = unserialize_report("collectreport", d) assert newrep.passed == rep.passed assert newrep.failed == rep.failed assert newrep.skipped == rep.skipped @@ -137,7 +137,7 @@ class TestSlaveInteractor: slave.sendcommand("runtests", ids=ids) ev = slave.popevent("testreport") assert ev.name == "testreport" - rep = unserialize_report(ev.kwargs['data']) + rep = unserialize_report(ev.name, ev.kwargs['data']) assert rep.nodeid.endswith("::test_func") assert rep.passed assert rep.when == "call" @@ -155,7 +155,7 @@ class TestSlaveInteractor: assert not ev.kwargs ev = slave.popevent() assert ev.name == "collectreport" - rep = unserialize_report(ev.kwargs['data']) + rep = unserialize_report(ev.name, ev.kwargs['data']) assert rep.skipped ev = slave.popevent("collectionfinish") print ev.kwargs @@ -168,7 +168,7 @@ class TestSlaveInteractor: assert not ev.kwargs ev = slave.popevent() assert ev.name == "collectreport" - rep = unserialize_report(ev.kwargs['data']) + rep = unserialize_report(ev.name, ev.kwargs['data']) assert rep.failed ev = slave.popevent("collectionfinish") print ev.kwargs diff --git a/testing/test_util.py b/testing/test_util.py deleted file mode 100644 index 7d64fdb..0000000 --- a/testing/test_util.py +++ /dev/null @@ -1,61 +0,0 @@ -import py -from xdist.util import StatRecorder - -def test_filechange(tmpdir): - tmp = tmpdir - hello = tmp.ensure("hello.py") - sd = StatRecorder([tmp]) - changed = sd.check() - assert not changed - - hello.write("world") - changed = sd.check() - assert changed - - tmp.ensure("new.py") - changed = sd.check() - assert changed - - tmp.join("new.py").remove() - changed = sd.check() - assert changed - - tmp.join("a", "b", "c.py").ensure() - changed = sd.check() - assert changed - - tmp.join("a", "c.txt").ensure() - changed = sd.check() - assert changed - changed = sd.check() - assert not changed - - tmp.join("a").remove() - changed = sd.check() - assert changed - -def test_pycremoval(tmpdir): - tmp = tmpdir - hello = tmp.ensure("hello.py") - sd = StatRecorder([tmp]) - changed = sd.check() - assert not changed - - pycfile = hello + "c" - pycfile.ensure() - changed = sd.check() - assert not changed - - hello.write("world") - changed = sd.check() - assert not pycfile.check() - - -def test_waitonchange(tmpdir, monkeypatch): - tmp = tmpdir - sd = StatRecorder([tmp]) - - l = [True, False] - monkeypatch.setattr(StatRecorder, 'check', lambda self: l.pop()) - sd.waitonchange(checkinterval=0.2) - assert not l diff --git a/tox.ini b/tox.ini index c5eacfc..b82c04c 100644 --- a/tox.ini +++ b/tox.ini @@ -8,7 +8,7 @@ changedir=testing deps= {distshare}/py-* commands= - py.test -rsfxX --tools-on-path \ + py.test -rsfxX \ --junitxml={envlogdir}/junit-{envname}.xml [] [testenv:py26] basepython=python2.6 diff --git a/xdist/dsession.py b/xdist/dsession.py index 6bdd416..128a606 100644 --- a/xdist/dsession.py +++ b/xdist/dsession.py @@ -199,12 +199,17 @@ class DSession: nodeid=nodeid, location=location) def slave_testreport(self, node, rep): - self.sched.remove_item(node, rep.nodeid) + if rep.when in ("setup", "call"): + self.sched.remove_item(node, rep.nodeid) #self.report_line("testreport %s: %s" %(rep.id, rep.status)) enrich_report_with_platform_data(rep, node) self.config.hook.pytest_runtest_logreport(report=rep) self._handlefailures(rep) + def slave_teardownreport(self, node, rep): + enrich_report_with_platform_data(rep, node) + self.config.hook.pytest__teardown_final_logerror(report=rep) + def slave_collectreport(self, node, rep): #self.report_line("collectreport %s: %s" %(rep.id, rep.status)) #rep.node = node diff --git a/xdist/looponfail.py b/xdist/looponfail.py index 3abd256..aed5404 100644 --- a/xdist/looponfail.py +++ b/xdist/looponfail.py @@ -11,13 +11,12 @@ import py import sys import execnet from py._test.session import gettopdir -from xdist import util def looponfail_main(config): remotecontrol = RemoteControl(config) # XXX better configure rootdir rootdirs = [gettopdir(config.args)] - statrecorder = util.StatRecorder(rootdirs) + statrecorder = StatRecorder(rootdirs) try: while 1: remotecontrol.loop_once() @@ -191,3 +190,54 @@ class SlaveFailSession: topdir = str(self.topdir) self.channel.send((topdir, trails, failreports, self.collection_failed)) +class StatRecorder: + def __init__(self, rootdirlist): + self.rootdirlist = rootdirlist + self.statcache = {} + self.check() # snapshot state + + def fil(self, p): + return p.ext in ('.py', '.txt', '.c', '.h') + def rec(self, p): + return p.check(dotfile=0) + + def waitonchange(self, checkinterval=1.0): + while 1: + changed = self.check() + if changed: + return + py.std.time.sleep(checkinterval) + + def check(self, removepycfiles=True): + changed = False + statcache = self.statcache + newstat = {} + for rootdir in self.rootdirlist: + for path in rootdir.visit(self.fil, self.rec): + oldstat = statcache.get(path, None) + if oldstat is not None: + del statcache[path] + try: + newstat[path] = curstat = path.stat() + except py.error.ENOENT: + if oldstat: + del statcache[path] + changed = True + else: + if oldstat: + if oldstat.mtime != curstat.mtime or \ + oldstat.size != curstat.size: + changed = True + py.builtin.print_("# MODIFIED", path) + if removepycfiles and path.ext == ".py": + pycfile = path + "c" + if pycfile.check(): + pycfile.remove() + + else: + changed = True + if statcache: + changed = True + self.statcache = newstat + return changed + diff --git a/xdist/remote.py b/xdist/remote.py index e31f116..a763b7c 100644 --- a/xdist/remote.py +++ b/xdist/remote.py @@ -26,6 +26,10 @@ class SlaveInteractor: for line in str(excrepr).split("\n"): self.log("IERROR> " + line) + def pytest__teardown_final_logerror(self, report): + rep = serialize_report(report) + self.sendevent("teardownreport", data=rep) + def pytest_sessionstart(self, session): self.session = session self.collection = session.collection diff --git a/xdist/slavemanage.py b/xdist/slavemanage.py index a319e28..aa32a53 100644 --- a/xdist/slavemanage.py +++ b/xdist/slavemanage.py @@ -295,8 +295,8 @@ class SlaveController(object): self.notify_inproc("slavefinished", node=self) #elif eventname == "logstart": # self.notify_inproc(eventname, node=self, **kwargs) - elif eventname in ("testreport", "collectreport"): - rep = unserialize_report(kwargs['data']) + elif eventname in ("testreport", "collectreport", "teardownreport"): + rep = unserialize_report(eventname, kwargs['data']) self.notify_inproc(eventname, node=self, rep=rep) elif eventname == "collectionfinish": self.notify_inproc(eventname, node=self, ids=kwargs['ids']) @@ -310,9 +310,11 @@ class SlaveController(object): py.builtin.print_("!" * 20, excinfo) self.config.pluginmanager.notify_exception(excinfo) -def unserialize_report(reportdict): +def unserialize_report(name, reportdict): d = reportdict - if 'result' in d: - return runner.CollectReport(**d) - else: + if name == "testreport": return runner.TestReport(**d) + elif name == "collectreport": + return runner.CollectReport(**d) + elif name == "teardownreport": + return runner.TeardownErrorReport(**d) diff --git a/xdist/util.py b/xdist/util.py deleted file mode 100644 index 5074a33..0000000 --- a/xdist/util.py +++ /dev/null @@ -1,53 +0,0 @@ -import py - -class StatRecorder: - def __init__(self, rootdirlist): - self.rootdirlist = rootdirlist - self.statcache = {} - self.check() # snapshot state - - def fil(self, p): - return p.ext in ('.py', '.txt', '.c', '.h') - def rec(self, p): - return p.check(dotfile=0) - - def waitonchange(self, checkinterval=1.0): - while 1: - changed = self.check() - if changed: - return - py.std.time.sleep(checkinterval) - - def check(self, removepycfiles=True): - changed = False - statcache = self.statcache - newstat = {} - for rootdir in self.rootdirlist: - for path in rootdir.visit(self.fil, self.rec): - oldstat = statcache.get(path, None) - if oldstat is not None: - del statcache[path] - try: - newstat[path] = curstat = path.stat() - except py.error.ENOENT: - if oldstat: - del statcache[path] - changed = True - else: - if oldstat: - if oldstat.mtime != curstat.mtime or \ - oldstat.size != curstat.size: - changed = True - py.builtin.print_("# MODIFIED", path) - if removepycfiles and path.ext == ".py": - pycfile = path + "c" - if pycfile.check(): - pycfile.remove() - - else: - changed = True - if statcache: - changed = True - self.statcache = newstat - return changed -