[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