[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:
hpk
2008-08-16 17:26:59 +02:00
parent 7428eadf7d
commit abc8cf09aa
187 changed files with 27242 additions and 18 deletions

View File

@@ -0,0 +1 @@
#

View 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])

View File

@@ -0,0 +1 @@
#

View 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

View 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()

View 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)