[pypy-svn] r35719 - in pypy/dist/pypy/module/_stackless: . test

arigo at codespeak.net arigo at codespeak.net
Thu Dec 14 07:24:19 CET 2006


Author: arigo
Date: Thu Dec 14 07:24:16 2006
New Revision: 35719

Modified:
   pypy/dist/pypy/module/_stackless/coroutine.py
   pypy/dist/pypy/module/_stackless/interp_coroutine.py
   pypy/dist/pypy/module/_stackless/interp_greenlet.py
   pypy/dist/pypy/module/_stackless/test/test_greenlet.py
Log:
A serious attempt at completing interp_greenlet.  It involved fixing the
parent logic used when Coroutine delegates to py.magic.greenlets, i.e.
on top of CPython.  Doing the Right Thing about parents is kind of easy
with greenlets when nothing else is around, but it's more fun to deal
with when they interact with other pieces of code that have their own
dynamic idea about what the parent should be.

Ported all the py lib greenlet tests.  Only one failure (skipped for
now) about a gc.collect() that should trigger a GreenletExit but
doesn't - to investigate.



Modified: pypy/dist/pypy/module/_stackless/coroutine.py
==============================================================================
--- pypy/dist/pypy/module/_stackless/coroutine.py	(original)
+++ pypy/dist/pypy/module/_stackless/coroutine.py	Thu Dec 14 07:24:16 2006
@@ -123,7 +123,7 @@
         if self.get_is_zombie():
             return
         self.set_is_zombie(True)
-        self.space.userdel(self)
+        self.space.userdel(self.space.wrap(self))
 
     def w_getcurrent(space):
         return space.wrap(AppCoroutine._get_state(space).current)

Modified: pypy/dist/pypy/module/_stackless/interp_coroutine.py
==============================================================================
--- pypy/dist/pypy/module/_stackless/interp_coroutine.py	(original)
+++ pypy/dist/pypy/module/_stackless/interp_coroutine.py	Thu Dec 14 07:24:16 2006
@@ -35,12 +35,13 @@
 
 try:
     from py.magic import greenlet
-    #main_greenlet = greenlet.getcurrent()
+    main_greenlet = greenlet.getcurrent()
 except (ImportError, ValueError):
     def greenlet(*args, **kwargs):
         raise NotImplementedError("need either greenlets or a translated version of pypy")
 
 class FrameChain(object):
+    """Greenlet-based emulation of the primitive rstack 'frames' of RPython"""
 
     def __init__(self, thunk=None):
         if thunk:
@@ -52,11 +53,6 @@
         last = FrameChain()
         return self.greenlet.switch(last)
 
-    def shutdown(self):
-        current = FrameChain()
-        target = current.greenlet.parent
-        target.switch(None)
-
 import sys, os
 
 
@@ -111,6 +107,10 @@
         if self.things_to_do:
             self._do_things_to_do()
 
+    def push_exception(self, exc):
+        self.things_to_do = True
+        self.temp_exc = exc
+
     def check_for_zombie(self, obj):
         return co in self.to_delete
 
@@ -188,22 +188,25 @@
             self.frame = self._greenlet_bind()
 
     def _greenlet_bind(self):
-        state = self.costate
-        self.parent = state.current
-        assert self.parent is not None
         weak = [self]
         def _greenlet_execute(incoming_frame):
             try:
-                return weak[0]._execute(incoming_frame)
-            finally:
-                del weak[0]
-                chain.shutdown()
+                chain2go2next = weak[0]._execute(incoming_frame)
+            except:
+                # no exception is supposed to get out of _execute()
+                # better report it directly into the main greenlet then,
+                # and hidden to prevent catching
+                main_greenlet.throw(AssertionError(
+                    "unexpected exception out of Coroutine._execute()",
+                    *sys.exc_info()))
+                assert 0
+            del weak[0]
+            greenlet.getcurrent().parent = chain2go2next.greenlet
+            return None   # as the result of the FrameChain.switch()
         chain = FrameChain(_greenlet_execute)
         return chain
 
     def _bind(self):
-        state = self.costate
-        self.parent = state.current
         incoming_frame = yield_current_frame_to_caller()
         return self._execute(incoming_frame)
 
@@ -213,9 +216,9 @@
             try:
                 try:
                     exc = None
-                    syncstate.switched(incoming_frame)
                     thunk = self.thunk
                     self.thunk = None
+                    syncstate.switched(incoming_frame)
                     thunk.call()
                     resume_point("coroutine__bind", state)
                 except Exception, e:
@@ -232,8 +235,7 @@
             pass
         except Exception, e:
             # redirect all unhandled exceptions to the parent
-            syncstate.things_to_do = True
-            syncstate.temp_exc = exc
+            syncstate.push_exception(e)
         while self.parent is not None and self.parent.frame is None:
             # greenlet behavior is fine
             self.parent = self.parent.parent
@@ -253,9 +255,21 @@
         if self.frame is None:
             return
         state = self.costate
-        syncstate.things_to_do = True
-        syncstate.temp_exc = CoroutineExit()
-        self.parent = state.current
+        syncstate.push_exception(CoroutineExit())
+        # careful here - if setting self.parent to state.current would
+        # create a loop, break it.  The assumption is that 'self'
+        # will die, so that state.current's chain of parents can be
+        # modified to skip 'self' without too many people noticing.
+        p = state.current
+        if p is self or self.parent is None:
+            pass  # killing the current of the main - don't change any parent
+        else:
+            while p.parent is not None:
+                if p.parent is self:
+                    p.parent = self.parent
+                    break
+                p = p.parent
+            self.parent = state.current
         self.switch()
 
     def _kill_finally(self):

Modified: pypy/dist/pypy/module/_stackless/interp_greenlet.py
==============================================================================
--- pypy/dist/pypy/module/_stackless/interp_greenlet.py	(original)
+++ pypy/dist/pypy/module/_stackless/interp_greenlet.py	Thu Dec 14 07:24:16 2006
@@ -1,155 +1,199 @@
-from pypy.interpreter.baseobjspace import Wrappable
 from pypy.interpreter.argument import Arguments
 from pypy.interpreter.typedef import GetSetProperty, TypeDef
-from pypy.interpreter.typedef import interp_attrproperty, interp_attrproperty_w
 from pypy.interpreter.gateway import interp2app, ObjSpace, W_Root
+from pypy.interpreter.gateway import NoneNotWrapped
 from pypy.interpreter.error import OperationError
-from pypy.interpreter.function import StaticMethod
 
-from pypy.module._stackless.stackless_flags import StacklessFlags
-from pypy.module._stackless.interp_coroutine import Coroutine, BaseCoState, AbstractThunk
-from pypy.module._stackless.coroutine import _AppThunk, makeStaticMethod
+from pypy.module._stackless.interp_coroutine import Coroutine, BaseCoState
+from pypy.module._stackless.interp_coroutine import AbstractThunk, syncstate
+from pypy.module._stackless.coroutine import makeStaticMethod
+
 
 class GreenletThunk(AbstractThunk):
 
-    def __init__(self, space, costate, greenlet):
-        self.space = space
-        self.costate = costate
+    def __init__(self, greenlet):
         self.greenlet = greenlet
 
     def call(self):
-        __args__ = self.costate.__args__
-        assert __args__ is not None
+        greenlet = self.greenlet
+        greenlet.active = True
         try:
-            w_result = self.space.call_args(self.greenlet.w_callable, __args__)
-        except OperationError, operr:
-            self.greenlet.w_dead = self.space.w_True
-            self.costate.operr = operr
-            w_result = self.space.w_None
-        else:
-            self.greenlet.w_dead = self.space.w_True
-        while 1:
-            __args__ = Arguments(self.space, [w_result])
+            space = greenlet.space
+            args_w = greenlet.costate.args_w
+            __args__ = Arguments(space, args_w)
             try:
-                w_result = self.greenlet.w_parent.w_switch(__args__)
-            except OperationError, operr:
-                self.costate.operr = operr
+                w_run = space.getattr(space.wrap(greenlet), space.wrap('run'))
+                greenlet.w_callable = None
+                w_result = space.call_args(w_run, __args__)
+            except OperationError, operror:
+                if not operror.match(space, greenlet.costate.w_GreenletExit):
+                    raise
+                w_result = operror.w_value
+        finally:
+            greenlet.active = False
+        greenlet.costate.args_w = [w_result]
 
 class AppGreenletCoState(BaseCoState):
     def __init__(self, space):
         BaseCoState.__init__(self)
-        self.__args__ = None
+        self.args_w = None
         self.space = space
-        self.operr = None
-        
+        self.w_GreenletExit  = get(space, "GreenletExit")
+        self.w_GreenletError = get(space, "GreenletError")
+
     def post_install(self):
         self.current = self.main = AppGreenlet(self.space, is_main=True)
 
 class AppGreenlet(Coroutine):
     def __init__(self, space, w_callable=None, is_main=False):
+        Coroutine.__init__(self, self._get_state(space))
         self.space = space
         self.w_callable = w_callable
-        self.w_dead = space.w_False
-        self.has_ever_run = False or is_main
-        self.is_main = is_main
-        state = self._get_state(space)
-        if is_main:
-            self.w_parent = None
-        else:
-            w_parent = state.current
-            assert isinstance(w_parent, AppGreenlet)
-            self.w_parent = w_parent
-        Coroutine.__init__(self, state)
+        self.active = is_main
         self.subctx = space.getexecutioncontext().Subcontext()
         if is_main:
             self.subctx.framestack = None     # wack
         else:
-            self.bind(GreenletThunk(space, state, self))
+            self.bind(GreenletThunk(self))
 
-    def descr_method__new__(space, w_subtype, w_callable):
+    def descr_method__new__(space, w_subtype, __args__):
         co = space.allocate_instance(AppGreenlet, w_subtype)
-        AppGreenlet.__init__(co, space, w_callable)
+        AppGreenlet.__init__(co, space)
         return space.wrap(co)
 
+    def descr_method__init__(self, w_run=NoneNotWrapped,
+                                   w_parent=NoneNotWrapped):
+        if w_run is not None:
+            self.w_set_run.im_func(self.space, self, w_run)
+        if w_parent is not None:
+            self.w_set_parent.im_func(self.space, self, w_parent)
+        # XXX strange style above, GetSetProperty needs a cleanup
+
     def _get_state(space):
         return space.fromcache(AppGreenletCoState)
     _get_state = staticmethod(_get_state)
 
     def hello(self):
+        print "hello  ", id(self), self.subctx.framestack.items
+        print syncstate.things_to_do, syncstate.temp_exc
         ec = self.space.getexecutioncontext()
         self.subctx.enter(ec)
 
     def goodbye(self):
         ec = self.space.getexecutioncontext()
         self.subctx.leave(ec)
+        print "goodbye", id(self), self.subctx.framestack.items
+        print syncstate.things_to_do, syncstate.temp_exc
 
     def w_getcurrent(space):
         return space.wrap(AppGreenlet._get_state(space).current)
     w_getcurrent = staticmethod(w_getcurrent)
 
-    def w_switch(self, __args__):
-        #print "switch", __args__, id(self)
-        if __args__.num_kwds():
-            raise OperationError(
-                self.space.w_TypeError,
-                self.space.wrap("switch() takes not keyword arguments"))
-        self.has_ever_run = True
-        self.costate.__args__ = __args__
-        self.switch()
-        #print "after switch"
-        #print self.costate.__args__
-        if self.costate.operr is not None:
-            operr = self.costate.operr
-            self.costate.operr = None
-            raise operr
-        args_w, kwds_w = self.costate.__args__.unpack()
-        if len(args_w) == 1:
-            return args_w[0]
-        return self.space.newtuple(args_w)
-
-    def w_throw(self, w_exception):
-        self.costate.operr = OperationError(w_exception, self.space.wrap(""))
-        self.w_switch(Arguments(self.space, []))
+    def w_switch(self, args_w):
+        # Find the switch target - it might be a parent greenlet
+        space = self.space
+        costate = self.costate
+        target = self
+        while target.isdead():
+            target = target.parent
+            assert isinstance(target, AppGreenlet)
+        # Switch to it
+        costate.args_w = args_w
+        if target is not costate.current:
+            target.switch()
+        else:
+            # case not handled in Coroutine.switch()
+            syncstate._do_things_to_do()
+        result_w = costate.args_w
+        costate.args_w = None
+        # costate.args_w can be set to None above for throw(), but then
+        # switch() should have raised.  At this point cosstate.args_w != None.
+        assert result_w is not None
+        # Return the result of a switch, packaging it in a tuple if
+        # there is more than one value.
+        if len(result_w) == 1:
+            return result_w[0]
+        return space.newtuple(result_w)
+
+    def w_throw(self, w_type=None, w_value=None, w_traceback=None):
+        space = self.space
+        if space.is_w(w_type, space.w_None):
+            w_type = self.costate.w_GreenletExit
+        # Code copied from RAISE_VARARGS but slightly modified.  Not too nice.
+        operror = OperationError(w_type, w_value)
+        operror.normalize_exception(space)
+        if not space.is_w(w_traceback, space.w_None):
+            from pypy.interpreter import pytraceback
+            tb = space.interpclass_w(w_traceback)
+            if tb is None or not space.is_true(space.isinstance(tb, 
+                space.gettypeobject(pytraceback.PyTraceback.typedef))):
+                raise OperationError(space.w_TypeError,
+                      space.wrap("throw: arg 3 must be a traceback or None"))
+            operror.application_traceback = tb
+        # Dead greenlet: turn GreenletExit into a regular return
+        if self.isdead() and operror.match(space, self.costate.w_GreenletExit):
+            args_w = [operror.w_value]
+        else:
+            syncstate.push_exception(operror)
+            args_w = None
+        return self.w_switch(args_w)
 
     def _userdel(self):
-        self.space.userdel(self)
+        self.space.userdel(self.space.wrap(self))
 
-def w_get_is_dead(space, w_self):
-    self = space.interp_w(AppGreenlet, w_self)
-    return self.w_dead
-
-def descr__bool__(space, w_self):
-    self = space.interp_w(AppGreenlet, w_self)
-    return space.wrap(self.has_ever_run and not space.is_true(self.w_dead))
-
-def w_get_parent(space, w_self):
-    self = space.interp_w(AppGreenlet, w_self)
-    if self.w_parent is not None:
-        return self.w_parent
-    else:
-        return space.w_None
-
-def w_set_parent(space, w_self, w_parent):
-    self = space.interp_w(AppGreenlet, w_self)
-    newparent = space.interp_w(AppGreenlet, w_parent)
-    curr = newparent
-    while 1:
-        if space.is_true(space.is_(self, curr)):
-            raise OperationError(space.w_ValueError, space.wrap("cyclic parent chain"))
-        if not curr.w_parent is None:
-            break
-        curr = curr.w_parent
-    self.w_parent = newparent
-
-def w_get_frame(space, w_self):
-    self = space.interp_w(AppGreenlet, w_self)    
-    if (not self.has_ever_run or space.is_true(self.w_dead) or
-        self.costate.current is self):
-        return space.w_None
-    try:
-        return self.subctx.framestack.top(0)
-    except IndexError:
-        return space.w_None
+    def isdead(self):
+        return self.thunk is None and not self.active
+
+    def w_get_is_dead(space, self):
+        return space.newbool(self.isdead())
+
+    def descr__nonzero__(self):
+        return self.space.newbool(self.active)
+
+    def w_get_run(space, self):
+        w_run = self.w_callable
+        if w_run is None:
+            raise OperationError(space.w_AttributeError, space.wrap("run"))
+        return w_run
+
+    def w_set_run(space, self, w_run):
+        space = self.space
+        if self.thunk is None:
+            raise OperationError(space.w_AttributeError,
+                                 space.wrap("run cannot be set "
+                                            "after the start of the greenlet"))
+        self.w_callable = w_run
+
+    def w_del_run(space, self):
+        if self.w_callable is None:
+            raise OperationError(space.w_AttributeError, space.wrap("run"))
+        self.w_callable = None
+
+    def w_get_parent(space, self):
+        return space.wrap(self.parent)
+
+    def w_set_parent(space, self, w_parent):
+        newparent = space.interp_w(AppGreenlet, w_parent)
+        if newparent.costate is not self.costate:
+            raise OperationError(self.costate.w_GreenletError,
+                                 space.wrap("invalid foreign parent"))
+        curr = newparent
+        while curr:
+            if curr is self:
+                raise OperationError(space.w_ValueError,
+                                     space.wrap("cyclic parent chain"))
+            curr = curr.parent
+        self.parent = newparent
+
+    def w_get_frame(space, self):
+        if not self.active or self.costate.current is self:
+            f = None
+        else:
+            try:
+                f = self.subctx.framestack.top(0)
+            except IndexError:
+                f = None
+        return space.wrap(f)
 
 def get(space, name):
     w_module = space.getbuiltinmodule('_stackless')
@@ -158,25 +202,32 @@
 def post_install(module):
     makeStaticMethod(module, 'greenlet', 'getcurrent')
     space = module.space
-    AppGreenlet._get_state(space).post_install()
+    state = AppGreenlet._get_state(space)
+    state.post_install()
     w_module = space.getbuiltinmodule('_stackless')
-    space.appexec([w_module, get(space, "GreenletExit"),
-                   get(space, "GreenletError")], """
+    space.appexec([w_module,
+                   state.w_GreenletExit,
+                   state.w_GreenletError], """
     (mod, exit, error):
         mod.greenlet.GreenletExit = exit
         mod.greenlet.error = error
     """)
 
 AppGreenlet.typedef = TypeDef("greenlet",
-    __new__ = interp2app(AppGreenlet.descr_method__new__.im_func),
+    __new__ = interp2app(AppGreenlet.descr_method__new__.im_func,
+                         unwrap_spec=[ObjSpace, W_Root, Arguments]),
+    __init__ = interp2app(AppGreenlet.descr_method__init__),
     switch = interp2app(AppGreenlet.w_switch,
-                        unwrap_spec=['self', Arguments]),
-    dead = GetSetProperty(w_get_is_dead),
-    parent = GetSetProperty(w_get_parent, w_set_parent),
+                        unwrap_spec=['self', 'args_w']),
+    dead = GetSetProperty(AppGreenlet.w_get_is_dead),
+    run = GetSetProperty(AppGreenlet.w_get_run,
+                         AppGreenlet.w_set_run,
+                         AppGreenlet.w_del_run),
+    parent = GetSetProperty(AppGreenlet.w_get_parent,
+                            AppGreenlet.w_set_parent),
     getcurrent = interp2app(AppGreenlet.w_getcurrent),
     throw = interp2app(AppGreenlet.w_throw),
-    gr_frame = GetSetProperty(w_get_frame),
-    __nonzero__ = interp2app(descr__bool__),
+    gr_frame = GetSetProperty(AppGreenlet.w_get_frame),
+    __nonzero__ = interp2app(AppGreenlet.descr__nonzero__),
     __module__ = '_stackless',
 )
-

Modified: pypy/dist/pypy/module/_stackless/test/test_greenlet.py
==============================================================================
--- pypy/dist/pypy/module/_stackless/test/test_greenlet.py	(original)
+++ pypy/dist/pypy/module/_stackless/test/test_greenlet.py	Thu Dec 14 07:24:16 2006
@@ -1,6 +1,6 @@
 from pypy.conftest import gettestobjspace, skip_on_missing_buildoption
 
-class AppTest_Coroutine:
+class AppTest_Greenlet:
 
     def setup_class(cls):
         space = gettestobjspace(usemodules=('_stackless',))
@@ -193,3 +193,434 @@
         del g2
         assert seen == [greenlet.GreenletExit, greenlet.GreenletExit]
 
+
+# ____________________________________________________________
+#
+# The tests from py.magic.greenlet
+# For now, without the ones that involve threads
+#
+class AppTest_PyMagicTestGreenlet:
+
+    def setup_class(cls):
+        space = gettestobjspace(usemodules=('_stackless',))
+        cls.space = space
+        cls.w_glob = space.appexec([], """():
+            import sys
+            from _stackless import greenlet
+
+            class SomeError(Exception):
+                pass
+
+            def fmain(seen):
+                try:
+                    greenlet.getcurrent().parent.switch()
+                except:
+                    seen.append(sys.exc_info()[0])
+                    raise
+                raise SomeError
+
+            class Glob: pass
+            glob = Glob()
+            glob.__dict__.update(locals())
+            return glob
+        """)
+
+    def test_simple(self):
+        greenlet = self.glob.greenlet
+        lst = []
+        def f():
+            lst.append(1)
+            greenlet.getcurrent().parent.switch()
+            lst.append(3)
+        g = greenlet(f)
+        lst.append(0)
+        g.switch()
+        lst.append(2)
+        g.switch()
+        lst.append(4)
+        assert lst == range(5)
+
+    def test_exception(self):
+        greenlet  = self.glob.greenlet
+        fmain     = self.glob.fmain
+        SomeError = self.glob.SomeError
+        seen = []
+        g1 = greenlet(fmain)
+        g2 = greenlet(fmain)
+        g1.switch(seen)
+        g2.switch(seen)
+        g2.parent = g1
+        assert seen == []
+        raises(SomeError, g2.switch)
+        assert seen == [SomeError]
+        g2.switch()
+        assert seen == [SomeError]
+
+    def test_send_exception(self):
+        greenlet  = self.glob.greenlet
+        fmain     = self.glob.fmain
+        def send_exception(g, exc):
+            # note: send_exception(g, exc)  can be now done with  g.throw(exc).
+            # the purpose of this test is to explicitely check the
+            # propagation rules.
+            def crasher(exc):
+                raise exc
+            g1 = greenlet(crasher, parent=g)
+            g1.switch(exc)
+
+        seen = []
+        g1 = greenlet(fmain)
+        g1.switch(seen)
+        raises(KeyError, "send_exception(g1, KeyError)")
+        assert seen == [KeyError]
+
+    def test_dealloc(self):
+        skip("XXX in-progress: GC handling of greenlets")
+        import gc
+        greenlet = self.glob.greenlet
+        fmain    = self.glob.fmain
+        seen = []
+        g1 = greenlet(fmain)
+        g2 = greenlet(fmain)
+        g1.switch(seen)
+        g2.switch(seen)
+        assert seen == []
+        del g1
+        gc.collect()
+        assert seen == [greenlet.GreenletExit]
+        del g2
+        gc.collect()
+        assert seen == [greenlet.GreenletExit, greenlet.GreenletExit]
+
+    def test_frame(self):
+        import sys
+        greenlet = self.glob.greenlet
+        def f1():
+            f = sys._getframe(0)
+            assert f.f_back is None
+            greenlet.getcurrent().parent.switch(f)
+            return "meaning of life"
+        g = greenlet(f1)
+        frame = g.switch()
+        assert frame is g.gr_frame
+        assert g
+        next = g.switch()
+        assert not g
+        assert next == "meaning of life"
+        assert g.gr_frame is None
+
+
+class AppTest_PyMagicTestThrow:
+
+    def setup_class(cls):
+        space = gettestobjspace(usemodules=('_stackless',))
+        cls.space = space
+
+    def test_class(self):
+        from _stackless import greenlet
+        def switch(*args):
+            return greenlet.getcurrent().parent.switch(*args)
+
+        def f():
+            try:
+                switch("ok")
+            except RuntimeError:
+                switch("ok")
+                return
+            switch("fail")
+
+        g = greenlet(f)
+        res = g.switch()
+        assert res == "ok"
+        res = g.throw(RuntimeError)
+        assert res == "ok"
+
+    def test_val(self):
+        from _stackless import greenlet
+        def switch(*args):
+            return greenlet.getcurrent().parent.switch(*args)
+
+        def f():
+            try:
+                switch("ok")
+            except RuntimeError, val:
+                if str(val) == "ciao":
+                    switch("ok")
+                    return
+            switch("fail")
+
+        g = greenlet(f)
+        res = g.switch()
+        assert res == "ok"
+        res = g.throw(RuntimeError("ciao"))
+        assert res == "ok"
+
+        g = greenlet(f)
+        res = g.switch()
+        assert res == "ok"
+        res = g.throw(RuntimeError, "ciao")
+        assert res == "ok"
+
+    def test_kill(self):
+        from _stackless import greenlet
+        def switch(*args):
+            return greenlet.getcurrent().parent.switch(*args)
+
+        def f():
+            switch("ok")
+            switch("fail")
+
+        g = greenlet(f)
+        res = g.switch()
+        assert res == "ok"
+        res = g.throw()
+        assert isinstance(res, greenlet.GreenletExit)
+        assert g.dead
+        res = g.throw()    # immediately eaten by the already-dead greenlet
+        assert isinstance(res, greenlet.GreenletExit)
+
+    def test_throw_goes_to_original_parent(self):
+        from _stackless import greenlet
+        main = greenlet.getcurrent()
+        def f1():
+            try:
+                main.switch("f1 ready to catch")
+            except IndexError:
+                return "caught"
+            else:
+                return "normal exit"
+        def f2():
+            main.switch("from f2")
+
+        g1 = greenlet(f1)
+        g2 = greenlet(f2, parent=g1)
+        raises(IndexError, g2.throw, IndexError)
+        assert g2.dead
+        assert g1.dead
+
+        g1 = greenlet(f1)
+        g2 = greenlet(f2, parent=g1)
+        res = g1.switch()
+        assert res == "f1 ready to catch"
+        res = g2.throw(IndexError)
+        assert res == "caught"
+        assert g2.dead
+        assert g1.dead
+
+        g1 = greenlet(f1)
+        g2 = greenlet(f2, parent=g1)
+        res = g1.switch()
+        assert res == "f1 ready to catch"
+        res = g2.switch()
+        assert res == "from f2"
+        res = g2.throw(IndexError)
+        assert res == "caught"
+        assert g2.dead
+        assert g1.dead
+            
+
+class AppTest_PyMagicTestGenerator:
+
+    def setup_class(cls):
+        space = gettestobjspace(usemodules=('_stackless',))
+        cls.space = space
+
+    def test_generator(self):
+        from _stackless import greenlet
+
+        class genlet(greenlet):
+
+            def __init__(self, *args, **kwds):
+                self.args = args
+                self.kwds = kwds
+
+            def run(self):
+                fn, = self.fn
+                fn(*self.args, **self.kwds)
+
+            def __iter__(self):
+                return self
+
+            def next(self):
+                self.parent = greenlet.getcurrent()
+                result = self.switch()
+                if self:
+                    return result
+                else:
+                    raise StopIteration
+
+        def Yield(value):
+            g = greenlet.getcurrent()
+            while not isinstance(g, genlet):
+                if g is None:
+                    raise RuntimeError, 'yield outside a genlet'
+                g = g.parent
+            g.parent.switch(value)
+
+        def generator(func):
+            class generator(genlet):
+                fn = (func,)
+            return generator
+
+        # ___ test starts here ___
+        seen = []
+        def g(n):
+            for i in range(n):
+                seen.append(i)
+                Yield(i)
+        g = generator(g)
+        for k in range(3):
+            for j in g(5):
+                seen.append(j)
+        assert seen == 3 * [0, 0, 1, 1, 2, 2, 3, 3, 4, 4]
+
+
+class AppTest_PyMagicTestGeneratorNested:
+
+    def setup_class(cls):
+        space = gettestobjspace(usemodules=('_stackless',))
+        cls.space = space
+        cls.w_glob = space.appexec([], """():
+            from _stackless import greenlet
+
+            class genlet(greenlet):
+
+                def __init__(self, *args, **kwds):
+                    self.args = args
+                    self.kwds = kwds
+                    self.child = None
+
+                def run(self):
+                    fn, = self.fn
+                    fn(*self.args, **self.kwds)
+
+                def __iter__(self):
+                    return self
+
+                def set_child(self, child):
+                    self.child = child
+
+                def next(self):
+                    if self.child:
+                        child = self.child
+                        while child.child:
+                            tmp = child
+                            child = child.child
+                            tmp.child = None
+
+                        result = child.switch()
+                    else:
+                        self.parent = greenlet.getcurrent()            
+                        result = self.switch()
+
+                    if self:
+                        return result
+                    else:
+                        raise StopIteration
+
+            def Yield(value, level = 1):
+                g = greenlet.getcurrent()
+
+                while level != 0:
+                    if not isinstance(g, genlet):
+                        raise RuntimeError, 'yield outside a genlet'
+                    if level > 1:
+                        g.parent.set_child(g)
+                    g = g.parent
+                    level -= 1
+
+                g.switch(value)
+
+            def Genlet(func):
+                class Genlet(genlet):
+                    fn = (func,)
+                return Genlet
+
+            class Glob: pass
+            glob = Glob()
+            glob.__dict__.update(locals())
+            return glob
+        """)
+
+    def test_genlet_1(self):
+        Genlet = self.glob.Genlet
+        Yield  = self.glob.Yield
+
+        def g1(n, seen):
+            for i in range(n):
+                seen.append(i+1)
+                yield i
+
+        def g2(n, seen):
+            for i in range(n):
+                seen.append(i+1)
+                Yield(i)
+
+        g2 = Genlet(g2)
+
+        def nested(i):
+            Yield(i)
+
+        def g3(n, seen):
+            for i in range(n):
+                seen.append(i+1)
+                nested(i)
+        g3 = Genlet(g3)
+
+        raises(RuntimeError, Yield, 10)
+        for g in [g1, g2, g3]:
+            seen = []
+            for k in range(3):
+                for j in g(5, seen):
+                    seen.append(j)
+            assert seen == 3 * [1, 0, 2, 1, 3, 2, 4, 3, 5, 4]
+        raises(RuntimeError, Yield, 10)
+
+    def test_nested_genlets(self):
+        Genlet = self.glob.Genlet
+        Yield  = self.glob.Yield
+        def a(n):
+            if n == 0:
+                return
+            for ii in ax(n-1):
+                Yield(ii)
+            Yield(n)
+        ax = Genlet(a)
+        seen = []
+        for ii in ax(5):
+            seen.append(ii)
+        assert seen == [1, 2, 3, 4, 5]
+
+    def test_perms(self):
+        Genlet = self.glob.Genlet
+        Yield  = self.glob.Yield
+        def perms(l):
+            if len(l) > 1:
+                for e in l:
+                    # No syntactical sugar for generator expressions
+                    [Yield([e] + p) for p in perms([x for x in l if x!=e])]
+            else:
+                Yield(l)
+        perms = Genlet(perms)
+        gen_perms = perms(range(4))
+        permutations = list(gen_perms)
+        assert len(permutations) == 4*3*2*1
+        assert [0,1,2,3] in permutations
+        assert [3,2,1,0] in permutations
+
+    def test_layered_genlets(self):
+        Genlet = self.glob.Genlet
+        Yield  = self.glob.Yield
+        def gr1(n):
+            for ii in range(1, n):
+                Yield(ii)
+                Yield(ii * ii, 2)
+        gr1 = Genlet(gr1)
+        def gr2(n, seen):
+            for ii in gr1(n):
+                seen.append(ii)
+        gr2 = Genlet(gr2)
+        seen = []
+        for ii in gr2(5, seen):
+            seen.append(ii)
+        assert seen == [1, 1, 2, 4, 3, 9, 4, 16]



More information about the Pypy-commit mailing list