Bump to 3.46.0
[platform/upstream/pygobject2.git] / tests / test_gobject.py
1 # -*- Mode: Python -*-
2
3 import sys
4 import gc
5 import unittest
6 import warnings
7 import weakref
8 import platform
9
10 import pytest
11
12 from gi.repository import GObject, GLib, Gio
13 from gi import PyGIDeprecationWarning
14 from gi.module import get_introspection_module
15 from gi import _gi
16
17 import testhelper
18 from .helper import capture_glib_deprecation_warnings
19
20
21 @pytest.mark.skipif(platform.python_implementation() == "PyPy", reason="crashes")
22 def test_gobject_weak_ref():
23
24     called = []
25
26     def callback(*args):
27         called.extend(args)
28
29     # object gets finalized
30     obj = GObject.Object()
31     obj.weak_ref(callback, 1)
32     del obj
33     gc.collect()
34     gc.collect()
35     assert called == [1]
36     del called[:]
37
38     # wrapper gets finalized first
39     obj = GObject.Object()
40     pyref = weakref.ref(obj, lambda x: callback(-2))
41     value = GObject.Value(GObject.Object, obj)
42     ref = obj.weak_ref(callback, 2)
43     del obj
44     gc.collect()
45     assert called == [-2]
46     del pyref
47     value.unset()
48     gc.collect()
49     assert called == [-2, 2]
50     del called[:]
51
52     # weakref gets unregistered first
53     obj = GObject.Object()
54     ref = obj.weak_ref(callback, 3)
55     ref.unref()
56     del obj
57     gc.collect()
58     assert not called
59
60     # weakref gets GCed
61     obj = GObject.Object()
62     obj.weak_ref(callback, 4)
63     gc.collect()
64     del obj
65     assert called == [4]
66
67
68 class TestGObjectAPI(unittest.TestCase):
69
70     def test_run_dispose(self):
71         class TestObject(GObject.GObject):
72             int_prop = GObject.Property(default=0, type=int)
73
74         obj = TestObject()
75         called = []
76
77         def on_notify(*args):
78             called.append(args)
79
80         obj.connect('notify::int-prop', on_notify)
81         obj.notify("int-prop")
82         obj.notify("int-prop")
83         # after this everything should be disconnected
84         obj.run_dispose()
85         obj.notify("int-prop")
86         obj.notify("int-prop")
87         assert len(called) == 2
88
89     def test_call_method_uninitialized_instance(self):
90         obj = GObject.Object.__new__(GObject.Object)
91         with self.assertRaisesRegex(RuntimeError, '.*is not initialized'):
92             obj.notify("foo")
93
94     def test_gobject_inheritance(self):
95         # GObject.Object is a class hierarchy as follows:
96         # overrides.Object -> introspection.Object -> static.GObject
97         GIObjectModule = get_introspection_module('GObject')
98         self.assertTrue(issubclass(GObject.Object, GIObjectModule.Object))
99         self.assertTrue(issubclass(GIObjectModule.Object, _gi.GObject))
100
101         self.assertEqual(_gi.GObject.__gtype__, GObject.TYPE_OBJECT)
102         self.assertEqual(GIObjectModule.Object.__gtype__, GObject.TYPE_OBJECT)
103         self.assertEqual(GObject.Object.__gtype__, GObject.TYPE_OBJECT)
104
105         # The pytype wrapper should hold the outer most Object class from overrides.
106         self.assertEqual(GObject.TYPE_OBJECT.pytype, GObject.Object)
107
108     def test_gobject_unsupported_overrides(self):
109         obj = GObject.Object()
110
111         with self.assertRaisesRegex(RuntimeError, 'Data access methods are unsupported.*'):
112             obj.get_data()
113
114         with self.assertRaisesRegex(RuntimeError, 'This method is currently unsupported.*'):
115             obj.force_floating()
116
117     def test_compat_api(self):
118         with warnings.catch_warnings(record=True) as w:
119             warnings.simplefilter('always')
120             # GObject formerly exposed a lot of GLib's functions
121             self.assertEqual(GObject.markup_escape_text('foo'), 'foo')
122
123             ml = GObject.MainLoop()
124             self.assertFalse(ml.is_running())
125
126             context = GObject.main_context_default()
127             self.assertTrue(context.pending() in [False, True])
128
129             context = GObject.MainContext()
130             self.assertFalse(context.pending())
131
132             self.assertTrue(issubclass(w[0].category, PyGIDeprecationWarning))
133             self.assertTrue('GLib.markup_escape_text' in str(w[0]), str(w[0]))
134
135             self.assertLess(GObject.PRIORITY_HIGH, GObject.PRIORITY_DEFAULT)
136
137     def test_min_max_int(self):
138         with warnings.catch_warnings():
139             warnings.simplefilter('ignore', PyGIDeprecationWarning)
140
141             self.assertEqual(GObject.G_MAXINT16, 2 ** 15 - 1)
142             self.assertEqual(GObject.G_MININT16, -2 ** 15)
143             self.assertEqual(GObject.G_MAXUINT16, 2 ** 16 - 1)
144
145             self.assertEqual(GObject.G_MAXINT32, 2 ** 31 - 1)
146             self.assertEqual(GObject.G_MININT32, -2 ** 31)
147             self.assertEqual(GObject.G_MAXUINT32, 2 ** 32 - 1)
148
149             self.assertEqual(GObject.G_MAXINT64, 2 ** 63 - 1)
150             self.assertEqual(GObject.G_MININT64, -2 ** 63)
151             self.assertEqual(GObject.G_MAXUINT64, 2 ** 64 - 1)
152
153
154 class TestReferenceCounting(unittest.TestCase):
155     def test_regular_object(self):
156         obj = GObject.GObject()
157         self.assertEqual(obj.__grefcount__, 1)
158
159         obj = GObject.new(GObject.GObject)
160         self.assertEqual(obj.__grefcount__, 1)
161
162     def test_floating(self):
163         obj = testhelper.Floating()
164         self.assertEqual(obj.__grefcount__, 1)
165
166         obj = GObject.new(testhelper.Floating)
167         self.assertEqual(obj.__grefcount__, 1)
168
169     def test_owned_by_library(self):
170         # Upon creation, the refcount of the object should be 2:
171         # - someone already has a reference on the new object.
172         # - the python wrapper should hold its own reference.
173         obj = testhelper.OwnedByLibrary()
174         self.assertEqual(obj.__grefcount__, 2)
175
176         # We ask the library to release its reference, so the only
177         # remaining ref should be our wrapper's. Once the wrapper
178         # will run out of scope, the object will get finalized.
179         obj.release()
180         self.assertEqual(obj.__grefcount__, 1)
181
182     def test_owned_by_library_out_of_scope(self):
183         obj = testhelper.OwnedByLibrary()
184         self.assertEqual(obj.__grefcount__, 2)
185
186         # We are manually taking the object out of scope. This means
187         # that our wrapper has been freed, and its reference dropped. We
188         # cannot check it but the refcount should now be 1 (the ref held
189         # by the library is still there, we didn't call release()
190         obj = None
191
192         # When we get the object back from the lib, the wrapper is
193         # re-created, so our refcount will be 2 once again.
194         obj = testhelper.owned_by_library_get_instance_list()[0]
195         self.assertEqual(obj.__grefcount__, 2)
196
197         obj.release()
198         self.assertEqual(obj.__grefcount__, 1)
199
200     def test_owned_by_library_using_gobject_new(self):
201         # Upon creation, the refcount of the object should be 2:
202         # - someone already has a reference on the new object.
203         # - the python wrapper should hold its own reference.
204         obj = GObject.new(testhelper.OwnedByLibrary)
205         self.assertEqual(obj.__grefcount__, 2)
206
207         # We ask the library to release its reference, so the only
208         # remaining ref should be our wrapper's. Once the wrapper
209         # will run out of scope, the object will get finalized.
210         obj.release()
211         self.assertEqual(obj.__grefcount__, 1)
212
213     def test_owned_by_library_out_of_scope_using_gobject_new(self):
214         obj = GObject.new(testhelper.OwnedByLibrary)
215         self.assertEqual(obj.__grefcount__, 2)
216
217         # We are manually taking the object out of scope. This means
218         # that our wrapper has been freed, and its reference dropped. We
219         # cannot check it but the refcount should now be 1 (the ref held
220         # by the library is still there, we didn't call release()
221         obj = None
222
223         # When we get the object back from the lib, the wrapper is
224         # re-created, so our refcount will be 2 once again.
225         obj = testhelper.owned_by_library_get_instance_list()[0]
226         self.assertEqual(obj.__grefcount__, 2)
227
228         obj.release()
229         self.assertEqual(obj.__grefcount__, 1)
230
231     def test_floating_and_sunk(self):
232         # Upon creation, the refcount of the object should be 2:
233         # - someone already has a reference on the new object.
234         # - the python wrapper should hold its own reference.
235         obj = testhelper.FloatingAndSunk()
236         self.assertEqual(obj.__grefcount__, 2)
237
238         # We ask the library to release its reference, so the only
239         # remaining ref should be our wrapper's. Once the wrapper
240         # will run out of scope, the object will get finalized.
241         obj.release()
242         self.assertEqual(obj.__grefcount__, 1)
243
244     def test_floating_and_sunk_out_of_scope(self):
245         obj = testhelper.FloatingAndSunk()
246         self.assertEqual(obj.__grefcount__, 2)
247
248         # We are manually taking the object out of scope. This means
249         # that our wrapper has been freed, and its reference dropped. We
250         # cannot check it but the refcount should now be 1 (the ref held
251         # by the library is still there, we didn't call release()
252         obj = None
253
254         # When we get the object back from the lib, the wrapper is
255         # re-created, so our refcount will be 2 once again.
256         obj = testhelper.floating_and_sunk_get_instance_list()[0]
257         self.assertEqual(obj.__grefcount__, 2)
258
259         obj.release()
260         self.assertEqual(obj.__grefcount__, 1)
261
262     def test_floating_and_sunk_using_gobject_new(self):
263         # Upon creation, the refcount of the object should be 2:
264         # - someone already has a reference on the new object.
265         # - the python wrapper should hold its own reference.
266         obj = GObject.new(testhelper.FloatingAndSunk)
267         self.assertEqual(obj.__grefcount__, 2)
268
269         # We ask the library to release its reference, so the only
270         # remaining ref should be our wrapper's. Once the wrapper
271         # will run out of scope, the object will get finalized.
272         obj.release()
273         self.assertEqual(obj.__grefcount__, 1)
274
275     def test_floating_and_sunk_out_of_scope_using_gobject_new(self):
276         obj = GObject.new(testhelper.FloatingAndSunk)
277         self.assertEqual(obj.__grefcount__, 2)
278
279         # We are manually taking the object out of scope. This means
280         # that our wrapper has been freed, and its reference dropped. We
281         # cannot check it but the refcount should now be 1 (the ref held
282         # by the library is still there, we didn't call release()
283         obj = None
284
285         # When we get the object back from the lib, the wrapper is
286         # re-created, so our refcount will be 2 once again.
287         obj = testhelper.floating_and_sunk_get_instance_list()[0]
288         self.assertEqual(obj.__grefcount__, 2)
289
290         obj.release()
291         self.assertEqual(obj.__grefcount__, 1)
292
293     def test_uninitialized_object(self):
294         class Obj(GObject.GObject):
295             def __init__(self):
296                 x = self.__grefcount__
297                 super(Obj, self).__init__()
298                 assert x >= 0  # quiesce pyflakes
299
300         # Accessing __grefcount__ before the object is initialized is wrong.
301         # Ensure we get a proper exception instead of a crash.
302         self.assertRaises(TypeError, Obj)
303
304
305 class A(GObject.GObject):
306     def __init__(self):
307         super(A, self).__init__()
308
309
310 class TestPythonReferenceCounting(unittest.TestCase):
311     # Newly created instances should alwayshave two references: one for
312     # the GC, and one for the bound variable in the local scope.
313
314     def test_new_instance_has_two_refs(self):
315         obj = GObject.GObject()
316         if hasattr(sys, "getrefcount"):
317             self.assertEqual(sys.getrefcount(obj), 2)
318
319     def test_new_instance_has_two_refs_using_gobject_new(self):
320         obj = GObject.new(GObject.GObject)
321         if hasattr(sys, "getrefcount"):
322             self.assertEqual(sys.getrefcount(obj), 2)
323
324     def test_new_subclass_instance_has_two_refs(self):
325         obj = A()
326         if hasattr(sys, "getrefcount"):
327             self.assertEqual(sys.getrefcount(obj), 2)
328
329     def test_new_subclass_instance_has_two_refs_using_gobject_new(self):
330         obj = GObject.new(A)
331         if hasattr(sys, "getrefcount"):
332             self.assertEqual(sys.getrefcount(obj), 2)
333
334
335 class TestContextManagers(unittest.TestCase):
336     class ContextTestObject(GObject.GObject):
337         prop = GObject.Property(default=0, type=int)
338
339     def on_prop_set(self, obj, prop):
340         # Handler which tracks property changed notifications.
341         self.tracking.append(obj.get_property(prop.name))
342
343     def setUp(self):
344         self.tracking = []
345         self.obj = self.ContextTestObject()
346         self.handler = self.obj.connect('notify::prop', self.on_prop_set)
347
348     def test_freeze_notify_context(self):
349         # Verify prop tracking list
350         self.assertEqual(self.tracking, [])
351         self.obj.props.prop = 1
352         self.assertEqual(self.tracking, [1])
353         self.obj.props.prop = 2
354         self.assertEqual(self.tracking, [1, 2])
355         self.assertEqual(self.obj.__grefcount__, 1)
356
357         if hasattr(sys, "getrefcount"):
358             pyref_count = sys.getrefcount(self.obj)
359
360         # Using the context manager the tracking list should not be affected.
361         # The GObject reference count should stay the same and the python
362         # object ref-count should go up.
363         with self.obj.freeze_notify():
364             self.assertEqual(self.obj.__grefcount__, 1)
365             if hasattr(sys, "getrefcount"):
366                 self.assertEqual(sys.getrefcount(self.obj), pyref_count + 1)
367             self.obj.props.prop = 3
368             self.assertEqual(self.obj.props.prop, 3)
369             self.assertEqual(self.tracking, [1, 2])
370
371         # After the context manager, the prop should have been modified,
372         # the tracking list will be modified, and the python object ref
373         # count goes back down.
374         gc.collect()
375         self.assertEqual(self.obj.props.prop, 3)
376         self.assertEqual(self.tracking, [1, 2, 3])
377         self.assertEqual(self.obj.__grefcount__, 1)
378         if hasattr(sys, "getrefcount"):
379             self.assertEqual(sys.getrefcount(self.obj), pyref_count)
380
381     def test_handler_block_context(self):
382         # Verify prop tracking list
383         self.assertEqual(self.tracking, [])
384         self.obj.props.prop = 1
385         self.assertEqual(self.tracking, [1])
386         self.obj.props.prop = 2
387         self.assertEqual(self.tracking, [1, 2])
388         self.assertEqual(self.obj.__grefcount__, 1)
389
390         if hasattr(sys, "getrefcount"):
391             pyref_count = sys.getrefcount(self.obj)
392
393         # Using the context manager the tracking list should not be affected.
394         # The GObject reference count should stay the same and the python
395         # object ref-count should go up.
396         with self.obj.handler_block(self.handler):
397             self.assertEqual(self.obj.__grefcount__, 1)
398             if hasattr(sys, "getrefcount"):
399                 self.assertEqual(sys.getrefcount(self.obj), pyref_count + 1)
400             self.obj.props.prop = 3
401             self.assertEqual(self.obj.props.prop, 3)
402             self.assertEqual(self.tracking, [1, 2])
403
404         # After the context manager, the prop should have been modified
405         # the tracking list should have stayed the same and the GObject ref
406         # count goes back down.
407         gc.collect()
408         self.assertEqual(self.obj.props.prop, 3)
409         self.assertEqual(self.tracking, [1, 2])
410         self.assertEqual(self.obj.__grefcount__, 1)
411         if hasattr(sys, "getrefcount"):
412             self.assertEqual(sys.getrefcount(self.obj), pyref_count)
413
414     def test_freeze_notify_context_nested(self):
415         self.assertEqual(self.tracking, [])
416         with self.obj.freeze_notify():
417             self.obj.props.prop = 1
418             self.assertEqual(self.tracking, [])
419
420             with self.obj.freeze_notify():
421                 self.obj.props.prop = 2
422                 self.assertEqual(self.tracking, [])
423
424                 with self.obj.freeze_notify():
425                     self.obj.props.prop = 3
426                     self.assertEqual(self.tracking, [])
427                 self.assertEqual(self.tracking, [])
428             self.assertEqual(self.tracking, [])
429
430         # Finally after last context, the notifications should have collapsed
431         # and the last one sent.
432         self.assertEqual(self.tracking, [3])
433
434     def test_handler_block_context_nested(self):
435         self.assertEqual(self.tracking, [])
436         with self.obj.handler_block(self.handler):
437             self.obj.props.prop = 1
438             self.assertEqual(self.tracking, [])
439
440             with self.obj.handler_block(self.handler):
441                 self.obj.props.prop = 2
442                 self.assertEqual(self.tracking, [])
443
444                 with self.obj.handler_block(self.handler):
445                     self.obj.props.prop = 3
446                     self.assertEqual(self.tracking, [])
447                 self.assertEqual(self.tracking, [])
448             self.assertEqual(self.tracking, [])
449
450         # Finally after last context, the notifications should have collapsed
451         # and the last one sent.
452         self.assertEqual(self.obj.props.prop, 3)
453         self.assertEqual(self.tracking, [])
454
455     def test_freeze_notify_normal_usage_ref_counts(self):
456         # Ensure ref counts without using methods as context managers
457         # maintain the same count.
458         self.assertEqual(self.obj.__grefcount__, 1)
459         self.obj.freeze_notify()
460         self.assertEqual(self.obj.__grefcount__, 1)
461         self.obj.thaw_notify()
462         self.assertEqual(self.obj.__grefcount__, 1)
463
464     def test_handler_block_normal_usage_ref_counts(self):
465         self.assertEqual(self.obj.__grefcount__, 1)
466         self.obj.handler_block(self.handler)
467         self.assertEqual(self.obj.__grefcount__, 1)
468         self.obj.handler_unblock(self.handler)
469         self.assertEqual(self.obj.__grefcount__, 1)
470
471     def test_freeze_notify_context_error(self):
472         # Test an exception occurring within a freeze context exits the context
473         try:
474             with self.obj.freeze_notify():
475                 self.obj.props.prop = 1
476                 self.assertEqual(self.tracking, [])
477                 raise ValueError('Simulation')
478         except ValueError:
479             pass
480
481         # Verify the property set within the context called notify.
482         self.assertEqual(self.obj.props.prop, 1)
483         self.assertEqual(self.tracking, [1])
484
485         # Verify we are still not in a frozen context.
486         self.obj.props.prop = 2
487         self.assertEqual(self.tracking, [1, 2])
488
489     def test_handler_block_context_error(self):
490         # Test an exception occurring within a handler block exits the context
491         try:
492             with self.obj.handler_block(self.handler):
493                 self.obj.props.prop = 1
494                 self.assertEqual(self.tracking, [])
495                 raise ValueError('Simulation')
496         except ValueError:
497             pass
498
499         # Verify the property set within the context didn't call notify.
500         self.assertEqual(self.obj.props.prop, 1)
501         self.assertEqual(self.tracking, [])
502
503         # Verify we are still not in a handler block context.
504         self.obj.props.prop = 2
505         self.assertEqual(self.tracking, [2])
506
507
508 @unittest.skipUnless(hasattr(GObject.Binding, 'unbind'),
509                      'Requires newer GLib which has g_binding_unbind')
510 class TestPropertyBindings(unittest.TestCase):
511     class TestObject(GObject.GObject):
512         int_prop = GObject.Property(default=0, type=int)
513
514     def setUp(self):
515         self.source = self.TestObject()
516         self.target = self.TestObject()
517
518     def test_default_binding(self):
519         binding = self.source.bind_property('int_prop', self.target, 'int_prop',
520                                             GObject.BindingFlags.DEFAULT)
521         binding = binding  # PyFlakes
522
523         # Test setting value on source gets pushed to target
524         self.source.int_prop = 1
525         self.assertEqual(self.source.int_prop, 1)
526         self.assertEqual(self.target.int_prop, 1)
527
528         # Test setting value on target does not change source
529         self.target.props.int_prop = 2
530         self.assertEqual(self.source.int_prop, 1)
531         self.assertEqual(self.target.int_prop, 2)
532
533     def test_call_binding(self):
534         binding = self.source.bind_property('int_prop', self.target, 'int_prop',
535                                             GObject.BindingFlags.DEFAULT)
536         with capture_glib_deprecation_warnings() as warn:
537             result = binding()
538         assert len(warn)
539         assert result is binding
540
541     def test_bidirectional_binding(self):
542         binding = self.source.bind_property('int_prop', self.target, 'int_prop',
543                                             GObject.BindingFlags.BIDIRECTIONAL)
544         binding = binding  # PyFlakes
545
546         # Test setting value on source gets pushed to target
547         self.source.int_prop = 1
548         self.assertEqual(self.source.int_prop, 1)
549         self.assertEqual(self.target.int_prop, 1)
550
551         # Test setting value on target also changes source
552         self.target.props.int_prop = 2
553         self.assertEqual(self.source.int_prop, 2)
554         self.assertEqual(self.target.int_prop, 2)
555
556     def test_transform_to_only(self):
557         def transform_to(binding, value, user_data=None):
558             self.assertEqual(user_data, 'test-data')
559             return value * 2
560
561         binding = self.source.bind_property('int_prop', self.target, 'int_prop',
562                                             GObject.BindingFlags.DEFAULT,
563                                             transform_to, None, 'test-data')
564         binding = binding  # PyFlakes
565
566         self.source.int_prop = 1
567         self.assertEqual(self.source.int_prop, 1)
568         self.assertEqual(self.target.int_prop, 2)
569
570         self.target.props.int_prop = 1
571         self.assertEqual(self.source.int_prop, 1)
572         self.assertEqual(self.target.int_prop, 1)
573
574     def test_transform_from_only(self):
575         def transform_from(binding, value, user_data=None):
576             self.assertEqual(user_data, None)
577             return value * 2
578
579         binding = self.source.bind_property('int_prop', self.target, 'int_prop',
580                                             GObject.BindingFlags.BIDIRECTIONAL,
581                                             None, transform_from)
582         binding = binding  # PyFlakes
583
584         self.source.int_prop = 1
585         self.assertEqual(self.source.int_prop, 1)
586         self.assertEqual(self.target.int_prop, 1)
587
588         self.target.props.int_prop = 1
589         self.assertEqual(self.source.int_prop, 2)
590         self.assertEqual(self.target.int_prop, 1)
591
592     def test_transform_bidirectional(self):
593         test_data = object()
594
595         def transform_to(binding, value, user_data=None):
596             self.assertEqual(user_data, test_data)
597             return value * 2
598
599         def transform_from(binding, value, user_data=None):
600             self.assertEqual(user_data, test_data)
601             return value // 2
602
603         if hasattr(sys, "getrefcount"):
604             test_data_ref_count = sys.getrefcount(test_data)
605             transform_to_ref_count = sys.getrefcount(transform_to)
606             transform_from_ref_count = sys.getrefcount(transform_from)
607
608         # bidirectional bindings
609         binding = self.source.bind_property('int_prop', self.target, 'int_prop',
610                                             GObject.BindingFlags.BIDIRECTIONAL,
611                                             transform_to, transform_from, test_data)
612         binding = binding  # PyFlakes
613         if hasattr(sys, "getrefcount"):
614             binding_ref_count = sys.getrefcount(binding)
615             binding_gref_count = binding.__grefcount__
616
617         self.source.int_prop = 1
618         self.assertEqual(self.source.int_prop, 1)
619         self.assertEqual(self.target.int_prop, 2)
620
621         self.target.props.int_prop = 4
622         self.assertEqual(self.source.int_prop, 2)
623         self.assertEqual(self.target.int_prop, 4)
624
625         if hasattr(sys, "getrefcount"):
626             self.assertEqual(sys.getrefcount(binding), binding_ref_count)
627             self.assertEqual(binding.__grefcount__, binding_gref_count)
628
629         # test_data ref count increases by 2, once for each callback.
630         if hasattr(sys, "getrefcount"):
631             self.assertEqual(sys.getrefcount(test_data), test_data_ref_count + 2)
632             self.assertEqual(sys.getrefcount(transform_to), transform_to_ref_count + 1)
633             self.assertEqual(sys.getrefcount(transform_from), transform_from_ref_count + 1)
634
635         # Unbind should clear out the binding and its transforms
636         binding.unbind()
637
638         # Setting source or target should not change the other.
639         self.target.int_prop = 3
640         self.source.int_prop = 5
641         self.assertEqual(self.target.int_prop, 3)
642         self.assertEqual(self.source.int_prop, 5)
643
644         if hasattr(sys, "getrefcount"):
645             self.assertEqual(sys.getrefcount(test_data), test_data_ref_count)
646             self.assertEqual(sys.getrefcount(transform_to), transform_to_ref_count)
647             self.assertEqual(sys.getrefcount(transform_from), transform_from_ref_count)
648
649     def test_explicit_unbind_clears_connection(self):
650         self.assertEqual(self.source.int_prop, 0)
651         self.assertEqual(self.target.int_prop, 0)
652
653         # Test deleting binding reference removes binding.
654         binding = self.source.bind_property('int_prop', self.target, 'int_prop')
655         self.source.int_prop = 1
656         self.assertEqual(self.source.int_prop, 1)
657         self.assertEqual(self.target.int_prop, 1)
658
659         # unbind should clear out the bindings self reference
660         binding.unbind()
661         self.assertEqual(binding.__grefcount__, 1)
662
663         self.source.int_prop = 10
664         self.assertEqual(self.source.int_prop, 10)
665         self.assertEqual(self.target.int_prop, 1)
666
667         glib_version = (GLib.MAJOR_VERSION, GLib.MINOR_VERSION, GLib.MICRO_VERSION)
668
669         # calling unbind() on an already unbound binding
670         if glib_version >= (2, 57, 3):
671             # Fixed in newer glib:
672             # https://gitlab.gnome.org/GNOME/glib/merge_requests/244
673             for i in range(10):
674                 binding.unbind()
675         else:
676             self.assertRaises(ValueError, binding.unbind)
677
678     def test_reference_counts(self):
679         self.assertEqual(self.source.__grefcount__, 1)
680         self.assertEqual(self.target.__grefcount__, 1)
681
682         # Binding ref count will be 2 do to the initial ref implicitly held by
683         # the act of binding and the ref incurred by using __call__ to generate
684         # a wrapper from the weak binding ref within python.
685         binding = self.source.bind_property('int_prop', self.target, 'int_prop')
686         self.assertEqual(binding.__grefcount__, 2)
687
688         # Creating a binding does not inc refs on source and target (they are weak
689         # on the binding object itself)
690         self.assertEqual(self.source.__grefcount__, 1)
691         self.assertEqual(self.target.__grefcount__, 1)
692
693         # Use GObject.get_property because the "props" accessor leaks.
694         # Note property names are canonicalized.
695         self.assertEqual(binding.get_property('source'), self.source)
696         self.assertEqual(binding.get_property('source_property'), 'int-prop')
697         self.assertEqual(binding.get_property('target'), self.target)
698         self.assertEqual(binding.get_property('target_property'), 'int-prop')
699         self.assertEqual(binding.get_property('flags'), GObject.BindingFlags.DEFAULT)
700
701         # Delete reference to source or target and the binding will remove its own
702         # "self reference".
703         ref = self.source.weak_ref()
704         del self.source
705         gc.collect()
706         self.assertEqual(ref(), None)
707         self.assertEqual(binding.__grefcount__, 1)
708
709         # Finally clear out the last ref held by the python wrapper
710         ref = binding.weak_ref()
711         del binding
712         gc.collect()
713         self.assertEqual(ref(), None)
714
715
716 class TestGValue(unittest.TestCase):
717     def test_type_constant(self):
718         self.assertEqual(GObject.TYPE_VALUE, GObject.Value.__gtype__)
719         self.assertEqual(GObject.type_name(GObject.TYPE_VALUE), 'GValue')
720
721     def test_no_type(self):
722         value = GObject.Value()
723         self.assertEqual(value.g_type, GObject.TYPE_INVALID)
724         self.assertRaises(TypeError, value.set_value, 23)
725         self.assertEqual(value.get_value(), None)
726
727     def test_int(self):
728         value = GObject.Value(GObject.TYPE_UINT)
729         self.assertEqual(value.g_type, GObject.TYPE_UINT)
730         value.set_value(23)
731         self.assertEqual(value.get_value(), 23)
732         value.set_value(42.0)
733         self.assertEqual(value.get_value(), 42)
734
735     def test_string(self):
736         value = GObject.Value(str, 'foo_bar')
737         self.assertEqual(value.g_type, GObject.TYPE_STRING)
738         self.assertEqual(value.get_value(), 'foo_bar')
739
740     def test_float(self):
741         # python float is G_TYPE_DOUBLE
742         value = GObject.Value(float, 23.4)
743         self.assertEqual(value.g_type, GObject.TYPE_DOUBLE)
744         value.set_value(1e50)
745         self.assertAlmostEqual(value.get_value(), 1e50)
746
747         value = GObject.Value(GObject.TYPE_FLOAT, 23.4)
748         self.assertEqual(value.g_type, GObject.TYPE_FLOAT)
749         self.assertRaises(TypeError, value.set_value, 'string')
750         self.assertRaises(OverflowError, value.set_value, 1e50)
751
752     def test_float_inf_nan(self):
753         nan = float('nan')
754         for type_ in [GObject.TYPE_FLOAT, GObject.TYPE_DOUBLE]:
755             for x in [float('inf'), float('-inf'), nan]:
756                 value = GObject.Value(type_, x)
757                 # assertEqual() is False for (nan, nan)
758                 if x is nan:
759                     self.assertEqual(str(value.get_value()), 'nan')
760                 else:
761                     self.assertEqual(value.get_value(), x)
762
763     def test_enum(self):
764         value = GObject.Value(GLib.FileError, GLib.FileError.FAILED)
765         self.assertEqual(value.get_value(), GLib.FileError.FAILED)
766
767     def test_flags(self):
768         value = GObject.Value(GLib.IOFlags, GLib.IOFlags.IS_READABLE)
769         self.assertEqual(value.get_value(), GLib.IOFlags.IS_READABLE)
770
771     def test_object(self):
772         class TestObject(GObject.Object):
773             pass
774         obj = TestObject()
775         value = GObject.Value(GObject.TYPE_OBJECT, obj)
776         self.assertEqual(value.get_value(), obj)
777
778     def test_value_array(self):
779         value = GObject.Value(GObject.ValueArray)
780         self.assertEqual(value.g_type, GObject.type_from_name('GValueArray'))
781         value.set_value([32, 'foo_bar', 0.3])
782         self.assertEqual(value.get_value(), [32, 'foo_bar', 0.3])
783
784     def test_value_array_from_gvalue_list(self):
785         value = GObject.Value(GObject.ValueArray, [
786             GObject.Value(GObject.TYPE_UINT, 0xffffffff),
787             GObject.Value(GObject.TYPE_STRING, 'foo_bar')])
788         self.assertEqual(value.g_type, GObject.type_from_name('GValueArray'))
789         self.assertEqual(value.get_value(), [0xffffffff, 'foo_bar'])
790         self.assertEqual(testhelper.value_array_get_nth_type(value, 0), GObject.TYPE_UINT)
791         self.assertEqual(testhelper.value_array_get_nth_type(value, 1), GObject.TYPE_STRING)
792
793     def test_value_array_append_gvalue(self):
794         with warnings.catch_warnings():
795             warnings.simplefilter('ignore', DeprecationWarning)
796
797             arr = GObject.ValueArray.new(0)
798             arr.append(GObject.Value(GObject.TYPE_UINT, 0xffffffff))
799             arr.append(GObject.Value(GObject.TYPE_STRING, 'foo_bar'))
800             self.assertEqual(arr.get_nth(0), 0xffffffff)
801             self.assertEqual(arr.get_nth(1), 'foo_bar')
802             self.assertEqual(testhelper.value_array_get_nth_type(arr, 0), GObject.TYPE_UINT)
803             self.assertEqual(testhelper.value_array_get_nth_type(arr, 1), GObject.TYPE_STRING)
804
805     def test_gerror_boxing(self):
806         error = GLib.Error('test message', domain='mydomain', code=42)
807         value = GObject.Value(GLib.Error, error)
808         self.assertEqual(value.g_type, GObject.type_from_name('GError'))
809
810         unboxed = value.get_value()
811         self.assertEqual(unboxed.message, error.message)
812         self.assertEqual(unboxed.domain, error.domain)
813         self.assertEqual(unboxed.code, error.code)
814
815     def test_gerror_novalue(self):
816         GLib.Error('test message', domain='mydomain', code=42)
817         value = GObject.Value(GLib.Error)
818         self.assertEqual(value.g_type, GObject.type_from_name('GError'))
819         self.assertEqual(value.get_value(), None)
820
821
822 def test_list_properties():
823
824     def find_param(props, name):
825         for param in props:
826             if param.name == name:
827                 return param
828         return
829
830     list_props = GObject.list_properties
831
832     props = list_props(Gio.Action)
833     param = find_param(props, "enabled")
834     assert param
835     assert param.value_type == GObject.TYPE_BOOLEAN
836     assert list_props("GAction") == list_props(Gio.Action)
837     assert list_props(Gio.Action.__gtype__) == list_props(Gio.Action)
838
839     props = list_props(Gio.SimpleAction)
840     assert find_param(props, "enabled")
841
842     def names(props):
843         return [p.name for p in props]
844
845     assert (set(names(list_props(Gio.Action))) <=
846             set(names(list_props(Gio.SimpleAction))))
847
848     props = list_props(Gio.FileIcon)
849     param = find_param(props, "file")
850     assert param
851     assert param.value_type == Gio.File.__gtype__
852
853     assert list_props("GFileIcon") == list_props(Gio.FileIcon)
854     assert list_props(Gio.FileIcon.__gtype__) == list_props(Gio.FileIcon)
855     assert list_props(Gio.FileIcon(
856         file=Gio.File.new_for_path('.'))) == list_props(Gio.FileIcon)
857
858     for obj in [Gio.ActionEntry, Gio.DBusError, 0, object()]:
859         with pytest.raises(TypeError):
860             list_props(obj)