[py-svn] r7120 - in py/dist: . doc py py/execnet py/test py/test/report/text
hpk at codespeak.net
hpk at codespeak.net
Mon Oct 25 22:05:15 CEST 2004
Author: hpk
Date: Mon Oct 25 22:05:14 2004
New Revision: 7120
Added:
py/dist/pytestconf.py
- copied, changed from r7106, py/dist/py/pytest.py
Removed:
py/dist/py/pytest.py
Modified:
py/dist/ (props changed)
py/dist/doc/rest_test.py
py/dist/py/execnet/channel.py
py/dist/py/execnet/gateway.py
py/dist/py/execnet/register.py
py/dist/py/execnet/test_gateway.py
py/dist/py/test/cmdline.py
py/dist/py/test/config.py
py/dist/py/test/defaultconfig.py
py/dist/py/test/report/text/reporter.py
py/dist/py/test/run.py
py/dist/py/test/test_config.py
Log:
- first implementation of "py.test --session" mode
which lets you rerun failing tests until they are
resolved. If all tests pass the session is finished.
- a medium size fight at terminating correctly with
no left-over processes when you have a KeyboardIntterupt
there are still some cases where a process is left
over. humpf. but it often works so it's probably
some race condition. Welcome to threads and signals.
- some internal renaming to make things more consistent
'py.path.extpy()' paths are now generally abbreviated
'extpy' for e.g. instance attributes.
Modified: py/dist/doc/rest_test.py
==============================================================================
--- py/dist/doc/rest_test.py (original)
+++ py/dist/doc/rest_test.py Mon Oct 25 22:05:14 2004
@@ -7,9 +7,9 @@
rest = mydir.dirpath('tool', 'rest.py')
class RestItem(py.test.Item):
- def __init__(self, path):
+ def __init__(self, path):
self.path = path
- self.pypath = py.path.extpy(mypath, 'RestItem.execute')
+ self.extpy = py.path.extpy(mypath, 'RestItem.execute')
def execute(self, *args):
out = py.process.cmdexec("%s %s 2>&1" %(rest, self.path))
Modified: py/dist/py/execnet/channel.py
==============================================================================
--- py/dist/py/execnet/channel.py (original)
+++ py/dist/py/execnet/channel.py Mon Oct 25 22:05:14 2004
@@ -1,37 +1,8 @@
-
import threading
import Queue
if 'Message' not in globals():
from py.__impl__.execnet.message import Message
-class ChannelFile:
- def __init__(self, channel):
- self.channel = channel
-
- def write(self, out):
- if self.channel.isclosed():
- return # raise IOError, "cannot write to %r" % self
- self.channel.send(out)
-
- def flush(self):
- pass
-
- def close(self):
- self.channel.close()
-
- def __repr__(self):
- state = self.channel.isclosed() and 'closed' or 'open'
- return '<ChannelFile %d %s>' %(self.channel.id, state)
-
-def receive2file(channel, f):
- while 1:
- try:
- out = channel.receive()
- except EOFError:
- break
- f.write(out)
- f.flush()
-
class Channel(object):
"""Communication channel between two possibly remote threads of code. """
def __init__(self, gateway, id):
@@ -40,7 +11,7 @@
self.id = id
self._items = Queue.Queue()
self._closeevent = threading.Event()
- self._depchannel = []
+ #self._depchannel = []
def __repr__(self):
flag = self.isclosed() and "closed" or "open"
@@ -49,43 +20,45 @@
def isclosed(self):
return self._closeevent.isSet()
+ def open(self, mode='w'):
+ assert mode == 'w'
+ return ChannelFile(self)
+
def close(self, error=None):
""" close down this channel on both sides. """
- if self.id not in self.gateway.channelfactory:
- return
- put = self.gateway._outgoing.put
- if error is not None:
- put(Message.CHANNEL_CLOSE_ERROR(self.id, str(error)))
- else:
- put(Message.CHANNEL_CLOSE(self.id))
- self._close()
+ if self.id in self.gateway.channelfactory:
+ put = self.gateway._outgoing.put
+ if error is not None:
+ put(Message.CHANNEL_CLOSE_ERROR(self.id, str(error)))
+ else:
+ put(Message.CHANNEL_CLOSE(self.id))
+ self._close()
def _close(self, finalitem=EOFError()):
if self.id in self.gateway.channelfactory:
del self.gateway.channelfactory[self.id]
- self._finalitem = finalitem
- for x in self._depchannel:
- x._close()
- self._items.put(finalitem)
- self._closeevent.set()
+ self._finalitem = finalitem
+ #for x in self._depchannel:
+ # x._close()
+ self._items.put(finalitem)
+ self._closeevent.set()
def newchannel(self):
""" return a new channel whose life cycle depends on this channel. """
chan = self.gateway.channelfactory.new()
- self._depchannel.append(chan)
return chan
def _receivechannel(self, newid):
""" receive a remotely created new (sub)channel. """
newchannel = Channel(self.gateway, newid)
self.gateway.channelfactory[newid] = newchannel
- self._depchannel.append(newchannel)
+ #self._depchannel.append(newchannel)
self._items.put(newchannel)
def waitclose(self, timeout):
""" wait until this channel is closed. Note that a closed
- channel may still hold items that will be received or
- send. Note also that exceptions from the other side will be
+ channel may still hold items that can be received or
+ send. Also note that exceptions from the other side will be
reraised as gateway.ExecutionFailed exceptions containing
a textual representation of the remote traceback.
"""
@@ -175,4 +148,32 @@
finally:
self._lock.release()
-
+
+class ChannelFile:
+ def __init__(self, channel):
+ self.channel = channel
+
+ def write(self, out):
+ if self.channel.isclosed():
+ return
+ self.channel.send(out)
+
+ def flush(self):
+ pass
+
+ def close(self):
+ self.channel.close()
+
+ def __repr__(self):
+ state = self.channel.isclosed() and 'closed' or 'open'
+ return '<ChannelFile %d %s>' %(self.channel.id, state)
+
+def receive2file(channel, f):
+ while 1:
+ try:
+ out = channel.receive()
+ except EOFError:
+ break
+ f.write(out)
+ f.flush()
+
Modified: py/dist/py/execnet/gateway.py
==============================================================================
--- py/dist/py/execnet/gateway.py (original)
+++ py/dist/py/execnet/gateway.py Mon Oct 25 22:05:14 2004
@@ -14,7 +14,8 @@
assert Message and Source and ChannelFactory, "Import/Configuration Error"
-debug = open('/tmp/execnet-debug', 'wa')
+import os
+debug = 0 # open('/tmp/execnet-debug-%d' % os.getpid() , 'wa')
sysex = (KeyboardInterrupt, SystemExit)
@@ -39,6 +40,7 @@
self._execqueue = Queue.Queue()
self._outgoing = Queue.Queue()
self.channelfactory = ChannelFactory(self, startcount)
+ self._exitlock = threading.Lock()
self.iothreads = [
threading.Thread(target=self.thread_receiver, name='receiver'),
threading.Thread(target=self.thread_sender, name='sender'),
@@ -62,13 +64,19 @@
self.trace("joining %r" % x)
x.join()
self.workerthreads[:] = []
+ self.trace("workerthreads are empty now")
def exit(self):
- if self.workerthreads:
- self._stopexec()
- self._outgoing.put(Message.EXIT_GATEWAY())
- else:
- self.trace("exit() called, but gateway has not threads anymore!")
+ self._exitlock.acquire()
+ try:
+ #for channel in self.channelfactory.values():
+ # channel.close()
+ if self.workerthreads:
+ self._stopexec()
+ self._outgoing.put(Message.EXIT_GATEWAY())
+ return True
+ finally:
+ self._exitlock.release()
def join(self):
current = threading.currentThread()
@@ -186,5 +194,4 @@
print >>debug, "="*20 + "cleaning up" + "=" * 20
debug.flush()
for x in _gateways:
- if x.workerthreads:
- x.exit()
+ x.exit()
Modified: py/dist/py/execnet/register.py
==============================================================================
--- py/dist/py/execnet/register.py (original)
+++ py/dist/py/execnet/register.py Mon Oct 25 22:05:14 2004
@@ -41,10 +41,11 @@
self._pidchannel = self.remote_exec("import os ; channel.send(os.getpid())")
def exit(self):
- super(PopenGateway, self).exit()
+ if not super(PopenGateway, self).exit():
+ return
try:
- self._pidchannel.waitclose(timeout=0.5)
pid = self._pidchannel.receive()
+ self._pidchannel.waitclose(timeout=0.5)
except IOError:
self.trace("could not receive child PID")
else:
Modified: py/dist/py/execnet/test_gateway.py
==============================================================================
--- py/dist/py/execnet/test_gateway.py (original)
+++ py/dist/py/execnet/test_gateway.py Mon Oct 25 22:05:14 2004
@@ -107,11 +107,10 @@
# check that the both sides previous channels are really gone
channel.waitclose(0.3)
assert channel.id not in self.gw.channelfactory
- assert c.id not in self.gw.channelfactory
+ #assert c.id not in self.gw.channelfactory
newchan = self.gw.remote_exec('''
assert %d not in channel.gateway.channelfactory
- assert %d not in channel.gateway.channelfactory
- ''' % (c.id, channel.id))
+ ''' % (channel.id))
newchan.waitclose(0.3)
class TestBasicPopenGateway(PopenGatewayTestSetup, BasicRemoteExecution):
Deleted: /py/dist/py/pytest.py
==============================================================================
--- /py/dist/py/pytest.py Mon Oct 25 22:05:14 2004
+++ (empty file)
@@ -1,20 +0,0 @@
-#pythonexecutables = ('python2.2', 'python2.3',)
-#pythonexecutable = 'python2.2'
-
-# in the future we want to be able to say here:
-#def setup_module(extpy):
-# mod = extpy.resolve()
-# mod.module = 23
-# directory = pypath.root.dirpath()
-
-import py
-pkgdir = py.magic.autopath().pkgdir
-
-# default values for options (modified from cmdline)
-verbose = 0
-nocapture = False
-collectonly = False
-exitfirstproblem = False
-fulltrace = False
-showlocals = False
-nomagic = False
Modified: py/dist/py/test/cmdline.py
==============================================================================
--- py/dist/py/test/cmdline.py (original)
+++ py/dist/py/test/cmdline.py Mon Oct 25 22:05:14 2004
@@ -6,7 +6,7 @@
#
# main entry point
#
-configbasename = 'pytest.py'
+configbasename = 'pytestconf.py'
def old(argv):
if argv is None:
@@ -20,19 +20,19 @@
def waitfilechange():
""" wait until project files are changed. """
- pkgdir = py.test.config.getfirst('pkgdir')
- pkgdir = py.path.local(pkgdir)
+ rootdir = py.test.config.getfirst('rootdir')
+ rootdir = py.path.local(rootdir)
fil = py.path.checker(fnmatch='*.py')
rec = py.path.checker(dotfile=0)
statcache = {}
- for path in pkgdir.visit(fil, rec):
+ for path in rootdir.visit(fil, rec):
statcache[path] = path.stat()
+ print "waiting for file change below", str(rootdir)
while 1:
- py.std.time.sleep(0.2)
- for path in pkgdir.visit(fil, rec):
+ py.std.time.sleep(0.4)
+ for path, cst in statcache.items():
st = path.stat()
- cst = statcache[path]
if st.st_mtime != cst.st_mtime or \
st.st_size != cst.st_size:
return
@@ -45,18 +45,6 @@
for x in self._faileditems:
yield x
-def master():
- #if not py.test.config.option.session:
- # break
- #l = driver.getfailed()
- #if l:
- # collectors = [FailingCollector(l)]
- #elif isinstance(fncollectors[0], FailingCollector):
- # collectors = fncollectors
- #else:
- # break
- waitfilechange()
-
def main():
args = py.std.sys.argv[1:]
py.test.config.readconfiguration(*getanchors(args))
@@ -65,54 +53,119 @@
if not filenames:
filenames.append(str(py.path.local()))
- master(args, filenames)
+ try:
+ while 1:
+ failures = master(args, filenames)
+ if not failures or not py.test.config.option.session:
+ break
+ while failures:
+ print "session mode: %d failures remaining" % len(failures)
+ waitfilechange()
+ failures = failure_master(args, filenames, failures)
+ except KeyboardInterrupt:
+ print
+ print "Keybordinterrupt"
+ raise SystemExit, 2
+
+class StdouterrProxy:
+ def __init__(self, gateway):
+ self.gateway = gateway
+ def setup(self):
+ channel = self.gateway.remote_exec("""
+ import sys
+ out, err = channel.newchannel(), channel.newchannel()
+ channel.send(out)
+ channel.send(err)
+ sys.stdout, sys.stderr = out.open('w'), err.open('w')
+ """)
+ self.stdout = channel.receive()
+ self.stderr = channel.receive()
+ channel.waitclose(1.0)
+ py.std.threading.Thread(target=receive2file,
+ args=(self.stdout, sys.stdout)).start()
+ py.std.threading.Thread(target=receive2file,
+ args=(self.stderr, sys.stderr)).start()
+ def teardown(self):
+ self.stdout.close()
+ self.stderr.close()
+
+def waitfinish(channel):
+ try:
+ while 1:
+ try:
+ channel.waitclose(0.1)
+ except IOError:
+ continue
+ else:
+ failures = channel.receive()
+ return failures
+ break
+ finally:
+ #print "closing down channel and gateway"
+ channel.close()
+ channel.gateway.exit()
+
+def failure_master(args, filenames, failures):
+ gw = py.execnet.PopenGateway()
+ outproxy = StdouterrProxy(gw)
+ outproxy.setup()
+ try:
+ channel = gw.remote_exec("""
+ from py.__impl__.test.cmdline import failure_slave
+ failure_slave(channel)
+ """)
+ channel.send((args, filenames))
+ channel.send(failures)
+ return waitfinish(channel)
+ finally:
+ outproxy.teardown()
+def failure_slave(channel):
+ """ we run this on the other side. """
+ args, filenames = channel.receive()
+ filenames = map(py.path.local, filenames)
+ py.test.config.readconfiguration(*filenames)
+ py.test.config.parseargs(args)
+
+ failures = channel.receive()
+ col = FailureCollector(failures)
+ driver = py.test.Driver(channel)
+ failures = driver.run(col)
+ channel.send(failures)
+
+class FailureCollector(py.test.collect.Collector):
+ def __init__(self, failures):
+ self.failures = failures
+ def __iter__(self):
+ for root,modpath in self.failures:
+ extpy = py.path.extpy(root, modpath)
+ yield self.Item(extpy)
+
def master(args, filenames):
gw = py.execnet.PopenGateway()
- channel = gw.remote_exec("""
- from py.__impl__.test.cmdline import slave
- slave(channel)
- """)
- print "sending (args, filenames)"
- channel.send((args, filenames))
- print "receiving stdout/stderr"
- stdout = channel.receive()
- stderr = channel.receive()
- py.std.threading.Thread(target=receive2file,
- args=(stdout, sys.stdout)).start()
- py.std.threading.Thread(target=receive2file,
- args=(stderr, sys.stderr)).start()
- print "waiting"
- while 1:
- try:
- channel.waitclose(0.1)
- except IOError:
- continue
- except KeyboardInterrupt:
- print
- print "keyboard interrupt"
- except:
- py.std.traceback.print_exc()
- channel.close()
- gw.exit()
- break
+ outproxy = StdouterrProxy(gw)
+ outproxy.setup()
+ try:
+ channel = gw.remote_exec("""
+ from py.__impl__.test.cmdline import slave
+ slave(channel)
+ """)
+ channel.send((args, filenames))
+ return waitfinish(channel)
+ finally:
+ outproxy.teardown()
def slave(channel):
""" we run this on the other side. """
args, filenames = channel.receive()
- out, err = channel.newchannel(), channel.newchannel()
- channel.send(out)
- channel.send(err)
- sys.stdout = ChannelFile(out)
- sys.stderr = ChannelFile(err)
-
filenames = map(py.path.local, filenames)
py.test.config.readconfiguration(*filenames)
py.test.config.parseargs(args)
fncollectors = list(getcollectors(filenames))
driver = py.test.Driver(channel)
- driver.run(fncollectors)
+ failures = driver.run(fncollectors)
+ channel.send(failures)
def getcollectors(filenames):
current = py.path.local()
Modified: py/dist/py/test/config.py
==============================================================================
--- py/dist/py/test/config.py (original)
+++ py/dist/py/test/config.py Mon Oct 25 22:05:14 2004
@@ -10,7 +10,7 @@
#
# config file handling (utest.conf)
#
-configbasename = 'pytest.py'
+configbasename = 'pytestconf.py'
class Config:
def __init__(self):
@@ -81,18 +81,6 @@
# setattr(self.option, name, cmdlineoption.__dict__[name])
return remaining
- def restartpython(self):
- # XXX better hack to restart with correct python version?
- pythonexecutable = self.getfirst('pythonexecutable', None)
- if pythonexecutable:
- bn = py.path.local(py.std.sys.executable).basename
- if bn != pythonexecutable:
- # XXX shell escaping
- print "restarting with", pythonexecutable
- print "%s %s" % (pythonexecutable, " ".join(py.std.sys.argv[0:]))
- py.std.os.system("%s %s" % (
- pythonexecutable, " ".join(py.std.sys.argv[0:])))
- return True
config = Config()
Modified: py/dist/py/test/defaultconfig.py
==============================================================================
--- py/dist/py/test/defaultconfig.py (original)
+++ py/dist/py/test/defaultconfig.py Mon Oct 25 22:05:14 2004
@@ -17,6 +17,9 @@
Option('-l', '--showlocals',
action="store_true", dest="showlocals", default=False,
help="show locals in tracebacks (disabled by default)"),
+ Option('', '--session',
+ action="store_true", dest="session", default=False,
+ help="run a test session/rerun only failing tests. "),
Option('', '--fulltrace',
action="store_true", dest="fulltrace", default=False,
help="Don't try to cut any tracebacks (default is to cut)"),
@@ -29,7 +32,4 @@
Option('', '--collectonly',
action="store_true", dest="collectonly", default=False,
help="only collect tests, don't execute them. "),
- Option('', '--session',
- action="store_true", dest="session", default=False,
- help="run a test session to rerun only failing tests. "),
])
Modified: py/dist/py/test/report/text/reporter.py
==============================================================================
--- py/dist/py/test/report/text/reporter.py (original)
+++ py/dist/py/test/report/text/reporter.py Mon Oct 25 22:05:14 2004
@@ -142,10 +142,6 @@
% (result.excinfo[1].__class__.__name__,
result.excinfo[1]))
pdb.post_mortem(result.excinfo[2])
- if self.option.exitfirstproblem:
- if (issubclass(restype, Collector.Error) or
- issubclass(restype, Item.Failed)):
- py.test.exit("first problem, exit configured.")
def report_collect_error(self, error):
restype, c = self.processresult(error)
Modified: py/dist/py/test/run.py
==============================================================================
--- py/dist/py/test/run.py (original)
+++ py/dist/py/test/run.py Mon Oct 25 22:05:14 2004
@@ -2,8 +2,11 @@
import py
collect = py.test.collect
-class Exit(Exception):
+class Exit(Exception):
""" for immediate program exits without tracebacks and reporter/summary. """
+ def __init__(self, item=None):
+ self.item = None
+ Exception.__init__(self)
def exit(*args):
raise Exit(*args)
@@ -25,20 +28,25 @@
self._setupstack = []
self._instance = None
self._channel = channel
+ self._failed = []
def run(self, collectors):
""" main loop for running tests. """
self.setup()
try:
- self.reporter.start()
try:
+ self.reporter.start()
for x in collectors:
self.run_collector_or_item(x)
- except self.Exit:
- pass
- finally:
- self.teardown()
+ finally:
+ self.teardown()
+ except self.Exit, ex:
+ pass
self.reporter.end()
+ l = []
+ for x in self._failed:
+ l.append((str(x.extpy.root), str(x.extpy.modpath)))
+ return l
def run_collector_or_item(self, obj):
""" run (possibly many) testitems and/or collectors. """
@@ -65,7 +73,7 @@
traceback.print_exc()
raise SystemExit, 1
if self.option.exitfirstproblem:
- raise SystemExit, 2
+ raise self.Exit()
else:
raise TypeError("%r is not a Item or Collector instance" % obj)
@@ -88,10 +96,12 @@
raise
except:
res = item.Failed(excinfo=py.std.sys.exc_info())
- if not isinstance(res, item.Passed):
- self._failed.append(item)
res.item = item
self.reporter.enditem(res)
+ if isinstance(res, (item.Failed,)):
+ self._failed.append(item)
+ if py.test.config.option.exitfirstproblem:
+ raise self.Exit(res.item)
def setup_path(self, extpy):
""" setup objects along the path to the test-method
@@ -108,12 +118,9 @@
for x in rest:
stack.append((x, self._setupone(x)))
- def getfailed(self):
- return self._failed
-
def setup(self):
""" setup any neccessary resources. """
- self._failed = []
+ self._failed[:] = []
def teardown(self):
""" teardown any resources the driver knows about. """
Modified: py/dist/py/test/test_config.py
==============================================================================
--- py/dist/py/test/test_config.py (original)
+++ py/dist/py/test/test_config.py Mon Oct 25 22:05:14 2004
@@ -30,9 +30,9 @@
def test_config_order():
from py.__impl__.test import config
o = py.test.config.tmpdir.ensure('configorder', dir=1)
- o.ensure('pytest.py').write('x=1 ; import py ; py._x = [x]')
- o.ensure('a/pytest.py').write('x=2 ; import py ; py._x.append(x)')
- o.ensure('a/b/c/pytest.py').write('x=3 ; import py ; py._x.append(x)')
+ o.ensure('pytestconf.py').write('x=1 ; import py ; py._x = [x]')
+ o.ensure('a/pytestconf.py').write('x=2 ; import py ; py._x.append(x)')
+ o.ensure('a/b/c/pytestconf.py').write('x=3 ; import py ; py._x.append(x)')
cfg = config.Config()
cfg.readconfiguration(o)
assert cfg.getfirst('x') == 1
Copied: py/dist/pytestconf.py (from r7106, py/dist/py/pytest.py)
==============================================================================
--- py/dist/py/pytest.py (original)
+++ py/dist/pytestconf.py Mon Oct 25 22:05:14 2004
@@ -8,7 +8,7 @@
# directory = pypath.root.dirpath()
import py
-pkgdir = py.magic.autopath().pkgdir
+rootdir = py.magic.autopath().dirpath()
# default values for options (modified from cmdline)
verbose = 0
More information about the pytest-commit
mailing list