summaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/Makefile.am2
-rw-r--r--tests/Makefile.in2
-rw-r--r--tests/test_generictreemodel.py406
-rw-r--r--tests/test_gi.py6
-rw-r--r--tests/test_object_marshaling.py616
-rw-r--r--tests/test_overrides_gtk.py123
6 files changed, 1114 insertions, 41 deletions
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 1d40539..287542d 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -92,6 +92,7 @@ EXTRA_DIST = \
test_internal_api.py \
test_iochannel.py \
test_mainloop.py \
+ test_object_marshaling.py \
test_option.py \
test_properties.py \
test_signal.py \
@@ -107,6 +108,7 @@ EXTRA_DIST = \
test_overrides_gdk.py \
test_overrides_gtk.py \
test_atoms.py \
+ test_generictreemodel.py \
compat_test_pygtk.py \
gi/__init__.py \
gi/overrides/__init__.py \
diff --git a/tests/Makefile.in b/tests/Makefile.in
index a29792b..5c8f709 100644
--- a/tests/Makefile.in
+++ b/tests/Makefile.in
@@ -337,6 +337,7 @@ EXTRA_DIST = \
test_internal_api.py \
test_iochannel.py \
test_mainloop.py \
+ test_object_marshaling.py \
test_option.py \
test_properties.py \
test_signal.py \
@@ -352,6 +353,7 @@ EXTRA_DIST = \
test_overrides_gdk.py \
test_overrides_gtk.py \
test_atoms.py \
+ test_generictreemodel.py \
compat_test_pygtk.py \
gi/__init__.py \
gi/overrides/__init__.py \
diff --git a/tests/test_generictreemodel.py b/tests/test_generictreemodel.py
new file mode 100644
index 0000000..ff0f523
--- /dev/null
+++ b/tests/test_generictreemodel.py
@@ -0,0 +1,406 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# test_generictreemodel - Tests for GenericTreeModel
+# Copyright (C) 2013 Simon Feltman
+#
+# test_generictreemodel.py: Tests for GenericTreeModel
+#
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with this library; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
+# USA
+
+
+# system
+import gc
+import sys
+import weakref
+import unittest
+
+# pygobject
+from gi.repository import GObject
+from gi.repository import Gtk
+from pygtkcompat.generictreemodel import GenericTreeModel
+from pygtkcompat.generictreemodel import _get_user_data_as_pyobject
+
+
+class Node(object):
+ """Represents a generic node with name, value, and children."""
+ def __init__(self, name, value, *children):
+ self.name = name
+ self.value = value
+ self.children = list(children)
+ self.parent = None
+ self.next = None
+
+ for i, child in enumerate(children):
+ child.parent = weakref.ref(self)
+ if i < len(children) - 1:
+ child.next = weakref.ref(children[i + 1])
+
+ def __repr__(self):
+ return 'Node("%s", %s)' % (self.name, self.value)
+
+
+class TesterModel(GenericTreeModel):
+ def __init__(self):
+ super(TesterModel, self).__init__()
+ self.root = Node('root', 0,
+ Node('spam', 1,
+ Node('sushi', 2),
+ Node('bread', 3)
+ ),
+ Node('eggs', 4)
+ )
+
+ def on_get_flags(self):
+ return 0
+
+ def on_get_n_columns(self):
+ return 2
+
+ def on_get_column_type(self, n):
+ return (str, int)[n]
+
+ def on_get_iter(self, path):
+ node = self.root
+ path = list(path)
+ idx = path.pop(0)
+ while path:
+ idx = path.pop(0)
+ node = node.children[idx]
+ return node
+
+ def on_get_path(self, node):
+ def rec_get_path(n):
+ for i, child in enumerate(n.children):
+ if child == node:
+ return [i]
+ else:
+ res = rec_get_path(child)
+ if res:
+ res.insert(0, i)
+
+ return rec_get_path(self.root)
+
+ def on_get_value(self, node, column):
+ if column == 0:
+ return node.name
+ elif column == 1:
+ return node.value
+
+ def on_iter_has_child(self, node):
+ return bool(node.children)
+
+ def on_iter_next(self, node):
+ if node.next:
+ return node.next()
+
+ def on_iter_children(self, node):
+ if node:
+ return node.children[0]
+ else:
+ return self.root
+
+ def on_iter_n_children(self, node):
+ if node is None:
+ return 1
+ return len(node.children)
+
+ def on_iter_nth_child(self, node, n):
+ if node is None:
+ assert n == 0
+ return self.root
+ return node.children[n]
+
+ def on_iter_parent(self, child):
+ if child.parent:
+ return child.parent()
+
+
+class TestReferences(unittest.TestCase):
+ def setUp(self):
+ pass
+
+ def test_c_tree_iter_user_data_as_pyobject(self):
+ obj = object()
+ obj_id = id(obj)
+ ref_count = sys.getrefcount(obj)
+
+ # This is essentially a stolen ref in the context of _CTreeIter.get_user_data_as_pyobject
+ it = Gtk.TreeIter()
+ it.user_data = obj_id
+
+ obj2 = _get_user_data_as_pyobject(it)
+ self.assertEqual(obj, obj2)
+ self.assertEqual(sys.getrefcount(obj), ref_count + 1)
+
+ def test_leak_references_on(self):
+ model = TesterModel()
+ obj_ref = weakref.ref(model.root)
+ # Initial refcount is 1 for model.root + the temporary
+ self.assertEqual(sys.getrefcount(model.root), 2)
+
+ # Iter increases by 1 do to assignment to iter.user_data
+ res, it = model.do_get_iter([0])
+ self.assertEqual(id(model.root), it.user_data)
+ self.assertEqual(sys.getrefcount(model.root), 3)
+
+ # Verify getting a TreeIter more then once does not further increase
+ # the ref count.
+ res2, it2 = model.do_get_iter([0])
+ self.assertEqual(id(model.root), it2.user_data)
+ self.assertEqual(sys.getrefcount(model.root), 3)
+
+ # Deleting the iter does not decrease refcount because references
+ # leak by default (they are stored in the held_refs pool)
+ del it
+ gc.collect()
+ self.assertEqual(sys.getrefcount(model.root), 3)
+
+ # Deleting a model should free all held references to user data
+ # stored by TreeIters
+ del model
+ gc.collect()
+ self.assertEqual(obj_ref(), None)
+
+ def test_row_deleted_frees_refs(self):
+ model = TesterModel()
+ obj_ref = weakref.ref(model.root)
+ # Initial refcount is 1 for model.root + the temporary
+ self.assertEqual(sys.getrefcount(model.root), 2)
+
+ # Iter increases by 1 do to assignment to iter.user_data
+ res, it = model.do_get_iter([0])
+ self.assertEqual(id(model.root), it.user_data)
+ self.assertEqual(sys.getrefcount(model.root), 3)
+
+ # Notifying the underlying model of a row_deleted should decrease the
+ # ref count.
+ model.row_deleted(Gtk.TreePath('0'), model.root)
+ self.assertEqual(sys.getrefcount(model.root), 2)
+
+ # Finally deleting the actual object should collect it completely
+ del model.root
+ gc.collect()
+ self.assertEqual(obj_ref(), None)
+
+ def test_leak_references_off(self):
+ model = TesterModel()
+ model.leak_references = False
+
+ obj_ref = weakref.ref(model.root)
+ # Initial refcount is 1 for model.root + the temporary
+ self.assertEqual(sys.getrefcount(model.root), 2)
+
+ # Iter does not increas count by 1 when leak_references is false
+ res, it = model.do_get_iter([0])
+ self.assertEqual(id(model.root), it.user_data)
+ self.assertEqual(sys.getrefcount(model.root), 2)
+
+ # Deleting the iter does not decrease refcount because assigning user_data
+ # eats references and does not release them.
+ del it
+ gc.collect()
+ self.assertEqual(sys.getrefcount(model.root), 2)
+
+ # Deleting the model decreases the final ref, and the object is collected
+ del model
+ gc.collect()
+ self.assertEqual(obj_ref(), None)
+
+ def test_iteration_refs(self):
+ # Pull iterators off the model using the wrapped C API which will
+ # then call back into the python overrides.
+ model = TesterModel()
+ nodes = [node for node in model.iter_depth_first()]
+ values = [node.value for node in nodes]
+
+ # Verify depth first ordering
+ self.assertEqual(values, [0, 1, 2, 3, 4])
+
+ # Verify ref counts for each of the nodes.
+ # 5 refs for each node at this point:
+ # 1 - ref held in getrefcount function
+ # 2 - ref held by "node" var during iteration
+ # 3 - ref held by local "nodes" var
+ # 4 - ref held by the root/children graph itself
+ # 5 - ref held by the model "held_refs" instance var
+ for node in nodes:
+ self.assertEqual(sys.getrefcount(node), 5)
+
+ # A second iteration and storage of the nodes in a new list
+ # should only increase refcounts by 1 even though new
+ # iterators are created and assigned.
+ nodes2 = [node for node in model.iter_depth_first()]
+ for node in nodes2:
+ self.assertEqual(sys.getrefcount(node), 6)
+
+ # Hold weak refs and start verifying ref collection.
+ node_refs = [weakref.ref(node) for node in nodes]
+
+ # First round of collection
+ del nodes2
+ gc.collect()
+ for node in nodes:
+ self.assertEqual(sys.getrefcount(node), 5)
+
+ # Second round of collection, no more local lists of nodes.
+ del nodes
+ gc.collect()
+ for ref in node_refs:
+ node = ref()
+ self.assertEqual(sys.getrefcount(node), 4)
+
+ # Using invalidate_iters or row_deleted(path, node) will clear out
+ # the pooled refs held internal to the GenericTreeModel implementation.
+ model.invalidate_iters()
+ self.assertEqual(len(model._held_refs), 0)
+ gc.collect()
+ for ref in node_refs:
+ node = ref()
+ self.assertEqual(sys.getrefcount(node), 3)
+
+ # Deleting the root node at this point should allow all nodes to be collected
+ # as there is no longer a way to reach the children
+ del node # node still in locals() from last iteration
+ del model.root
+ gc.collect()
+ for ref in node_refs:
+ self.assertEqual(ref(), None)
+
+
+class TestIteration(unittest.TestCase):
+ def test_iter_next_root(self):
+ model = TesterModel()
+ it = model.get_iter([0])
+ self.assertEqual(it.user_data, id(model.root))
+ self.assertEqual(model.root.next, None)
+
+ it = model.iter_next(it)
+ self.assertEqual(it, None)
+
+ def test_iter_next_multiple(self):
+ model = TesterModel()
+ it = model.get_iter([0, 0])
+ self.assertEqual(it.user_data, id(model.root.children[0]))
+
+ it = model.iter_next(it)
+ self.assertEqual(it.user_data, id(model.root.children[1]))
+
+ it = model.iter_next(it)
+ self.assertEqual(it, None)
+
+
+class ErrorModel(GenericTreeModel):
+ # All on_* methods will raise a NotImplementedError by default
+ pass
+
+
+class ExceptHook(object):
+ """
+ Temporarily installs an exception hook in a context which
+ expects the given exc_type to be raised. This allows verification
+ of exceptions that occur within python gi callbacks but
+ are never bubbled through from python to C back to python.
+ This works because exception hooks are called in PyErr_Print.
+ """
+ def __init__(self, exc_type):
+ self._exc_type = exc_type
+ self._exceptions = []
+
+ def _excepthook(self, exc_type, value, traceback):
+ self._exceptions.append(exc_type)
+
+ def __enter__(self):
+ self._oldhook = sys.excepthook
+ sys.excepthook = self._excepthook
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ sys.excepthook = self._oldhook
+ assert len(self._exceptions) == 1, 'Expecting exactly one exception of type %s' % self._exc_type
+ assert issubclass(self._exceptions[0], self._exc_type), 'Expecting exactly one exception of type %s' % self._exc_type
+
+
+class TestReturnsAfterError(unittest.TestCase):
+ def setUp(self):
+ self.model = ErrorModel()
+
+ def test_get_flags(self):
+ with ExceptHook(NotImplementedError):
+ flags = self.model.get_flags()
+ self.assertEqual(flags, 0)
+
+ def test_get_n_columns(self):
+ with ExceptHook(NotImplementedError):
+ count = self.model.get_n_columns()
+ self.assertEqual(count, 0)
+
+ def test_get_column_type(self):
+ with ExceptHook(NotImplementedError):
+ col_type = self.model.get_column_type(0)
+ self.assertEqual(col_type, GObject.TYPE_INVALID)
+
+ def test_get_iter(self):
+ with ExceptHook(NotImplementedError):
+ self.assertRaises(ValueError, self.model.get_iter, Gtk.TreePath(0))
+
+ def test_get_path(self):
+ it = self.model.create_tree_iter('foo')
+ with ExceptHook(NotImplementedError):
+ path = self.model.get_path(it)
+ self.assertEqual(path, None)
+
+ def test_get_value(self):
+ it = self.model.create_tree_iter('foo')
+ with ExceptHook(NotImplementedError):
+ try:
+ self.model.get_value(it, 0)
+ except TypeError:
+ pass # silence TypeError converting None to GValue
+
+ def test_iter_has_child(self):
+ it = self.model.create_tree_iter('foo')
+ with ExceptHook(NotImplementedError):
+ res = self.model.iter_has_child(it)
+ self.assertEqual(res, False)
+
+ def test_iter_next(self):
+ it = self.model.create_tree_iter('foo')
+ with ExceptHook(NotImplementedError):
+ res = self.model.iter_next(it)
+ self.assertEqual(res, None)
+
+ def test_iter_children(self):
+ with ExceptHook(NotImplementedError):
+ res = self.model.iter_children(None)
+ self.assertEqual(res, None)
+
+ def test_iter_n_children(self):
+ with ExceptHook(NotImplementedError):
+ res = self.model.iter_n_children(None)
+ self.assertEqual(res, 0)
+
+ def test_iter_nth_child(self):
+ with ExceptHook(NotImplementedError):
+ res = self.model.iter_nth_child(None, 0)
+ self.assertEqual(res, None)
+
+ def test_iter_parent(self):
+ child = self.model.create_tree_iter('foo')
+ with ExceptHook(NotImplementedError):
+ res = self.model.iter_parent(child)
+ self.assertEqual(res, None)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/tests/test_gi.py b/tests/test_gi.py
index ee2f3de..4545539 100644
--- a/tests/test_gi.py
+++ b/tests/test_gi.py
@@ -2785,6 +2785,12 @@ class TestPropertiesObject(unittest.TestCase):
self.assertEqual(obj.props.some_variant.get_type_string(), 'b')
self.assertEqual(obj.props.some_variant.get_boolean(), True)
+ def test_setting_several_properties(self):
+ obj = GIMarshallingTests.PropertiesObject()
+ obj.set_properties(some_uchar=54, some_int=42)
+ self.assertEqual(42, obj.props.some_int)
+ self.assertEqual(54, obj.props.some_uchar)
+
class TestKeywords(unittest.TestCase):
def test_method(self):
diff --git a/tests/test_object_marshaling.py b/tests/test_object_marshaling.py
new file mode 100644
index 0000000..62570bc
--- /dev/null
+++ b/tests/test_object_marshaling.py
@@ -0,0 +1,616 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# vim: tabstop=4 shiftwidth=4 expandtab
+
+import unittest
+import weakref
+import gc
+import sys
+import warnings
+
+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 reference 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_transfer_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_transfer_full(self):
+ # Same as do_vfunc_return_object_transfer_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_transfer_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_transfer_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()
+
+
+@unittest.skipUnless(hasattr(VFuncsBase, 'get_ref_info_for_vfunc_return_object_transfer_none') and
+ hasattr(VFuncsBase, 'get_ref_info_for_vfunc_out_object_transfer_none'),
+ 'too old gobject-introspection')
+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
+
+ 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)
+
+ 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 adds an additional ref and gives a warning
+ # of a potential leak. If this occures it is really a bug in the underlying library
+ # but pygobject tries to react to this in a reasonable way.
+ vfuncs = self.VFuncs()
+ with warnings.catch_warnings(record=True) as warn:
+ warnings.simplefilter('always')
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+ self.assertTrue(issubclass(warn[0].category, RuntimeWarning))
+
+ # 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.assertFalse(is_floating)
+
+ gc.collect()
+ self.assertTrue(vfuncs.object_ref() is None)
+
+ def test_vfunc_out_object_transfer_none(self):
+ # Same as above except uses out arg instead of return
+ vfuncs = self.VFuncs()
+ with warnings.catch_warnings(record=True) as warn:
+ warnings.simplefilter('always')
+ ref_count, is_floating = vfuncs.get_ref_info_for_vfunc_out_object_transfer_none()
+ self.assertTrue(issubclass(warn[0].category, RuntimeWarning))
+
+ self.assertEqual(ref_count, 1)
+ self.assertFalse(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)
+
+ 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)
+
+ 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)
+
+
+@unittest.skipUnless(hasattr(VFuncsBase, 'get_ref_info_for_vfunc_return_object_transfer_none') and
+ hasattr(VFuncsBase, 'get_ref_info_for_vfunc_out_object_transfer_none'),
+ 'too old gobject-introspection')
+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
+
+ 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)
+
+ 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)
+
+ 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.
+ 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_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.assertFalse(is_floating)
+
+ gc.collect()
+ self.assertTrue(vfuncs.object_ref() is None)
+
+ 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)
+
+
+@unittest.skipUnless(hasattr(VFuncsBase, 'get_ref_info_for_vfunc_return_object_transfer_none') and
+ hasattr(VFuncsBase, 'get_ref_info_for_vfunc_out_object_transfer_none'),
+ 'too old gobject-introspection')
+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)
+
+ 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 perspective 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 after C cleans up 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_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 perspective 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)
+
+
+@unittest.skipUnless(hasattr(VFuncsBase, 'get_ref_info_for_vfunc_return_object_transfer_none') and
+ hasattr(VFuncsBase, 'get_ref_info_for_vfunc_out_object_transfer_none'),
+ 'too old gobject-introspection')
+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)
+
+ 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 perspective 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 after C cleans up 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_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)
+
+
+class TestPropertyHoldingObject(unittest.TestCase):
+ @unittest.expectedFailure # https://bugzilla.gnome.org/show_bug.cgi?id=675726
+ def test_props_getter_holding_object_ref_count(self):
+ holder = GIMarshallingTests.PropertiesObject()
+ held = GObject.Object()
+
+ self.assertEqual(holder.__grefcount__, 1)
+ self.assertEqual(held.__grefcount__, 1)
+
+ holder.set_property('some-object', held)
+ self.assertEqual(holder.__grefcount__, 1)
+
+ initial_ref_count = held.__grefcount__
+ holder.props.some_object
+ gc.collect()
+ self.assertEqual(held.__grefcount__, initial_ref_count)
+
+ @unittest.skipUnless(hasattr(GIMarshallingTests.PropertiesObject.props, 'some_object'),
+ 'too old gobject-introspection')
+ def test_get_property_holding_object_ref_count(self):
+ holder = GIMarshallingTests.PropertiesObject()
+ held = GObject.Object()
+
+ self.assertEqual(holder.__grefcount__, 1)
+ self.assertEqual(held.__grefcount__, 1)
+
+ holder.set_property('some-object', held)
+ self.assertEqual(holder.__grefcount__, 1)
+
+ initial_ref_count = held.__grefcount__
+ holder.get_property('some-object')
+ gc.collect()
+ self.assertEqual(held.__grefcount__, initial_ref_count)
+
+ @unittest.expectedFailure # https://bugzilla.gnome.org/show_bug.cgi?id=675726
+ def test_props_setter_holding_object_ref_count(self):
+ holder = GIMarshallingTests.PropertiesObject()
+ held = GObject.Object()
+
+ self.assertEqual(holder.__grefcount__, 1)
+ self.assertEqual(held.__grefcount__, 1)
+
+ # Setting property should only increase ref count by 1
+ holder.props.some_object = held
+ self.assertEqual(holder.__grefcount__, 1)
+ self.assertEqual(held.__grefcount__, 2)
+
+ # Clearing should pull it back down
+ holder.props.some_object = None
+ self.assertEqual(held.__grefcount__, 1)
+
+ @unittest.expectedFailure # https://bugzilla.gnome.org/show_bug.cgi?id=675726
+ def test_set_property_holding_object_ref_count(self):
+ holder = GIMarshallingTests.PropertiesObject()
+ held = GObject.Object()
+
+ self.assertEqual(holder.__grefcount__, 1)
+ self.assertEqual(held.__grefcount__, 1)
+
+ # Setting property should only increase ref count by 1
+ holder.set_property('some-object', held)
+ self.assertEqual(holder.__grefcount__, 1)
+ self.assertEqual(held.__grefcount__, 2)
+
+ # Clearing should pull it back down
+ holder.set_property('some-object', None)
+ self.assertEqual(held.__grefcount__, 1)
diff --git a/tests/test_overrides_gtk.py b/tests/test_overrides_gtk.py
index 9315019..69f0d38 100644
--- a/tests/test_overrides_gtk.py
+++ b/tests/test_overrides_gtk.py
@@ -2,6 +2,7 @@
# coding: UTF-8
# vim: tabstop=4 shiftwidth=4 expandtab
+import contextlib
import unittest
from compathelper import _unicode, _bytes
@@ -17,6 +18,38 @@ except ImportError:
Gtk = None
+@contextlib.contextmanager
+def realized(widget):
+ """Makes sure the widget is realized.
+
+ view = Gtk.TreeView()
+ with realized(view):
+ do_something(view)
+ """
+
+ if isinstance(widget, Gtk.Window):
+ toplevel = widget
+ else:
+ toplevel = widget.get_parent_window()
+
+ if toplevel is None:
+ window = Gtk.Window()
+ window.add(widget)
+
+ widget.realize()
+ while Gtk.events_pending():
+ Gtk.main_iteration()
+ assert widget.get_realized()
+ yield widget
+
+ if toplevel is None:
+ window.remove(widget)
+ window.destroy()
+
+ while Gtk.events_pending():
+ Gtk.main_iteration()
+
+
@unittest.skipUnless(Gtk, 'Gtk not available')
class TestGtk(unittest.TestCase):
def test_container(self):
@@ -519,6 +552,38 @@ class TestGtk(unittest.TestCase):
self.assertTrue(hasattr(widget.drag_dest_set_proxy, '__call__'))
self.assertTrue(hasattr(widget.drag_get_data, '__call__'))
+ def test_drag_target_list(self):
+ mixed_target_list = [Gtk.TargetEntry.new('test0', 0, 0),
+ ('test1', 1, 1),
+ Gtk.TargetEntry.new('test2', 2, 2),
+ ('test3', 3, 3)]
+
+ def _test_target_list(targets):
+ for i, target in enumerate(targets):
+ self.assertTrue(isinstance(target, Gtk.TargetEntry))
+ self.assertEqual(target.target, 'test' + str(i))
+ self.assertEqual(target.flags, i)
+ self.assertEqual(target.info, i)
+
+ _test_target_list(Gtk._construct_target_list(mixed_target_list))
+
+ widget = Gtk.Button()
+ widget.drag_dest_set(Gtk.DestDefaults.DROP, None, Gdk.DragAction.COPY)
+ widget.drag_dest_set_target_list(mixed_target_list)
+ widget.drag_dest_get_target_list()
+
+ widget.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, None, Gdk.DragAction.MOVE)
+ widget.drag_source_set_target_list(mixed_target_list)
+ widget.drag_source_get_target_list()
+
+ treeview = Gtk.TreeView()
+ treeview.enable_model_drag_source(Gdk.ModifierType.BUTTON1_MASK,
+ mixed_target_list,
+ Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
+
+ treeview.enable_model_drag_dest(mixed_target_list,
+ Gdk.DragAction.DEFAULT | Gdk.DragAction.MOVE)
+
def test_scrollbar(self):
# PyGTK compat
adjustment = Gtk.Adjustment()
@@ -1363,19 +1428,13 @@ class TestTreeView(unittest.TestCase):
store.append((0, "foo"))
store.append((1, "bar"))
view = Gtk.TreeView()
- # FIXME: We can't easily call get_cursor() to make sure this works as
- # expected as we need to realize and focus the column; the following
- # will raise a Gtk-CRITICAL which we ignore for now
- old_mask = GLib.log_set_always_fatal(
- GLib.LogLevelFlags.LEVEL_WARNING | GLib.LogLevelFlags.LEVEL_ERROR)
- try:
+
+ with realized(view):
view.set_cursor(store[1].path)
view.set_cursor(str(store[1].path))
view.get_cell_area(store[1].path)
view.get_cell_area(str(store[1].path))
- finally:
- GLib.log_set_always_fatal(old_mask)
def test_tree_view_column(self):
cell = Gtk.CellRendererText()
@@ -1403,28 +1462,21 @@ class TestTreeView(unittest.TestCase):
# unconnected
tree.insert_column_with_attributes(-1, 'Head4', cell4)
- # might cause a Pango warning, do not break on this
- old_mask = GLib.log_set_always_fatal(
- GLib.LogLevelFlags.LEVEL_CRITICAL | GLib.LogLevelFlags.LEVEL_ERROR)
- try:
- # We must realize the TreeView for cell.props.text to receive a value
- dialog = Gtk.Dialog()
- dialog.get_action_area().add(tree)
- dialog.show_all()
- dialog.hide()
- finally:
- GLib.log_set_always_fatal(old_mask)
+ with realized(tree):
+ tree.set_cursor(model[0].path)
+ while Gtk.events_pending():
+ Gtk.main_iteration()
- self.assertEqual(tree.get_column(0).get_title(), 'Head1')
- self.assertEqual(tree.get_column(1).get_title(), 'Head2')
- self.assertEqual(tree.get_column(2).get_title(), 'Head3')
- self.assertEqual(tree.get_column(3).get_title(), 'Head4')
+ self.assertEqual(tree.get_column(0).get_title(), 'Head1')
+ self.assertEqual(tree.get_column(1).get_title(), 'Head2')
+ self.assertEqual(tree.get_column(2).get_title(), 'Head3')
+ self.assertEqual(tree.get_column(3).get_title(), 'Head4')
- # cursor should be at the first row
- self.assertEqual(cell1.props.text, 'cell11')
- self.assertEqual(cell2.props.text, 'cell12')
- self.assertEqual(cell3.props.text, 'cell13')
- self.assertEqual(cell4.props.text, None)
+ # cursor should be at the first row
+ self.assertEqual(cell1.props.text, 'cell11')
+ self.assertEqual(cell2.props.text, 'cell12')
+ self.assertEqual(cell3.props.text, 'cell13')
+ self.assertEqual(cell4.props.text, None)
def test_tree_view_column_set_attributes(self):
store = Gtk.ListStore(int, str)
@@ -1442,19 +1494,8 @@ class TestTreeView(unittest.TestCase):
column.pack_start(cell, expand=True)
column.set_attributes(cell, text=1)
- # might cause a Pango warning, do not break on this
- old_mask = GLib.log_set_always_fatal(
- GLib.LogLevelFlags.LEVEL_CRITICAL | GLib.LogLevelFlags.LEVEL_ERROR)
- try:
- # We must realize the TreeView for cell.props.text to receive a value
- dialog = Gtk.Dialog()
- dialog.get_action_area().add(treeview)
- dialog.show_all()
- dialog.hide()
- finally:
- GLib.log_set_always_fatal(old_mask)
-
- self.assertTrue(cell.props.text in directors)
+ with realized(treeview):
+ self.assertTrue(cell.props.text in directors)
def test_tree_selection(self):
store = Gtk.ListStore(int, str)