[pypy-svn] r34897 - in pypy/dist/pypy/objspace/std: . test

mwh at codespeak.net mwh at codespeak.net
Thu Nov 23 15:22:39 CET 2006


Author: mwh
Date: Thu Nov 23 15:22:36 2006
New Revision: 34897

Modified:
   pypy/dist/pypy/objspace/std/test/test_typeobject.py
   pypy/dist/pypy/objspace/std/typeobject.py
   pypy/dist/pypy/objspace/std/typetype.py
Log:
issue204 testing
implement assignment to __bases__
introduces some amount of code duplication which could or should be reduced.


Modified: pypy/dist/pypy/objspace/std/test/test_typeobject.py
==============================================================================
--- pypy/dist/pypy/objspace/std/test/test_typeobject.py	(original)
+++ pypy/dist/pypy/objspace/std/test/test_typeobject.py	Thu Nov 23 15:22:36 2006
@@ -88,7 +88,205 @@
         assert Y.__bases__ ==  (X,)
         class Z(Y,X): pass
         assert Z.__bases__ ==  (Y, X)
-        
+
+        Z.__bases__ = (X,)
+        #print Z.__bases__
+        assert Z.__bases__ == (X,)
+
+    def test_mutable_bases(self):
+        # from CPython's test_descr
+        class C(object):
+            pass
+        class C2(object):
+            def __getattribute__(self, attr):
+                if attr == 'a':
+                    return 2
+                else:
+                    return super(C2, self).__getattribute__(attr)
+            def meth(self):
+                return 1
+        class D(C):
+            pass
+        class E(D):
+            pass
+        d = D()
+        e = E()
+        D.__bases__ = (C,)
+        D.__bases__ = (C2,)
+        #import pdb; pdb.set_trace()
+        assert d.meth() == 1
+        assert e.meth() == 1
+        assert d.a == 2
+        assert e.a == 2
+        assert C2.__subclasses__() == [D]
+
+        # stuff that shouldn't:
+        class L(list):
+            pass
+
+        try:
+            L.__bases__ = (dict,)
+        except TypeError:
+            pass
+        else:
+            assert 0, "shouldn't turn list subclass into dict subclass"
+
+        try:
+            list.__bases__ = (dict,)
+        except TypeError:
+            pass
+        else:
+            assert 0, "shouldn't be able to assign to list.__bases__"
+
+        try:
+            D.__bases__ = (C2, list)
+        except TypeError:
+            pass
+        else:
+            assert 0, "best_base calculation found wanting"
+
+        try:
+            del D.__bases__
+        except (TypeError, AttributeError):
+            pass
+        else:
+            assert 0, "shouldn't be able to delete .__bases__"
+
+        try:
+            D.__bases__ = ()
+        except TypeError, msg:
+            if str(msg) == "a new-style class can't have only classic bases":
+                assert 0, "wrong error message for .__bases__ = ()"
+        else:
+            assert 0, "shouldn't be able to set .__bases__ to ()"
+
+        try:
+            D.__bases__ = (D,)
+        except TypeError:
+            pass
+        else:
+            # actually, we'll have crashed by here...
+            assert 0, "shouldn't be able to create inheritance cycles"
+
+        try:
+            D.__bases__ = (C, C)
+        except TypeError:
+            pass
+        else:
+            assert 0, "didn't detect repeated base classes"
+
+        try:
+            D.__bases__ = (E,)
+        except TypeError:
+            pass
+        else:
+            assert 0, "shouldn't be able to create inheritance cycles"
+
+        # let's throw a classic class into the mix:
+        try:
+            class Classic:
+                __metaclass__ = _classobj
+                def meth2(self):
+                    return 3
+        except NameError:
+            class Classic:
+                def meth2(self):
+                    return 3
+
+        D.__bases__ = (C, Classic)
+
+        assert d.meth2() == 3
+        assert e.meth2() == 3
+        try:
+            d.a
+        except AttributeError:
+            pass
+        else:
+            assert 0, "attribute should have vanished"
+
+        try:
+            D.__bases__ = (Classic,)
+        except TypeError:
+            pass
+        else:
+            assert 0, "new-style class must have a new-style base"
+
+    def test_mutable_bases_with_failing_mro(self):
+        class WorkOnce(type):
+            def __new__(self, name, bases, ns):
+                self.flag = 0
+                return super(WorkOnce, self).__new__(WorkOnce, name, bases, ns)
+            def mro(instance):
+                if instance.flag > 0:
+                    raise RuntimeError, "bozo"
+                else:
+                    instance.flag += 1
+                    return type.mro(instance)
+
+        class WorkAlways(type):
+            def mro(self):
+                # this is here to make sure that .mro()s aren't called
+                # with an exception set (which was possible at one point).
+                # An error message will be printed in a debug build.
+                # What's a good way to test for this?
+                return type.mro(self)
+
+        class C(object):
+            pass
+
+        class C2(object):
+            pass
+
+        class D(C):
+            pass
+
+        class E(D):
+            pass
+
+        class F(D):
+            __metaclass__ = WorkOnce
+
+        class G(D):
+            __metaclass__ = WorkAlways
+
+        # Immediate subclasses have their mro's adjusted in alphabetical
+        # order, so E's will get adjusted before adjusting F's fails.  We
+        # check here that E's gets restored.
+
+        E_mro_before = E.__mro__
+        D_mro_before = D.__mro__
+
+        try:
+            D.__bases__ = (C2,)
+        except RuntimeError:
+            assert D.__mro__ == D_mro_before
+            assert E.__mro__ == E_mro_before
+        else:
+            assert 0, "exception not propagated"
+
+    def test_mutable_bases_catch_mro_conflict(self):
+        class A(object):
+            pass
+
+        class B(object):
+            pass
+
+        class C(A, B):
+            pass
+
+        class D(A, B):
+            pass
+
+        class E(C, D):
+            pass
+
+        try:
+            C.__bases__ = (B, A)
+        except TypeError:
+            pass
+        else:
+            raise TestFailed, "didn't catch MRO conflict"
+
     def test_builtin_add(self):
         x = 5
         assert x.__add__(6) == 11

Modified: pypy/dist/pypy/objspace/std/typeobject.py
==============================================================================
--- pypy/dist/pypy/objspace/std/typeobject.py	(original)
+++ pypy/dist/pypy/objspace/std/typeobject.py	Thu Nov 23 15:22:36 2006
@@ -342,6 +342,16 @@
         else:
             w_self.weak_subclasses_w.append(w_newref)
 
+    def remove_subclass(w_self, w_subclass):
+        space = w_self.space
+
+        for i in range(len(w_self.weak_subclasses_w)):
+            w_ref = w_self.weak_subclasses_w[i]
+            ob = space.call_function(w_ref)
+            if space.is_w(ob, w_subclass):
+                del w_self.weak_subclasses_w[i]
+                return
+
     # for now, weakref support for W_TypeObject is hard to get automatically
     _lifeline_ = None
     def getweakref(self):

Modified: pypy/dist/pypy/objspace/std/typetype.py
==============================================================================
--- pypy/dist/pypy/objspace/std/typetype.py	(original)
+++ pypy/dist/pypy/objspace/std/typetype.py	Thu Nov 23 15:22:36 2006
@@ -1,5 +1,6 @@
 from pypy.interpreter.error import OperationError
 from pypy.interpreter import gateway
+from pypy.interpreter.argument import Arguments
 from pypy.interpreter.typedef import weakref_descr
 from pypy.objspace.std.stdtypedef import *
 
@@ -84,14 +85,118 @@
     w_type = _check(space, w_type,"expected type")
     return space.newlist(w_type.compute_mro())
 
-def descr__bases(space, w_type):
+def descr_get__bases__(space, w_type):
     w_type = _check(space, w_type)
-    from pypy.objspace.std.typeobject import W_TypeObject
-    if not isinstance(w_type, W_TypeObject):
-        raise OperationError(space.w_TypeError, 
-                             space.wrap("descriptor is for 'type'"))
     return space.newtuple(w_type.bases_w)
 
+def mro_subclasses(space, w_type, temp):
+    from pypy.objspace.std.typeobject import W_TypeObject
+    if not w_type.weak_subclasses_w:
+        return
+    for w_ref in w_type.weak_subclasses_w:
+        w_sc = space.call_function(w_ref)
+        if not space.is_w(w_sc, space.w_None):
+            assert isinstance(w_sc, W_TypeObject)
+            temp.append((w_sc, w_sc.mro_w))
+            mro_internal(space, w_sc)
+            mro_subclasses(space, w_sc, temp)
+
+# should be a W_TypeObject method i guess
+def mro_internal(space, w_type):
+    if not space.is_w(space.type(w_type), space.w_type):
+        #w_type.mro_w = []
+        mro_func = space.type(w_type).lookup('mro')
+        mro_func_args = Arguments(space, [w_type])
+        w_mro = space.call_args(mro_func, mro_func_args)
+        w_type.mro_w = space.unpackiterable(w_mro)
+        # do some checking here
+    else:
+        w_type.mro_w = w_type.compute_mro()
+
+def best_base(space, newstyle_bases_w):
+    if not newstyle_bases_w:
+        raise OperationError(space.w_TypeError,
+                             space.wrap("a new-style class can't have only classic bases"))
+    w_bestbase = None
+    w_winner = None
+    for w_base in newstyle_bases_w:
+        w_candidate = w_base.get_layout()
+        if w_winner is None:
+            w_winner = w_candidate
+            w_bestbase = w_base
+        elif space.is_true(space.issubtype(w_winner, w_candidate)):
+            pass
+        elif space.is_true(space.issubtype(w_candidate, w_winner)):
+            w_winner = w_candidate
+            w_bestbase = w_base
+        else:
+            raise OperationError(space.w_TypeError,
+                                 space.wrap("multiple bases have instance lay-out conflict"))
+    return w_bestbase
+
+def descr_set__bases__(space, w_type, w_value):
+    from pypy.objspace.std.typeobject import W_TypeObject
+    # this assumes all app-level type objects are W_TypeObject
+    w_type = _check(space, w_type)
+    if not w_type.is_heaptype():
+        raise OperationError(space.w_TypeError,
+                             space.wrap("can't set %s.__bases__" %
+                                        w_type.name))
+    if not space.is_true(space.isinstance(w_value, space.w_tuple)):
+        raise OperationError(space.w_TypeError,
+                             space.wrap("can only assign tuple to %s.__bases__, not %s"%
+                                        (w_type.name, space.type(w_value).name)))
+    if space.int_w(space.len(w_value)) == 0:
+        raise OperationError(space.w_TypeError,
+                             space.wrap("can only assign non-empty tuple to %s.__bases__, not ()"%
+                                        w_type.name))
+    new_newstyle_bases = []
+    for w_base in space.unpackiterable(w_value):
+        if not isinstance(w_base, W_TypeObject):
+            w_typ = space.type(w_base)
+            if not space.is_w(w_typ, space.w_classobj):
+                raise OperationError(space.w_TypeError,
+                                     space.wrap("%s.__bases__ must be tuple of old- or new-style classes, not '%s'"%
+                                                (w_type.name, w_typ.name)))
+        else:
+            new_newstyle_bases.append(w_base)
+            if space.is_true(space.issubtype(w_base, w_type)):
+                raise OperationError(space.w_TypeError,
+                                     space.wrap("a __bases__ item causes an inheritance cycle"))
+
+    new_base = best_base(space, new_newstyle_bases)
+
+    if w_type.w_bestbase.get_full_instance_layout() != new_base.get_full_instance_layout():
+        raise OperationError(space.w_TypeError,
+                             space.wrap("__bases__ assignment: '%s' object layout differs from '%s'" %
+                                        (w_type.getname(space, '?'), new_base.getname(space, '?'))))
+
+    saved_bases = w_type.bases_w
+    saved_base = w_type.w_bestbase
+    saved_mro = w_type.mro_w
+
+    w_type.bases_w = space.unpackiterable(w_value)
+    w_type.w_bestbase = new_base
+
+    temp = []
+    try:
+        mro_internal(space, w_type)
+
+        mro_subclasses(space, w_type, temp)
+
+        for old_base in saved_bases:
+            if isinstance(old_base, W_TypeObject):
+                old_base.remove_subclass(w_type)
+        for new_base in new_newstyle_bases:
+            new_base.add_subclass(w_type)
+    except:
+        for cls, old_mro in temp:
+            cls.mro_w = old_mro
+        w_type.bases_w = saved_bases
+        w_type.w_bestbase = saved_base
+        w_type.mro_w = saved_mro
+        raise
+    
 def descr__base(space, w_type):
     w_type = _check(space, w_type)
     if w_type.w_bestbase is not None:
@@ -155,7 +260,7 @@
 type_typedef = StdTypeDef("type",
     __new__ = newmethod(descr__new__),
     __name__ = GetSetProperty(descr_get__name__, descr_set__name__),
-    __bases__ = GetSetProperty(descr__bases),
+    __bases__ = GetSetProperty(descr_get__bases__, descr_set__bases__),
     __base__ = GetSetProperty(descr__base),
     __mro__ = GetSetProperty(descr_get__mro__),
     __dict__ = GetSetProperty(descr_get_dict),



More information about the Pypy-commit mailing list