8 from gi.repository import GObject
9 from gi import PyGIDeprecationWarning
10 from gi.module import get_introspection_module
11 from gi._gobject import _gobject
16 class TestGObjectAPI(unittest.TestCase):
17 def test_gobject_inheritance(self):
18 # GObject.Object is a class hierarchy as follows:
19 # overrides.Object -> introspection.Object -> static.GObject
20 GIObjectModule = get_introspection_module('GObject')
21 self.assertTrue(issubclass(GObject.Object, GIObjectModule.Object))
22 self.assertTrue(issubclass(GIObjectModule.Object, _gobject.GObject))
24 self.assertEqual(_gobject.GObject.__gtype__, GObject.TYPE_OBJECT)
25 self.assertEqual(GIObjectModule.Object.__gtype__, GObject.TYPE_OBJECT)
26 self.assertEqual(GObject.Object.__gtype__, GObject.TYPE_OBJECT)
28 # The pytype wrapper should hold the outer most Object class from overrides.
29 self.assertEqual(GObject.TYPE_OBJECT.pytype, GObject.Object)
31 @unittest.skipIf(sys.version_info[:2] < (2, 7), 'Python 2.7 is required')
32 def test_gobject_unsupported_overrides(self):
33 obj = GObject.Object()
35 with self.assertRaisesRegex(RuntimeError, 'Data access methods are unsupported.*'):
38 with self.assertRaisesRegex(RuntimeError, 'This method is currently unsupported.*'):
41 def test_compat_api(self):
42 with warnings.catch_warnings(record=True) as w:
43 warnings.simplefilter('always')
44 # GObject formerly exposed a lot of GLib's functions
45 self.assertEqual(GObject.markup_escape_text('foo'), 'foo')
47 ml = GObject.MainLoop()
48 self.assertFalse(ml.is_running())
50 context = GObject.main_context_default()
51 self.assertTrue(context.pending() in [False, True])
53 context = GObject.MainContext()
54 self.assertFalse(context.pending())
56 self.assertTrue(issubclass(w[0].category, PyGIDeprecationWarning))
57 self.assertTrue('GLib.markup_escape_text' in str(w[0]), str(w[0]))
59 self.assertLess(GObject.PRIORITY_HIGH, GObject.PRIORITY_DEFAULT)
61 def test_min_max_int(self):
62 self.assertEqual(GObject.G_MAXINT16, 2 ** 15 - 1)
63 self.assertEqual(GObject.G_MININT16, -2 ** 15)
64 self.assertEqual(GObject.G_MAXUINT16, 2 ** 16 - 1)
66 self.assertEqual(GObject.G_MAXINT32, 2 ** 31 - 1)
67 self.assertEqual(GObject.G_MININT32, -2 ** 31)
68 self.assertEqual(GObject.G_MAXUINT32, 2 ** 32 - 1)
70 self.assertEqual(GObject.G_MAXINT64, 2 ** 63 - 1)
71 self.assertEqual(GObject.G_MININT64, -2 ** 63)
72 self.assertEqual(GObject.G_MAXUINT64, 2 ** 64 - 1)
75 class TestReferenceCounting(unittest.TestCase):
76 def test_regular_object(self):
77 obj = GObject.GObject()
78 self.assertEqual(obj.__grefcount__, 1)
80 obj = GObject.new(GObject.GObject)
81 self.assertEqual(obj.__grefcount__, 1)
83 def test_floating(self):
84 obj = testhelper.Floating()
85 self.assertEqual(obj.__grefcount__, 1)
87 obj = GObject.new(testhelper.Floating)
88 self.assertEqual(obj.__grefcount__, 1)
90 def test_owned_by_library(self):
91 # Upon creation, the refcount of the object should be 2:
92 # - someone already has a reference on the new object.
93 # - the python wrapper should hold its own reference.
94 obj = testhelper.OwnedByLibrary()
95 self.assertEqual(obj.__grefcount__, 2)
97 # We ask the library to release its reference, so the only
98 # remaining ref should be our wrapper's. Once the wrapper
99 # will run out of scope, the object will get finalized.
101 self.assertEqual(obj.__grefcount__, 1)
103 def test_owned_by_library_out_of_scope(self):
104 obj = testhelper.OwnedByLibrary()
105 self.assertEqual(obj.__grefcount__, 2)
107 # We are manually taking the object out of scope. This means
108 # that our wrapper has been freed, and its reference dropped. We
109 # cannot check it but the refcount should now be 1 (the ref held
110 # by the library is still there, we didn't call release()
113 # When we get the object back from the lib, the wrapper is
114 # re-created, so our refcount will be 2 once again.
115 obj = testhelper.owned_by_library_get_instance_list()[0]
116 self.assertEqual(obj.__grefcount__, 2)
119 self.assertEqual(obj.__grefcount__, 1)
121 def test_owned_by_library_using_gobject_new(self):
122 # Upon creation, the refcount of the object should be 2:
123 # - someone already has a reference on the new object.
124 # - the python wrapper should hold its own reference.
125 obj = GObject.new(testhelper.OwnedByLibrary)
126 self.assertEqual(obj.__grefcount__, 2)
128 # We ask the library to release its reference, so the only
129 # remaining ref should be our wrapper's. Once the wrapper
130 # will run out of scope, the object will get finalized.
132 self.assertEqual(obj.__grefcount__, 1)
134 def test_owned_by_library_out_of_scope_using_gobject_new(self):
135 obj = GObject.new(testhelper.OwnedByLibrary)
136 self.assertEqual(obj.__grefcount__, 2)
138 # We are manually taking the object out of scope. This means
139 # that our wrapper has been freed, and its reference dropped. We
140 # cannot check it but the refcount should now be 1 (the ref held
141 # by the library is still there, we didn't call release()
144 # When we get the object back from the lib, the wrapper is
145 # re-created, so our refcount will be 2 once again.
146 obj = testhelper.owned_by_library_get_instance_list()[0]
147 self.assertEqual(obj.__grefcount__, 2)
150 self.assertEqual(obj.__grefcount__, 1)
152 def test_floating_and_sunk(self):
153 # Upon creation, the refcount of the object should be 2:
154 # - someone already has a reference on the new object.
155 # - the python wrapper should hold its own reference.
156 obj = testhelper.FloatingAndSunk()
157 self.assertEqual(obj.__grefcount__, 2)
159 # We ask the library to release its reference, so the only
160 # remaining ref should be our wrapper's. Once the wrapper
161 # will run out of scope, the object will get finalized.
163 self.assertEqual(obj.__grefcount__, 1)
165 def test_floating_and_sunk_out_of_scope(self):
166 obj = testhelper.FloatingAndSunk()
167 self.assertEqual(obj.__grefcount__, 2)
169 # We are manually taking the object out of scope. This means
170 # that our wrapper has been freed, and its reference dropped. We
171 # cannot check it but the refcount should now be 1 (the ref held
172 # by the library is still there, we didn't call release()
175 # When we get the object back from the lib, the wrapper is
176 # re-created, so our refcount will be 2 once again.
177 obj = testhelper.floating_and_sunk_get_instance_list()[0]
178 self.assertEqual(obj.__grefcount__, 2)
181 self.assertEqual(obj.__grefcount__, 1)
183 def test_floating_and_sunk_using_gobject_new(self):
184 # Upon creation, the refcount of the object should be 2:
185 # - someone already has a reference on the new object.
186 # - the python wrapper should hold its own reference.
187 obj = GObject.new(testhelper.FloatingAndSunk)
188 self.assertEqual(obj.__grefcount__, 2)
190 # We ask the library to release its reference, so the only
191 # remaining ref should be our wrapper's. Once the wrapper
192 # will run out of scope, the object will get finalized.
194 self.assertEqual(obj.__grefcount__, 1)
196 def test_floating_and_sunk_out_of_scope_using_gobject_new(self):
197 obj = GObject.new(testhelper.FloatingAndSunk)
198 self.assertEqual(obj.__grefcount__, 2)
200 # We are manually taking the object out of scope. This means
201 # that our wrapper has been freed, and its reference dropped. We
202 # cannot check it but the refcount should now be 1 (the ref held
203 # by the library is still there, we didn't call release()
206 # When we get the object back from the lib, the wrapper is
207 # re-created, so our refcount will be 2 once again.
208 obj = testhelper.floating_and_sunk_get_instance_list()[0]
209 self.assertEqual(obj.__grefcount__, 2)
212 self.assertEqual(obj.__grefcount__, 1)
214 def test_uninitialized_object(self):
215 class Obj(GObject.GObject):
217 x = self.__grefcount__
218 super(Obj, self).__init__()
219 assert x >= 0 # quiesce pyflakes
221 # Accessing __grefcount__ before the object is initialized is wrong.
222 # Ensure we get a proper exception instead of a crash.
223 self.assertRaises(TypeError, Obj)
226 class A(GObject.GObject):
228 super(A, self).__init__()
231 class TestPythonReferenceCounting(unittest.TestCase):
232 # Newly created instances should alwayshave two references: one for
233 # the GC, and one for the bound variable in the local scope.
235 def test_new_instance_has_two_refs(self):
236 obj = GObject.GObject()
237 self.assertEqual(sys.getrefcount(obj), 2)
239 def test_new_instance_has_two_refs_using_gobject_new(self):
240 obj = GObject.new(GObject.GObject)
241 self.assertEqual(sys.getrefcount(obj), 2)
243 def test_new_subclass_instance_has_two_refs(self):
245 self.assertEqual(sys.getrefcount(obj), 2)
247 def test_new_subclass_instance_has_two_refs_using_gobject_new(self):
249 self.assertEqual(sys.getrefcount(obj), 2)
252 class TestContextManagers(unittest.TestCase):
253 class ContextTestObject(GObject.GObject):
254 prop = GObject.Property(default=0, type=int)
256 def on_prop_set(self, obj, prop):
257 # Handler which tracks property changed notifications.
258 self.tracking.append(obj.get_property(prop.name))
262 self.obj = self.ContextTestObject()
263 self.handler = self.obj.connect('notify::prop', self.on_prop_set)
265 def test_freeze_notify_context(self):
266 # Verify prop tracking list
267 self.assertEqual(self.tracking, [])
268 self.obj.props.prop = 1
269 self.assertEqual(self.tracking, [1])
270 self.obj.props.prop = 2
271 self.assertEqual(self.tracking, [1, 2])
272 self.assertEqual(self.obj.__grefcount__, 1)
274 pyref_count = sys.getrefcount(self.obj)
276 # Using the context manager the tracking list should not be affected.
277 # The GObject reference count should stay the same and the python
278 # object ref-count should go up.
279 with self.obj.freeze_notify():
280 self.assertEqual(self.obj.__grefcount__, 1)
281 self.assertEqual(sys.getrefcount(self.obj), pyref_count + 1)
282 self.obj.props.prop = 3
283 self.assertEqual(self.obj.props.prop, 3)
284 self.assertEqual(self.tracking, [1, 2])
286 # After the context manager, the prop should have been modified,
287 # the tracking list will be modified, and the python object ref
288 # count goes back down.
290 self.assertEqual(self.obj.props.prop, 3)
291 self.assertEqual(self.tracking, [1, 2, 3])
292 self.assertEqual(self.obj.__grefcount__, 1)
293 self.assertEqual(sys.getrefcount(self.obj), pyref_count)
295 def test_handler_block_context(self):
296 # Verify prop tracking list
297 self.assertEqual(self.tracking, [])
298 self.obj.props.prop = 1
299 self.assertEqual(self.tracking, [1])
300 self.obj.props.prop = 2
301 self.assertEqual(self.tracking, [1, 2])
302 self.assertEqual(self.obj.__grefcount__, 1)
304 pyref_count = sys.getrefcount(self.obj)
306 # Using the context manager the tracking list should not be affected.
307 # The GObject reference count should stay the same and the python
308 # object ref-count should go up.
309 with self.obj.handler_block(self.handler):
310 self.assertEqual(self.obj.__grefcount__, 1)
311 self.assertEqual(sys.getrefcount(self.obj), pyref_count + 1)
312 self.obj.props.prop = 3
313 self.assertEqual(self.obj.props.prop, 3)
314 self.assertEqual(self.tracking, [1, 2])
316 # After the context manager, the prop should have been modified
317 # the tracking list should have stayed the same and the GObject ref
318 # count goes back down.
320 self.assertEqual(self.obj.props.prop, 3)
321 self.assertEqual(self.tracking, [1, 2])
322 self.assertEqual(self.obj.__grefcount__, 1)
323 self.assertEqual(sys.getrefcount(self.obj), pyref_count)
325 def test_freeze_notify_context_nested(self):
326 self.assertEqual(self.tracking, [])
327 with self.obj.freeze_notify():
328 self.obj.props.prop = 1
329 self.assertEqual(self.tracking, [])
331 with self.obj.freeze_notify():
332 self.obj.props.prop = 2
333 self.assertEqual(self.tracking, [])
335 with self.obj.freeze_notify():
336 self.obj.props.prop = 3
337 self.assertEqual(self.tracking, [])
338 self.assertEqual(self.tracking, [])
339 self.assertEqual(self.tracking, [])
341 # Finally after last context, the notifications should have collapsed
342 # and the last one sent.
343 self.assertEqual(self.tracking, [3])
345 def test_handler_block_context_nested(self):
346 self.assertEqual(self.tracking, [])
347 with self.obj.handler_block(self.handler):
348 self.obj.props.prop = 1
349 self.assertEqual(self.tracking, [])
351 with self.obj.handler_block(self.handler):
352 self.obj.props.prop = 2
353 self.assertEqual(self.tracking, [])
355 with self.obj.handler_block(self.handler):
356 self.obj.props.prop = 3
357 self.assertEqual(self.tracking, [])
358 self.assertEqual(self.tracking, [])
359 self.assertEqual(self.tracking, [])
361 # Finally after last context, the notifications should have collapsed
362 # and the last one sent.
363 self.assertEqual(self.obj.props.prop, 3)
364 self.assertEqual(self.tracking, [])
366 def test_freeze_notify_normal_usage_ref_counts(self):
367 # Ensure ref counts without using methods as context managers
368 # maintain the same count.
369 self.assertEqual(self.obj.__grefcount__, 1)
370 self.obj.freeze_notify()
371 self.assertEqual(self.obj.__grefcount__, 1)
372 self.obj.thaw_notify()
373 self.assertEqual(self.obj.__grefcount__, 1)
375 def test_handler_block_normal_usage_ref_counts(self):
376 self.assertEqual(self.obj.__grefcount__, 1)
377 self.obj.handler_block(self.handler)
378 self.assertEqual(self.obj.__grefcount__, 1)
379 self.obj.handler_unblock(self.handler)
380 self.assertEqual(self.obj.__grefcount__, 1)
382 def test_freeze_notify_context_error(self):
383 # Test an exception occurring within a freeze context exits the context
385 with self.obj.freeze_notify():
386 self.obj.props.prop = 1
387 self.assertEqual(self.tracking, [])
388 raise ValueError('Simulation')
392 # Verify the property set within the context called notify.
393 self.assertEqual(self.obj.props.prop, 1)
394 self.assertEqual(self.tracking, [1])
396 # Verify we are still not in a frozen context.
397 self.obj.props.prop = 2
398 self.assertEqual(self.tracking, [1, 2])
400 def test_handler_block_context_error(self):
401 # Test an exception occurring within a handler block exits the context
403 with self.obj.handler_block(self.handler):
404 self.obj.props.prop = 1
405 self.assertEqual(self.tracking, [])
406 raise ValueError('Simulation')
410 # Verify the property set within the context didn't call notify.
411 self.assertEqual(self.obj.props.prop, 1)
412 self.assertEqual(self.tracking, [])
414 # Verify we are still not in a handler block context.
415 self.obj.props.prop = 2
416 self.assertEqual(self.tracking, [2])
419 class TestPropertyBindings(unittest.TestCase):
420 class TestObject(GObject.GObject):
421 int_prop = GObject.Property(default=0, type=int)
424 self.source = self.TestObject()
425 self.target = self.TestObject()
427 def test_default_binding(self):
428 binding = self.source.bind_property('int_prop', self.target, 'int_prop',
429 GObject.BindingFlags.DEFAULT)
430 binding = binding # PyFlakes
432 # Test setting value on source gets pushed to target
433 self.source.int_prop = 1
434 self.assertEqual(self.source.int_prop, 1)
435 self.assertEqual(self.target.int_prop, 1)
437 # Test setting value on target does not change source
438 self.target.props.int_prop = 2
439 self.assertEqual(self.source.int_prop, 1)
440 self.assertEqual(self.target.int_prop, 2)
442 def test_bidirectional_binding(self):
443 binding = self.source.bind_property('int_prop', self.target, 'int_prop',
444 GObject.BindingFlags.BIDIRECTIONAL)
445 binding = binding # PyFlakes
447 # Test setting value on source gets pushed to target
448 self.source.int_prop = 1
449 self.assertEqual(self.source.int_prop, 1)
450 self.assertEqual(self.target.int_prop, 1)
452 # Test setting value on target also changes source
453 self.target.props.int_prop = 2
454 self.assertEqual(self.source.int_prop, 2)
455 self.assertEqual(self.target.int_prop, 2)
457 def test_transform_to_only(self):
458 def transform_to(binding, value, user_data=None):
459 self.assertEqual(user_data, 'test-data')
462 binding = self.source.bind_property('int_prop', self.target, 'int_prop',
463 GObject.BindingFlags.DEFAULT,
464 transform_to, None, 'test-data')
465 binding = binding # PyFlakes
467 self.source.int_prop = 1
468 self.assertEqual(self.source.int_prop, 1)
469 self.assertEqual(self.target.int_prop, 2)
471 self.target.props.int_prop = 1
472 self.assertEqual(self.source.int_prop, 1)
473 self.assertEqual(self.target.int_prop, 1)
475 def test_transform_from_only(self):
476 def transform_from(binding, value, user_data=None):
477 self.assertEqual(user_data, None)
480 binding = self.source.bind_property('int_prop', self.target, 'int_prop',
481 GObject.BindingFlags.BIDIRECTIONAL,
482 None, transform_from)
483 binding = binding # PyFlakes
485 self.source.int_prop = 1
486 self.assertEqual(self.source.int_prop, 1)
487 self.assertEqual(self.target.int_prop, 1)
489 self.target.props.int_prop = 1
490 self.assertEqual(self.source.int_prop, 2)
491 self.assertEqual(self.target.int_prop, 1)
493 def test_transform_bidirectional(self):
494 def transform_to(binding, value, user_data=None):
495 self.assertEqual(user_data, 'test-data')
498 def transform_from(binding, value, user_data=None):
499 self.assertEqual(user_data, 'test-data')
502 # bidirectional bindings
503 binding = self.source.bind_property('int_prop', self.target, 'int_prop',
504 GObject.BindingFlags.BIDIRECTIONAL,
505 transform_to, transform_from, 'test-data')
506 binding = binding # PyFlakes
508 self.source.int_prop = 1
509 self.assertEqual(self.source.int_prop, 1)
510 self.assertEqual(self.target.int_prop, 2)
512 self.target.props.int_prop = 4
513 self.assertEqual(self.source.int_prop, 2)
514 self.assertEqual(self.target.int_prop, 4)
516 def test_explicit_unbind_clears_connection(self):
517 self.assertEqual(self.source.int_prop, 0)
518 self.assertEqual(self.target.int_prop, 0)
520 # Test deleting binding reference removes binding.
521 binding = self.source.bind_property('int_prop', self.target, 'int_prop')
522 self.source.int_prop = 1
523 self.assertEqual(self.source.int_prop, 1)
524 self.assertEqual(self.target.int_prop, 1)
527 self.assertEqual(binding(), None)
529 self.source.int_prop = 10
530 self.assertEqual(self.source.int_prop, 10)
531 self.assertEqual(self.target.int_prop, 1)
533 # An already unbound BindingWeakRef will raise if unbind is attempted a second time.
534 self.assertRaises(ValueError, binding.unbind)
536 def test_reference_counts(self):
537 self.assertEqual(self.source.__grefcount__, 1)
538 self.assertEqual(self.target.__grefcount__, 1)
540 # Binding ref count will be 2 do to the initial ref implicitly held by
541 # the act of binding and the ref incurred by using __call__ to generate
542 # a wrapper from the weak binding ref within python.
543 binding = self.source.bind_property('int_prop', self.target, 'int_prop')
544 self.assertEqual(binding().__grefcount__, 2)
546 # Creating a binding does not inc refs on source and target (they are weak
547 # on the binding object itself)
548 self.assertEqual(self.source.__grefcount__, 1)
549 self.assertEqual(self.target.__grefcount__, 1)
551 # Use GObject.get_property because the "props" accessor leaks.
552 # Note property names are canonicalized.
553 self.assertEqual(binding().get_property('source'), self.source)
554 self.assertEqual(binding().get_property('source_property'), 'int-prop')
555 self.assertEqual(binding().get_property('target'), self.target)
556 self.assertEqual(binding().get_property('target_property'), 'int-prop')
557 self.assertEqual(binding().get_property('flags'), GObject.BindingFlags.DEFAULT)
559 # Delete reference to source or target and the binding should listen.
560 ref = self.source.weak_ref()
563 self.assertEqual(ref(), None)
564 self.assertEqual(binding(), None)
566 if __name__ == '__main__':