[pygobject] Add tests for vfunc object arguments and returns



commit 97f48f5dcabc8dad4480727a78416b1c2a220777
Author: Simon Feltman <sfeltman src gnome org>
Date:   Wed Jan 30 04:35:32 2013 -0800

    Add tests for vfunc object arguments and returns
    
    Add tests which use different combinations of floating, transfer full,
    transfer none, and held wrapper as in, out, or return arguments to vfuncs.
    Most of these are marked as skip or expectedFailure due to various bugs
    noted on the tests.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=687522

 gi/overrides/GObject.py         |   15 +-
 tests/test_object_marshaling.py |  540 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 550 insertions(+), 5 deletions(-)
---
diff --git a/gi/overrides/GObject.py b/gi/overrides/GObject.py
index e0ca4a4..458db55 100644
--- a/gi/overrides/GObject.py
+++ b/gi/overrides/GObject.py
@@ -460,18 +460,23 @@ class Object(GObjectModule.Object):
     # they work as gi methods.
     bind_property_full = _unsupported_method
     compat_control = _unsupported_method
-    force_floating = _unsupported_method
     interface_find_property = _unsupported_method
     interface_install_property = _unsupported_method
     interface_list_properties = _unsupported_method
-    is_floating = _unsupported_method
     notify_by_pspec = _unsupported_method
+    run_dispose = _unsupported_method
+    watch_closure = _unsupported_method
+
+    # Make all reference management methods private but still accessible.
+    _ref = GObjectModule.Object.ref
+    _ref_sink = GObjectModule.Object.ref_sink
+    _unref = GObjectModule.Object.unref
+    _force_floating = GObjectModule.Object.force_floating
+
     ref = _unsupported_method
-    ref_count = _unsupported_method
     ref_sink = _unsupported_method
-    run_dispose = _unsupported_method
     unref = _unsupported_method
-    watch_closure = _unsupported_method
+    force_floating = _unsupported_method
 
     # The following methods are static APIs which need to leap frog the
     # gi methods until we verify the gi methods can replace them.
diff --git a/tests/test_object_marshaling.py b/tests/test_object_marshaling.py
new file mode 100644
index 0000000..87f7043
--- /dev/null
+++ b/tests/test_object_marshaling.py
@@ -0,0 +1,540 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+import unittest
+import weakref
+import gc
+import sys
+
+from gi.repository import GObject
+from gi.repository import GIMarshallingTests
+
+
+class StrongRef(object):
+    # A class that behaves like weakref.ref but holds a strong reference.
+    # This allows re-use of the VFuncsBase by swapping out the ObjectRef
+    # class var with either weakref.ref or StrongRef.
+
+    def __init__(self, obj):
+        self.obj = obj
+
+    def __call__(self):
+        return self.obj
+
+
+class VFuncsBase(GIMarshallingTests.Object):
+    # Class which generically implements the vfuncs used for refernce counting tests
+    # in a way that can be easily sub-classed and modified.
+
+    #: Object type used by this class for testing
+    #: This can be GObject.Object or GObject.InitiallyUnowned
+    Object = GObject.Object
+
+    #: Reference type used by this class for holding refs to in/out objects.
+    #: This can be set to weakref.ref or StrongRef
+    ObjectRef = weakref.ref
+
+    def __init__(self):
+        super(VFuncsBase, self).__init__()
+
+        #: Hold ref of input or output python wrappers
+        self.object_ref = None
+
+        #: store grefcount of input object
+        self.in_object_grefcount = None
+        self.in_object_is_floating = None
+
+    def do_vfunc_return_object_transfer_none(self):
+        # Return an object but keep a python reference to it.
+        obj = self.Object()
+        self.object_ref = self.ObjectRef(obj)
+        return obj
+
+    def do_vfunc_return_object_tansfer_full(self):
+        # Return an object and hand off the reference to the caller.
+        obj = self.Object()
+        self.object_ref = self.ObjectRef(obj)
+        return obj
+
+    def do_vfunc_out_object_transfer_none(self):
+        # Same as do_vfunc_return_object_transfer_none but the pygi
+        # internals convert the return here into an out arg.
+        obj = self.Object()
+        self.object_ref = self.ObjectRef(obj)
+        return obj
+
+    def do_vfunc_out_object_tansfer_full(self):
+        # Same as do_vfunc_return_object_tansfer_full but the pygi
+        # internals convert the return here into an out arg.
+        obj = self.Object()
+        self.object_ref = self.ObjectRef(obj)
+        return obj
+
+    def do_vfunc_in_object_tansfer_none(self, obj):
+        # 'obj' will have a python wrapper as well as still held
+        # by the caller.
+        self.object_ref = self.ObjectRef(obj)
+        self.in_object_grefcount = obj.__grefcount__
+        self.in_object_is_floating = obj.is_floating()
+
+    def do_vfunc_in_object_tansfer_full(self, obj):
+        # 'obj' will now be owned by the Python GObject wrapper.
+        # When obj goes out of scope and is collected, the GObject
+        # should also be fully released.
+        self.object_ref = self.ObjectRef(obj)
+        self.in_object_grefcount = obj.__grefcount__
+        self.in_object_is_floating = obj.is_floating()
+
+
+class TestVFuncsWithObjectArg(unittest.TestCase):
+    # Basic set of tests which work on non-floating objects which python does
+    # not keep an additional reference of.
+
+    class VFuncs(VFuncsBase):
+        # Object for testing non-floating objects without holding any refs.
+        Object = GObject.Object
+        ObjectRef = weakref.ref
+
+    @unittest.expectedFailure  # bug 675726
+    def test_vfunc_self_arg_ref_count(self):
+        # Check to make sure vfunc "self" arguments don't leak.
+        vfuncs = self.VFuncs()
+        vfuncs_ref = weakref.ref(vfuncs)
+        vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()  # Use any vfunc to test this.
+
+        gc.collect()
+        self.assertEqual(sys.getrefcount(vfuncs), 2)
+        self.assertEqual(vfuncs.__grefcount__, 1)
+
+        del vfuncs
+        gc.collect()
+        self.assertTrue(vfuncs_ref() is None)
+
+    @unittest.skip('bug 687522, should float returned object')
+    def test_vfunc_return_object_transfer_none(self):
+        # This tests a problem case where the vfunc returns a GObject owned solely by Python
+        # but the argument is marked as transfer-none.
+        # In this case pygobject marshaling should add an additional ref and give a warning
+        # of a potential leak.
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_none()
+
+        # The ref count of the GObject returned to the caller (get_ref_info_for_vfunc_return_object_transfer_none)
+        # should be a single floating ref
+        self.assertEqual(ref_count, 1)
+        self.assertTrue(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    @unittest.skip('bug 687522, should float returned object')
+    def test_vfunc_out_object_transfer_none(self):
+        # Same as above except uses out arg instead of return
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+
+        self.assertEqual(ref_count, 1)
+        self.assertTrue(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    def test_vfunc_return_object_transfer_full(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()
+
+        # The vfunc caller receives full ownership of a single ref which should not
+        # be floating.
+        self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    def test_vfunc_out_object_transfer_full(self):
+        # Same as above except uses out arg instead of return
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full()
+
+        self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    @unittest.expectedFailure  # bug 675726
+    def test_vfunc_in_object_transfer_none(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_none(self.VFuncs.Object)
+
+        gc.collect()
+        self.assertEqual(vfuncs.in_object_grefcount, 2)  # initial + python wrapper
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        self.assertEqual(ref_count, 1)  # ensure python wrapper released
+        self.assertFalse(is_floating)
+
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    @unittest.expectedFailure  # bug 675726
+    def test_vfunc_in_object_transfer_full(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_full(self.VFuncs.Object)
+
+        gc.collect()
+
+        # python wrapper should take sole ownership of the gobject
+        self.assertEqual(vfuncs.in_object_grefcount, 1)
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        # ensure python wrapper took ownership and released, after vfunc was complete
+        self.assertEqual(ref_count, 0)
+        self.assertFalse(is_floating)
+
+        self.assertTrue(vfuncs.object_ref() is None)
+
+
+class TestVFuncsWithFloatingArg(unittest.TestCase):
+    # All tests here work with a floating object by using InitiallyUnowned as the argument
+
+    class VFuncs(VFuncsBase):
+        # Object for testing non-floating objects without holding any refs.
+        Object = GObject.InitiallyUnowned
+        ObjectRef = weakref.ref
+
+    @unittest.skip('bug 687522, should float returned object')
+    def test_vfunc_return_object_transfer_none_with_floating(self):
+        # Python is expected to return a single floating reference without warning.
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_none()
+
+        # The ref count of the GObject returned to the caller (get_ref_info_for_vfunc_return_object_transfer_none)
+        # should be a single floating ref
+        self.assertEqual(ref_count, 1)
+        self.assertTrue(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    @unittest.skip('bug 687522, should float returned object')
+    def test_vfunc_out_object_transfer_none_with_floating(self):
+        # Same as above except uses out arg instead of return
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+
+        self.assertEqual(ref_count, 1)
+        self.assertTrue(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    @unittest.expectedFailure  # bug 687522, should float returned object
+    def test_vfunc_return_object_transfer_full_with_floating(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()
+
+        # The vfunc caller receives full ownership of a single ref which should be floating.
+        self.assertEqual(ref_count, 1)
+        self.assertTrue(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    @unittest.expectedFailure  # bug 687522, should float returned object
+    def test_vfunc_out_object_transfer_full_with_floating(self):
+        # Same as above except uses out arg instead of return
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full()
+
+        self.assertEqual(ref_count, 1)
+        self.assertTrue(is_floating)
+
+        gc.collect()
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    @unittest.expectedFailure  # bug 675726, should maintain floating ref
+    def test_vfunc_in_object_transfer_none_with_floating(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_none(self.VFuncs.Object)
+
+        gc.collect()
+
+        # python wrapper should maintain the object as floating and add an additional ref
+        self.assertEqual(vfuncs.in_object_grefcount, 2)
+        self.assertTrue(vfuncs.in_object_is_floating)
+
+        # vfunc caller should only have a single floating ref after the vfunc finishes
+        self.assertEqual(ref_count, 1)
+        self.assertTrue(is_floating)
+
+        self.assertTrue(vfuncs.object_ref() is None)
+
+    def test_vfunc_in_object_transfer_full_with_floating(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_full(self.VFuncs.Object)
+
+        gc.collect()
+
+        # python wrapper sinks and owns the gobject
+        self.assertEqual(vfuncs.in_object_grefcount, 1)
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        # ensure python wrapper took ownership and released
+        self.assertEqual(ref_count, 0)
+        self.assertFalse(is_floating)
+
+        self.assertTrue(vfuncs.object_ref() is None)
+
+
+class TestVFuncsWithHeldObjectArg(unittest.TestCase):
+    # Same tests as TestVFuncsWithObjectArg except we hold
+    # onto the python object reference in all cases.
+
+    class VFuncs(VFuncsBase):
+        # Object for testing non-floating objects with a held ref.
+        Object = GObject.Object
+        ObjectRef = StrongRef
+
+    def test_vfunc_return_object_transfer_none_with_held_object(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_none()
+
+        # Python holds the single gobject ref in 'vfuncs.object_ref'
+        # Because of this, we do not expect a floating ref or a ref increase.
+        self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        # The actual grefcount should stay at 1 even after the vfunc return.
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref)
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_out_object_transfer_none_with_held_object(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+
+        self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref)
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_return_object_transfer_full_with_held_object(self):
+        # The vfunc caller receives full ownership which should not
+        # be floating. However, the held python wrapper also has a ref.
+
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()
+
+        # Ref count from the perspective of C after the vfunc is called
+        # The vfunc caller receives a new reference which should not
+        # be floating. However, the held python wrapper also has a ref.
+        self.assertEqual(ref_count, 2)
+        self.assertFalse(is_floating)
+
+        # Current ref count
+        # The vfunc caller should have decremented its reference.
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref)
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_out_object_transfer_full_with_held_object(self):
+        # Same test as above except uses out arg instead of return
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full()
+
+        # Ref count from the perspective of C after the vfunc is called
+        # The vfunc caller receives a new reference which should not
+        # be floating. However, the held python wrapper also has a ref.
+        self.assertEqual(ref_count, 2)
+        self.assertFalse(is_floating)
+
+        # Current ref count
+        # The vfunc caller should have decremented its reference.
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref())
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    @unittest.expectedFailure  # bug 675726, leaks
+    def test_vfunc_in_object_transfer_none_with_held_object(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_none(self.VFuncs.Object)
+
+        gc.collect()
+
+        # Ref count inside vfunc from the perpsective of Python
+        self.assertEqual(vfuncs.in_object_grefcount, 2)  # initial + python wrapper
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertEqual(ref_count, 2)  # kept after vfunc + held python wrapper
+        self.assertFalse(is_floating)
+
+        # Current ref count
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 2)  # initial + python wrapper
+
+        held_object_ref = weakref.ref(vfuncs.object_ref())
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    @unittest.expectedFailure  # bug 675726, leaks
+    def test_vfunc_in_object_transfer_full_with_held_object(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_full(self.VFuncs.Object)
+
+        gc.collect()
+
+        # Ref count inside vfunc from the perpsective of Python
+        self.assertEqual(vfuncs.in_object_grefcount, 1)  # python wrapper takes ownership of the gobject
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        # Current ref count
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref())
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+
+class TestVFuncsWithHeldFloatingArg(unittest.TestCase):
+    # Tests for a floating object which we hold a reference to the python wrapper
+    # on the VFuncs test class.
+
+    class VFuncs(VFuncsBase):
+        # Object for testing floating objects with a held ref.
+        Object = GObject.InitiallyUnowned
+        ObjectRef = StrongRef
+
+    def test_vfunc_return_object_transfer_none_with_held_floating(self):
+        # Python holds onto the wrapper which basically means the floating ref
+        # should also be owned by python.
+
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_none()
+
+        # This is a borrowed ref from what is held in python.
+        self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        # The actual grefcount should stay at 1 even after the vfunc return.
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref)
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_out_object_transfer_none_with_held_floating(self):
+        # Same as above
+
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+
+        self.assertEqual(ref_count, 1)
+        self.assertFalse(is_floating)
+
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref)
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_return_object_transfer_full_with_held_floating(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_return_object_transfer_full()
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertEqual(ref_count, 2)
+        self.assertFalse(is_floating)
+
+        # Current ref count
+        # vfunc wrapper destroyes ref it was given
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref)
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_out_object_transfer_full_with_held_floating(self):
+        # Same test as above except uses out arg instead of return
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_full()
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertEqual(ref_count, 2)
+        self.assertFalse(is_floating)
+
+        # Current ref count
+        # vfunc wrapper destroyes ref it was given
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref())
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    @unittest.expectedFailure  # bug 675726, should maintain floating
+    def test_vfunc_in_floating_transfer_none_with_held_floating(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_none(self.VFuncs.Object)
+        gc.collect()
+
+        # Ref count inside vfunc from the perpsective of Python
+        self.assertTrue(vfuncs.in_object_is_floating)
+        self.assertEqual(vfuncs.in_object_grefcount, 2)  # python wrapper sinks and owns the gobject
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertTrue(is_floating)
+        self.assertEqual(ref_count, 2)  # floating + held by wrapper
+
+        # Current ref count
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 2)  # floating + held by wrapper
+
+        held_object_ref = weakref.ref(vfuncs.object_ref())
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)
+
+    def test_vfunc_in_floating_transfer_full_with_held_floating(self):
+        vfuncs = self.VFuncs()
+        ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_in_object_transfer_full(self.VFuncs.Object)
+        gc.collect()
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertEqual(vfuncs.in_object_grefcount, 1)  # python wrapper sinks and owns the gobject
+        self.assertFalse(vfuncs.in_object_is_floating)
+
+        # Ref count from the perspective of C after the vfunc is called
+        self.assertEqual(ref_count, 1)  # held by wrapper
+        self.assertFalse(is_floating)
+
+        # Current ref count
+        self.assertEqual(vfuncs.object_ref().__grefcount__, 1)
+
+        held_object_ref = weakref.ref(vfuncs.object_ref())
+        del vfuncs.object_ref
+        gc.collect()
+        self.assertTrue(held_object_ref() is None)



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]