[svn r57321] merging the event branch:
* moving in test, misc, code, io directories and py/__init__.py * py/bin/_find.py does not print to stderr anymore * a few fixes to conftest files in other dirs some more fixes and adjustments pending --HG-- branch : trunk
This commit is contained in:
1
py/test/looponfail/__init__.py
Normal file
1
py/test/looponfail/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
#
|
||||
160
py/test/looponfail/remote.py
Normal file
160
py/test/looponfail/remote.py
Normal file
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
LooponfailingSession and Helpers.
|
||||
|
||||
NOTE that one really has to avoid loading and depending on
|
||||
application modules within the controlling process
|
||||
(the one that starts repeatedly test processes)
|
||||
otherwise changes to source code can crash
|
||||
the controlling process which should never happen.
|
||||
"""
|
||||
|
||||
from __future__ import generators
|
||||
import py
|
||||
from py.__.test.session import Session
|
||||
from py.__.test.outcome import Failed, Passed, Skipped
|
||||
from py.__.test.dsession.mypickle import PickleChannel
|
||||
from py.__.test.report.terminal import TerminalReporter
|
||||
from py.__.test import event
|
||||
from py.__.test.looponfail import util
|
||||
|
||||
class LooponfailingSession(Session):
|
||||
def __init__(self, config):
|
||||
super(LooponfailingSession, self).__init__(config=config)
|
||||
self.rootdirs = [self.config.topdir] # xxx dist_rsync_roots?
|
||||
self.statrecorder = util.StatRecorder(self.rootdirs)
|
||||
self.remotecontrol = RemoteControl(self.config)
|
||||
self.out = py.io.TerminalWriter()
|
||||
|
||||
def main(self, initialitems=None):
|
||||
try:
|
||||
self.loopstate = loopstate = LoopState(initialitems)
|
||||
self.remotecontrol.setup()
|
||||
while 1:
|
||||
self.loop_once(loopstate)
|
||||
if not loopstate.colitems and loopstate.wasfailing:
|
||||
continue # rerun immediately
|
||||
self.statrecorder.waitonchange(checkinterval=2.0)
|
||||
except KeyboardInterrupt:
|
||||
print
|
||||
pass
|
||||
|
||||
def loop_once(self, loopstate):
|
||||
colitems = loopstate.colitems
|
||||
loopstate.wasfailing = colitems and len(colitems)
|
||||
loopstate.colitems = self.remotecontrol.runsession(colitems or ())
|
||||
#ev = event.LooponfailingInfo(loopstate.failreports, self.rootdirs)
|
||||
self.remotecontrol.setup()
|
||||
|
||||
class LoopState:
|
||||
def __init__(self, colitems=None):
|
||||
self.colitems = colitems
|
||||
|
||||
class RemoteControl(object):
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self._setexecutable()
|
||||
|
||||
def _setexecutable(self):
|
||||
# XXX --exec logic should go to DSession
|
||||
name = self.config.option.executable
|
||||
if name is None:
|
||||
executable = py.std.sys.executable
|
||||
else:
|
||||
executable = py.path.local.sysfind(name)
|
||||
assert executable is not None, executable
|
||||
self.executable = executable
|
||||
|
||||
def trace(self, *args):
|
||||
if self.config.option.debug:
|
||||
msg = " ".join([str(x) for x in args])
|
||||
print "RemoteControl:", msg
|
||||
|
||||
def setup(self, out=None):
|
||||
if hasattr(self, 'gateway'):
|
||||
raise ValueError("already have gateway %r" % self.gateway)
|
||||
if out is None:
|
||||
out = py.io.TerminalWriter()
|
||||
from py.__.test.dsession import masterslave
|
||||
self.trace("setting up slave session")
|
||||
self.gateway = py.execnet.PopenGateway(self.executable)
|
||||
channel = self.gateway.remote_exec(source="""
|
||||
from py.__.test.dsession.mypickle import PickleChannel
|
||||
channel = PickleChannel(channel)
|
||||
from py.__.test.looponfail.remote import slave_runsession
|
||||
from py.__.test.dsession import masterslave
|
||||
config = masterslave.receive_and_send_pickled_config(channel)
|
||||
width, hasmarkup = channel.receive()
|
||||
slave_runsession(channel, config, width, hasmarkup)
|
||||
""", stdout=out, stderr=out)
|
||||
channel = PickleChannel(channel)
|
||||
masterslave.send_and_receive_pickled_config(
|
||||
channel, self.config, remote_topdir=self.config.topdir)
|
||||
channel.send((out.fullwidth, out.hasmarkup))
|
||||
self.trace("set up of slave session complete")
|
||||
self.channel = channel
|
||||
|
||||
def ensure_teardown(self):
|
||||
if hasattr(self, 'channel'):
|
||||
if not self.channel.isclosed():
|
||||
self.trace("closing", self.channel)
|
||||
self.channel.close()
|
||||
del self.channel
|
||||
if hasattr(self, 'gateway'):
|
||||
self.trace("exiting", self.gateway)
|
||||
self.gateway.exit()
|
||||
del self.gateway
|
||||
|
||||
def runsession(self, colitems=()):
|
||||
try:
|
||||
self.trace("sending", colitems)
|
||||
trails = colitems
|
||||
self.channel.send(trails)
|
||||
try:
|
||||
return self.channel.receive()
|
||||
except self.channel.RemoteError, e:
|
||||
self.trace("ERROR", e)
|
||||
raise
|
||||
finally:
|
||||
self.ensure_teardown()
|
||||
|
||||
def slave_runsession(channel, config, width, hasmarkup):
|
||||
""" we run this on the other side. """
|
||||
if config.option.debug:
|
||||
def DEBUG(*args):
|
||||
print " ".join(map(str, args))
|
||||
else:
|
||||
def DEBUG(*args): pass
|
||||
|
||||
DEBUG("SLAVE: received configuration, using topdir:", config.topdir)
|
||||
#config.option.session = None
|
||||
config.option.looponfailing = False
|
||||
config.option.usepdb = False
|
||||
config.option.executable = None
|
||||
trails = channel.receive()
|
||||
|
||||
DEBUG("SLAVE: initsession()")
|
||||
session = config.initsession()
|
||||
session.reporter._tw.hasmarkup = hasmarkup
|
||||
session.reporter._tw.fullwidth = width
|
||||
if trails:
|
||||
colitems = [py.test.collect.Collector._fromtrail(x, config)
|
||||
for x in trails]
|
||||
else:
|
||||
colitems = None
|
||||
session.shouldclose = channel.isclosed
|
||||
#def sendevent(ev):
|
||||
# channel.send(ev)
|
||||
#session.bus.subscribe(sendevent)
|
||||
failreports = []
|
||||
def recordfailures(ev):
|
||||
if isinstance(ev, event.BaseReport):
|
||||
if ev.failed:
|
||||
failreports.append(ev)
|
||||
session.bus.subscribe(recordfailures)
|
||||
|
||||
DEBUG("SLAVE: starting session.main()")
|
||||
session.main(colitems)
|
||||
session.bus.unsubscribe(recordfailures)
|
||||
ev = event.LooponfailingInfo(failreports, [config.topdir])
|
||||
session.bus.notify(ev)
|
||||
channel.send([x.colitem._totrail() for x in failreports if x.failed])
|
||||
1
py/test/looponfail/testing/__init__.py
Normal file
1
py/test/looponfail/testing/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
#
|
||||
114
py/test/looponfail/testing/test_remote.py
Normal file
114
py/test/looponfail/testing/test_remote.py
Normal file
@@ -0,0 +1,114 @@
|
||||
import py
|
||||
from py.__.test.testing import suptest
|
||||
from py.__.test.looponfail.remote import LooponfailingSession, LoopState, RemoteControl
|
||||
from py.__.test import event
|
||||
|
||||
def getevent(l, evtype):
|
||||
result = getevents(l, evtype)
|
||||
if not result:
|
||||
raise ValueError("event %r not found in %r" %(evtype, l))
|
||||
return result[0]
|
||||
|
||||
def getevents(l, evtype):
|
||||
result = []
|
||||
for ev in l:
|
||||
if isinstance(ev, evtype):
|
||||
result.append(ev)
|
||||
return result
|
||||
|
||||
|
||||
class TestRemoteControl(suptest.InlineCollection):
|
||||
def test_nofailures(self):
|
||||
item = self.getitem("def test_func(): pass\n")
|
||||
events = []
|
||||
control = RemoteControl(item._config)
|
||||
control.setup()
|
||||
failures = control.runsession()
|
||||
assert not failures
|
||||
|
||||
def test_failures(self):
|
||||
item = self.getitem("def test_func(): assert 0\n")
|
||||
control = RemoteControl(item._config)
|
||||
control.setup()
|
||||
failures = control.runsession()
|
||||
assert failures
|
||||
control.setup()
|
||||
item.fspath.write("def test_func(): assert 1\n")
|
||||
(item.fspath + "c").remove()
|
||||
failures = control.runsession(failures)
|
||||
assert not failures
|
||||
|
||||
def test_failure_change(self):
|
||||
modcol = self.getitem("""
|
||||
def test_func():
|
||||
assert 0
|
||||
""")
|
||||
control = RemoteControl(modcol._config)
|
||||
control.setup()
|
||||
failures = control.runsession()
|
||||
assert failures
|
||||
control.setup()
|
||||
modcol.fspath.write(py.code.Source("""
|
||||
def test_func():
|
||||
assert 1
|
||||
def test_new():
|
||||
assert 0
|
||||
"""))
|
||||
(modcol.fspath + "c").remove()
|
||||
failures = control.runsession(failures)
|
||||
assert not failures
|
||||
control.setup()
|
||||
failures = control.runsession()
|
||||
assert failures
|
||||
assert str(failures).find("test_new") != -1
|
||||
|
||||
class TestLooponFailing(suptest.InlineCollection):
|
||||
def test_looponfailing_from_fail_to_ok(self):
|
||||
modcol = self.getmodulecol("""
|
||||
def test_one():
|
||||
x = 0
|
||||
assert x == 1
|
||||
def test_two():
|
||||
assert 1
|
||||
""")
|
||||
session = LooponfailingSession(modcol._config)
|
||||
loopstate = LoopState()
|
||||
session.remotecontrol.setup()
|
||||
session.loop_once(loopstate)
|
||||
assert len(loopstate.colitems) == 1
|
||||
|
||||
modcol.fspath.write(py.code.Source("""
|
||||
def test_one():
|
||||
x = 15
|
||||
assert x == 15
|
||||
def test_two():
|
||||
assert 1
|
||||
"""))
|
||||
assert session.statrecorder.check()
|
||||
session.loop_once(loopstate)
|
||||
assert not loopstate.colitems
|
||||
|
||||
def test_looponfailing_from_one_to_two_tests(self):
|
||||
modcol = self.getmodulecol("""
|
||||
def test_one():
|
||||
assert 0
|
||||
""")
|
||||
session = LooponfailingSession(modcol._config)
|
||||
loopstate = LoopState()
|
||||
session.remotecontrol.setup()
|
||||
loopstate.colitems = []
|
||||
session.loop_once(loopstate)
|
||||
assert len(loopstate.colitems) == 1
|
||||
|
||||
modcol.fspath.write(py.code.Source("""
|
||||
def test_one():
|
||||
assert 1 # passes now
|
||||
def test_two():
|
||||
assert 0 # new and fails
|
||||
"""))
|
||||
assert session.statrecorder.check()
|
||||
session.loop_once(loopstate)
|
||||
assert len(loopstate.colitems) == 0
|
||||
|
||||
session.loop_once(loopstate)
|
||||
assert len(loopstate.colitems) == 1
|
||||
90
py/test/looponfail/testing/test_util.py
Normal file
90
py/test/looponfail/testing/test_util.py
Normal file
@@ -0,0 +1,90 @@
|
||||
import py
|
||||
from py.__.test.looponfail.util import StatRecorder, EventRecorder
|
||||
from py.__.test import event
|
||||
|
||||
def test_filechange():
|
||||
tmp = py.test.ensuretemp("test_filechange")
|
||||
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():
|
||||
tmp = py.test.ensuretemp("test_pycremoval")
|
||||
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():
|
||||
tmp = py.test.ensuretemp("test_waitonchange")
|
||||
sd = StatRecorder([tmp])
|
||||
|
||||
wp = py._thread.WorkerPool(1)
|
||||
reply = wp.dispatch(sd.waitonchange, checkinterval=0.2)
|
||||
py.std.time.sleep(0.05)
|
||||
tmp.ensure("newfile.py")
|
||||
reply.get(timeout=0.5)
|
||||
wp.shutdown()
|
||||
|
||||
def test_eventrecorder():
|
||||
bus = event.EventBus()
|
||||
recorder = EventRecorder(bus)
|
||||
bus.notify(event.NOP())
|
||||
assert recorder.events
|
||||
assert not recorder.getfailures()
|
||||
rep = event.ItemTestReport(None, failed=True)
|
||||
bus.notify(rep)
|
||||
failures = recorder.getfailures()
|
||||
assert failures == [rep]
|
||||
recorder.clear()
|
||||
assert not recorder.events
|
||||
assert not recorder.getfailures()
|
||||
recorder.unsubscribe()
|
||||
bus.notify(rep)
|
||||
assert not recorder.events
|
||||
assert not recorder.getfailures()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
70
py/test/looponfail/util.py
Normal file
70
py/test/looponfail/util.py
Normal file
@@ -0,0 +1,70 @@
|
||||
import py
|
||||
from py.__.test import event
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
class EventRecorder(object):
|
||||
def __init__(self, bus):
|
||||
self.events = []
|
||||
self.bus = bus
|
||||
self.bus.subscribe(self.events.append)
|
||||
|
||||
def getfailures(self):
|
||||
return [ev for ev in self.events
|
||||
if isinstance(ev, event.BaseReport) and \
|
||||
ev.failed]
|
||||
def clear(self):
|
||||
self.events[:] = []
|
||||
|
||||
def unsubscribe(self):
|
||||
self.bus.unsubscribe(self.events.append)
|
||||
Reference in New Issue
Block a user