Imported Upstream version 3.25.1
[platform/upstream/pygobject2.git] / tests / test_signal.py
1 # -*- Mode: Python -*-
2
3 import gc
4 import unittest
5 import sys
6 import weakref
7
8 from gi.repository import GObject, GLib, Regress
9 from gi import _signalhelper as signalhelper
10 import testhelper
11 from compathelper import _long
12 from helper import capture_glib_warnings, capture_gi_deprecation_warnings
13
14
15 class C(GObject.GObject):
16     __gsignals__ = {'my_signal': (GObject.SignalFlags.RUN_FIRST, None,
17                                   (GObject.TYPE_INT,))}
18
19     def do_my_signal(self, arg):
20         self.arg = arg
21
22
23 class D(C):
24     def do_my_signal(self, arg2):
25         self.arg2 = arg2
26         C.do_my_signal(self, arg2)
27
28
29 class TestSignalCreation(unittest.TestCase):
30     # Bug 540376.
31     def test_illegals(self):
32         self.assertRaises(TypeError, lambda: GObject.signal_new('test',
33                                                                 None,
34                                                                 0,
35                                                                 None,
36                                                                 (GObject.TYPE_LONG,)))
37
38
39 class TestChaining(unittest.TestCase):
40     def setUp(self):
41         self.inst = C()
42         self.inst.connect("my_signal", self.my_signal_handler_cb, 1, 2, 3)
43
44     def my_signal_handler_cb(self, *args):
45         assert len(args) == 5
46         assert isinstance(args[0], C)
47         assert args[0] == self.inst
48
49         assert isinstance(args[1], int)
50         assert args[1] == 42
51
52         assert args[2:] == (1, 2, 3)
53
54     def test_chaining(self):
55         self.inst.emit("my_signal", 42)
56         assert self.inst.arg == 42
57
58     def test_chaining2(self):
59         inst2 = D()
60         inst2.emit("my_signal", 44)
61         assert inst2.arg == 44
62         assert inst2.arg2 == 44
63
64 # This is for bug 153718
65
66
67 class TestGSignalsError(unittest.TestCase):
68     def test_invalid_type(self, *args):
69         def foo():
70             class Foo(GObject.GObject):
71                 __gsignals__ = None
72         self.assertRaises(TypeError, foo)
73         gc.collect()
74
75     def test_invalid_name(self, *args):
76         def foo():
77             class Foo(GObject.GObject):
78                 __gsignals__ = {'not-exists': 'override'}
79
80         with capture_glib_warnings(allow_warnings=True):
81             self.assertRaises(TypeError, foo)
82         gc.collect()
83
84
85 class TestGPropertyError(unittest.TestCase):
86     def test_invalid_type(self, *args):
87         def foo():
88             class Foo(GObject.GObject):
89                 __gproperties__ = None
90         self.assertRaises(TypeError, foo)
91         gc.collect()
92
93     def test_invalid_name(self, *args):
94         def foo():
95             class Foo(GObject.GObject):
96                 __gproperties__ = {None: None}
97
98         self.assertRaises(TypeError, foo)
99         gc.collect()
100
101
102 class TestList(unittest.TestCase):
103     def test_list_names(self):
104         self.assertEqual(GObject.signal_list_names(C), ('my-signal',))
105
106
107 def my_accumulator(ihint, return_accu, handler_return, user_data):
108     """An accumulator that stops emission when the sum of handler
109     returned values reaches 3"""
110     assert user_data == "accum data"
111     if return_accu >= 3:
112         return False, return_accu
113     return True, return_accu + handler_return
114
115
116 class Foo(GObject.GObject):
117     my_acc_signal = GObject.Signal(return_type=GObject.TYPE_INT,
118                                    flags=GObject.SignalFlags.RUN_LAST,
119                                    accumulator=my_accumulator,
120                                    accu_data="accum data")
121
122     my_other_acc_signal = GObject.Signal(return_type=GObject.TYPE_BOOLEAN,
123                                          flags=GObject.SignalFlags.RUN_LAST,
124                                          accumulator=GObject.signal_accumulator_true_handled)
125
126     my_acc_first_wins = GObject.Signal(return_type=GObject.TYPE_BOOLEAN,
127                                        flags=GObject.SignalFlags.RUN_LAST,
128                                        accumulator=GObject.signal_accumulator_first_wins)
129
130
131 class TestAccumulator(unittest.TestCase):
132
133     def test_accumulator(self):
134         inst = Foo()
135         inst.my_acc_signal.connect(lambda obj: 1)
136         inst.my_acc_signal.connect(lambda obj: 2)
137         # the value returned in the following handler will not be
138         # considered, because at this point the accumulator already
139         # reached its limit.
140         inst.my_acc_signal.connect(lambda obj: 3)
141         retval = inst.my_acc_signal.emit()
142         self.assertEqual(retval, 3)
143
144     def test_accumulator_true_handled(self):
145         inst = Foo()
146         inst.my_other_acc_signal.connect(self._true_handler1)
147         inst.my_other_acc_signal.connect(self._true_handler2)
148         # the following handler will not be called because handler2
149         # returns True, so it should stop the emission.
150         inst.my_other_acc_signal.connect(self._true_handler3)
151         self.__true_val = None
152         inst.my_other_acc_signal.emit()
153         self.assertEqual(self.__true_val, 2)
154
155     def test_accumulator_first_wins(self):
156         # First signal hit will always win
157         inst = Foo()
158         inst.my_acc_first_wins.connect(self._true_handler3)
159         inst.my_acc_first_wins.connect(self._true_handler1)
160         inst.my_acc_first_wins.connect(self._true_handler2)
161         self.__true_val = None
162         inst.my_acc_first_wins.emit()
163         self.assertEqual(self.__true_val, 3)
164
165     def _true_handler1(self, obj):
166         self.__true_val = 1
167         return False
168
169     def _true_handler2(self, obj):
170         self.__true_val = 2
171         return True
172
173     def _true_handler3(self, obj):
174         self.__true_val = 3
175         return False
176
177
178 class E(GObject.GObject):
179     __gsignals__ = {'signal': (GObject.SignalFlags.RUN_FIRST, None,
180                                ())}
181
182     # Property used to test detailed signal
183     prop = GObject.Property(type=int, default=0)
184
185     def __init__(self):
186         GObject.GObject.__init__(self)
187         self.status = 0
188
189     def do_signal(self):
190         assert self.status == 0
191         self.status = 1
192
193
194 class F(GObject.GObject):
195     __gsignals__ = {'signal': (GObject.SignalFlags.RUN_FIRST, None,
196                                ())}
197
198     def __init__(self):
199         GObject.GObject.__init__(self)
200         self.status = 0
201
202     def do_signal(self):
203         self.status += 1
204
205
206 class TestEmissionHook(unittest.TestCase):
207     def test_add(self):
208         self.hook = True
209         e = E()
210         e.connect('signal', self._callback)
211         GObject.add_emission_hook(E, "signal", self._emission_hook)
212         e.emit('signal')
213         self.assertEqual(e.status, 3)
214
215     def test_remove(self):
216         self.hook = False
217         e = E()
218         e.connect('signal', self._callback)
219         hook_id = GObject.add_emission_hook(E, "signal", self._emission_hook)
220         GObject.remove_emission_hook(E, "signal", hook_id)
221         e.emit('signal')
222         self.assertEqual(e.status, 3)
223
224     def _emission_hook(self, e):
225         self.assertEqual(e.status, 1)
226         e.status = 2
227
228     def _callback(self, e):
229         if self.hook:
230             self.assertEqual(e.status, 2)
231         else:
232             self.assertEqual(e.status, 1)
233         e.status = 3
234
235     def test_callback_return_false(self):
236         self.hook = False
237         obj = F()
238
239         def _emission_hook(obj):
240             obj.status += 1
241             return False
242         GObject.add_emission_hook(obj, "signal", _emission_hook)
243         obj.emit('signal')
244         obj.emit('signal')
245         self.assertEqual(obj.status, 3)
246
247     def test_callback_return_true(self):
248         self.hook = False
249         obj = F()
250
251         def _emission_hook(obj):
252             obj.status += 1
253             return True
254         hook_id = GObject.add_emission_hook(obj, "signal", _emission_hook)
255         obj.emit('signal')
256         obj.emit('signal')
257         GObject.remove_emission_hook(obj, "signal", hook_id)
258         self.assertEqual(obj.status, 4)
259
260     def test_callback_return_true_but_remove(self):
261         self.hook = False
262         obj = F()
263
264         def _emission_hook(obj):
265             obj.status += 1
266             return True
267         hook_id = GObject.add_emission_hook(obj, "signal", _emission_hook)
268         obj.emit('signal')
269         GObject.remove_emission_hook(obj, "signal", hook_id)
270         obj.emit('signal')
271         self.assertEqual(obj.status, 3)
272
273
274 class TestMatching(unittest.TestCase):
275     class Object(GObject.Object):
276         status = 0
277         prop = GObject.Property(type=int, default=0)
278
279         @GObject.Signal()
280         def my_signal(self):
281             pass
282
283     @unittest.expectedFailure  # https://bugzilla.gnome.org/show_bug.cgi?id=692918
284     def test_signal_handler_block_matching(self):
285         def dummy(*args):
286             "Hack to work around: "
287
288         def foo(obj):
289             obj.status += 1
290
291         obj = self.Object()
292         handler_id = GObject.signal_connect_closure(obj, 'my-signal', foo, after=False)
293         handler_id
294
295         self.assertEqual(obj.status, 0)
296         obj.emit('my-signal')
297         self.assertEqual(obj.status, 1)
298
299         # Blocking by match criteria disables the foo callback
300         signal_id, detail = GObject.signal_parse_name('my-signal', obj, True)
301         count = GObject.signal_handlers_block_matched(obj,
302                                                       GObject.SignalMatchType.ID | GObject.SignalMatchType.CLOSURE,
303                                                       signal_id=signal_id, detail=detail,
304                                                       closure=foo, func=dummy, data=dummy)
305         self.assertEqual(count, 1)
306         obj.emit('my-signal')
307         self.assertEqual(obj.status, 1)
308
309         # Unblocking by the same match criteria allows callback to work again
310         count = GObject.signal_handlers_unblock_matched(obj,
311                                                         GObject.SignalMatchType.ID | GObject.SignalMatchType.CLOSURE,
312                                                         signal_id=signal_id, detail=detail,
313                                                         closure=foo, func=dummy, data=dummy)
314         self.assertEqual(count, 1)
315         obj.emit('my-signal')
316         self.assertEqual(obj.status, 2)
317
318         # Disconnecting by match criteria completely removes the handler
319         count = GObject.signal_handlers_disconnect_matched(obj,
320                                                            GObject.SignalMatchType.ID | GObject.SignalMatchType.CLOSURE,
321                                                            signal_id=signal_id, detail=detail,
322                                                            closure=foo, func=dummy, data=dummy)
323         self.assertEqual(count, 1)
324         obj.emit('my-signal')
325         self.assertEqual(obj.status, 2)
326
327     def test_signal_handler_find(self):
328         def foo(obj):
329             obj.status += 1
330
331         obj = self.Object()
332         handler_id = GObject.signal_connect_closure(obj, 'my-signal', foo, after=False)
333
334         signal_id, detail = GObject.signal_parse_name('my-signal', obj, True)
335         found_id = GObject.signal_handler_find(obj,
336                                                GObject.SignalMatchType.ID,
337                                                signal_id=signal_id, detail=detail,
338                                                closure=None, func=0, data=0)
339         self.assertEqual(handler_id, found_id)
340
341
342 class TestClosures(unittest.TestCase):
343     def setUp(self):
344         self.count = 0
345         self.emission_stopped = False
346         self.emission_error = False
347         self.handler_pending = False
348
349     def _callback_handler_pending(self, e):
350         signal_id, detail = GObject.signal_parse_name('signal', e, True)
351         self.handler_pending = GObject.signal_has_handler_pending(e, signal_id, detail,
352                                                                   may_be_blocked=False)
353
354     def _callback(self, e):
355         self.count += 1
356
357     def _callback_stop_emission(self, obj, prop, stop_it):
358         if stop_it:
359             obj.stop_emission_by_name('notify::prop')
360             self.emission_stopped = True
361         else:
362             self.count += 1
363
364     def _callback_invalid_stop_emission_name(self, obj, prop):
365         with capture_glib_warnings(allow_warnings=True) as warn:
366             obj.stop_emission_by_name('notasignal::baddetail')
367             self.emission_error = True
368             self.assertTrue(warn)
369
370     def test_disconnect_by_func(self):
371         e = E()
372         e.connect('signal', self._callback)
373         e.disconnect_by_func(self._callback)
374         e.emit('signal')
375         self.assertEqual(self.count, 0)
376
377     def test_disconnect(self):
378         e = E()
379         handler_id = e.connect('signal', self._callback)
380         self.assertTrue(e.handler_is_connected(handler_id))
381         e.disconnect(handler_id)
382         e.emit('signal')
383         self.assertEqual(self.count, 0)
384         self.assertFalse(e.handler_is_connected(handler_id))
385
386     def test_stop_emission_by_name(self):
387         e = E()
388
389         # Sandwich a callback that stops emission in between a callback that increments
390         e.connect('notify::prop', self._callback_stop_emission, False)
391         e.connect('notify::prop', self._callback_stop_emission, True)
392         e.connect('notify::prop', self._callback_stop_emission, False)
393
394         e.set_property('prop', 1234)
395         self.assertEqual(e.get_property('prop'), 1234)
396         self.assertEqual(self.count, 1)
397         self.assertTrue(self.emission_stopped)
398
399     def test_stop_emission_by_name_error(self):
400         e = E()
401
402         e.connect('notify::prop', self._callback_invalid_stop_emission_name)
403         with capture_glib_warnings():
404             e.set_property('prop', 1234)
405         self.assertTrue(self.emission_error)
406
407     def test_handler_block(self):
408         e = E()
409         e.connect('signal', self._callback)
410         e.handler_block_by_func(self._callback)
411         e.emit('signal')
412         self.assertEqual(self.count, 0)
413
414     def test_handler_unblock(self):
415         e = E()
416         handler_id = e.connect('signal', self._callback)
417         e.handler_block(handler_id)
418         e.handler_unblock_by_func(self._callback)
419         e.emit('signal')
420         self.assertEqual(self.count, 1)
421
422     def test_handler_block_method(self):
423         # Filed as #375589
424         class A:
425             def __init__(self):
426                 self.a = 0
427
428             def callback(self, o):
429                 self.a = 1
430                 o.handler_block_by_func(self.callback)
431
432         inst = A()
433         e = E()
434         e.connect("signal", inst.callback)
435         e.emit('signal')
436         self.assertEqual(inst.a, 1)
437         gc.collect()
438
439     def test_gstring(self):
440         class C(GObject.GObject):
441             __gsignals__ = {'my_signal': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_GSTRING,
442                                           (GObject.TYPE_GSTRING,))}
443
444             def __init__(self, test):
445                 GObject.GObject.__init__(self)
446                 self.test = test
447
448             def do_my_signal(self, data):
449                 self.data = data
450                 self.test.assertEqual(len(data), 3)
451                 return ''.join([data[2], data[1], data[0]])
452         c = C(self)
453         data = c.emit("my_signal", "\01\00\02")
454         self.assertEqual(data, "\02\00\01")
455
456     def test_handler_pending(self):
457         obj = F()
458         obj.connect('signal', self._callback_handler_pending)
459         obj.connect('signal', self._callback)
460
461         self.assertEqual(self.count, 0)
462         self.assertEqual(self.handler_pending, False)
463
464         obj.emit('signal')
465         self.assertEqual(self.count, 1)
466         self.assertEqual(self.handler_pending, True)
467
468     def test_signal_handlers_destroy(self):
469         obj = F()
470         obj.connect('signal', self._callback)
471         obj.connect('signal', self._callback)
472         obj.connect('signal', self._callback)
473
474         obj.emit('signal')
475         self.assertEqual(self.count, 3)
476
477         # count should remain at 3 after all handlers are destroyed
478         GObject.signal_handlers_destroy(obj)
479         obj.emit('signal')
480         self.assertEqual(self.count, 3)
481
482
483 class SigPropClass(GObject.GObject):
484     __gsignals__ = {'my_signal': (GObject.SignalFlags.RUN_FIRST, None,
485                                   (GObject.TYPE_INT,))}
486
487     __gproperties__ = {
488         'foo': (str, None, None, '',
489                 GObject.ParamFlags.WRITABLE | GObject.ParamFlags.CONSTRUCT),
490         }
491
492     signal_emission_failed = False
493
494     def do_my_signal(self, arg):
495         self.arg = arg
496
497     def do_set_property(self, pspec, value):
498         if pspec.name == 'foo':
499             self._foo = value
500         else:
501             raise AttributeError('unknown property %s' % pspec.name)
502         try:
503             self.emit("my-signal", 1)
504         except TypeError:
505             self.signal_emission_failed = True
506
507
508 class TestSigProp(unittest.TestCase):
509     def test_emit_in_property_setter(self):
510         obj = SigPropClass()
511         self.assertFalse(obj.signal_emission_failed)
512
513
514 class CM(GObject.GObject):
515     __gsignals__ = dict(
516         test1=(GObject.SignalFlags.RUN_FIRST, None, ()),
517         test2=(GObject.SignalFlags.RUN_LAST, None, (str,)),
518         test3=(GObject.SignalFlags.RUN_LAST, int, (GObject.TYPE_DOUBLE,)),
519         test4=(GObject.SignalFlags.RUN_FIRST, None,
520                (bool, _long, GObject.TYPE_FLOAT, GObject.TYPE_DOUBLE, int,
521                 GObject.TYPE_UINT, GObject.TYPE_ULONG)),
522         test_float=(GObject.SignalFlags.RUN_LAST, GObject.TYPE_FLOAT, (GObject.TYPE_FLOAT,)),
523         test_double=(GObject.SignalFlags.RUN_LAST, GObject.TYPE_DOUBLE, (GObject.TYPE_DOUBLE,)),
524         test_int64=(GObject.SignalFlags.RUN_LAST, GObject.TYPE_INT64, (GObject.TYPE_INT64,)),
525         test_string=(GObject.SignalFlags.RUN_LAST, str, (str,)),
526         test_object=(GObject.SignalFlags.RUN_LAST, object, (object,)),
527         test_paramspec=(GObject.SignalFlags.RUN_LAST, GObject.ParamSpec, ()),
528         test_paramspec_in=(GObject.SignalFlags.RUN_LAST, GObject.ParamSpec, (GObject.ParamSpec, )),
529         test_gvalue=(GObject.SignalFlags.RUN_LAST, GObject.Value, (GObject.Value,)),
530         test_gvalue_ret=(GObject.SignalFlags.RUN_LAST, GObject.Value, (GObject.TYPE_GTYPE,)),
531     )
532
533     testprop = GObject.Property(type=int)
534
535
536 class _TestCMarshaller:
537     def setUp(self):
538         self.obj = CM()
539         testhelper.connectcallbacks(self.obj)
540
541     def test_test1(self):
542         self.obj.emit("test1")
543
544     def test_test2(self):
545         self.obj.emit("test2", "string")
546
547     def test_test3(self):
548         rv = self.obj.emit("test3", 42.0)
549         self.assertEqual(rv, 20)
550
551     def test_test4(self):
552         self.obj.emit("test4", True, _long(10), 3.14, 1.78, 20, _long(30), _long(31))
553
554     def test_float(self):
555         rv = self.obj.emit("test-float", 1.234)
556         self.assertTrue(rv >= 1.233999 and rv <= 1.2400001, rv)
557
558     def test_double(self):
559         rv = self.obj.emit("test-double", 1.234)
560         self.assertEqual(rv, 1.234)
561
562     def test_int64(self):
563         rv = self.obj.emit("test-int64", 102030405)
564         self.assertEqual(rv, 102030405)
565
566         rv = self.obj.emit("test-int64", GLib.MAXINT64)
567         self.assertEqual(rv, GLib.MAXINT64 - 1)
568
569         rv = self.obj.emit("test-int64", GLib.MININT64)
570         self.assertEqual(rv, GLib.MININT64)
571
572     def test_string(self):
573         rv = self.obj.emit("test-string", "str")
574         self.assertEqual(rv, "str")
575
576     def test_object(self):
577         rv = self.obj.emit("test-object", self)
578         self.assertEqual(rv, self)
579
580     def test_paramspec(self):
581         rv = self.obj.emit("test-paramspec")
582         self.assertEqual(rv.name, "test-param")
583         self.assertEqual(rv.nick, "test")
584
585     @unittest.skipUnless(hasattr(GObject, 'param_spec_boolean'),
586                          'too old gobject-introspection')
587     def test_paramspec_in(self):
588         rv = GObject.param_spec_boolean('mybool', 'test-bool', 'do something',
589                                         True, GObject.ParamFlags.READABLE)
590
591         rv2 = self.obj.emit("test-paramspec-in", rv)
592         self.assertEqual(type(rv), type(rv2))
593         self.assertEqual(rv2.name, "mybool")
594         self.assertEqual(rv2.nick, "test-bool")
595
596     def test_C_paramspec(self):
597         self.notify_called = False
598
599         def cb_notify(obj, prop):
600             self.notify_called = True
601             self.assertEqual(obj, self.obj)
602             self.assertEqual(prop.name, "testprop")
603
604         self.obj.connect("notify", cb_notify)
605         self.obj.set_property("testprop", 42)
606         self.assertTrue(self.notify_called)
607
608     def test_gvalue(self):
609         # implicit int
610         rv = self.obj.emit("test-gvalue", 42)
611         self.assertEqual(rv, 42)
612
613         # explicit float
614         v = GObject.Value(GObject.TYPE_FLOAT, 1.234)
615         rv = self.obj.emit("test-gvalue", v)
616         self.assertAlmostEqual(rv, 1.234, places=4)
617
618         # implicit float
619         rv = self.obj.emit("test-gvalue", 1.234)
620         self.assertAlmostEqual(rv, 1.234, places=4)
621
622         # explicit int64
623         v = GObject.Value(GObject.TYPE_INT64, GLib.MAXINT64)
624         rv = self.obj.emit("test-gvalue", v)
625         self.assertEqual(rv, GLib.MAXINT64)
626
627         # explicit uint64
628         v = GObject.Value(GObject.TYPE_UINT64, GLib.MAXUINT64)
629         rv = self.obj.emit("test-gvalue", v)
630         self.assertEqual(rv, GLib.MAXUINT64)
631
632     @unittest.expectedFailure  # https://bugzilla.gnome.org/show_bug.cgi?id=705291
633     def test_gvalue_implicit_int64(self):
634         # implicit int64
635         rv = self.obj.emit("test-gvalue", GLib.MAXINT64)
636         self.assertEqual(rv, GLib.MAXINT64)
637
638         # implicit uint64
639         rv = self.obj.emit("test-gvalue", GLib.MAXUINT64)
640         self.assertEqual(rv, GLib.MAXUINT64)
641
642     def test_gvalue_ret(self):
643         self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_INT),
644                          GLib.MAXINT)
645         self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_UINT),
646                          GLib.MAXUINT)
647         self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_INT64),
648                          GLib.MAXINT64)
649         self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_UINT64),
650                          GLib.MAXUINT64)
651         self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_STRING),
652                          "hello")
653
654
655 if 'generic-c-marshaller' in GObject.features:
656     class TestCMarshaller(_TestCMarshaller, unittest.TestCase):
657         pass
658 else:
659     print()
660     print('** WARNING: LIBFFI disabled, not testing')
661     print()
662
663 # Test for 374653
664
665
666 class TestPyGValue(unittest.TestCase):
667     def test_none_null_boxed_conversion(self):
668         class C(GObject.GObject):
669             __gsignals__ = dict(my_boxed_signal=(
670                 GObject.SignalFlags.RUN_LAST,
671                 GObject.TYPE_STRV, ()))
672
673         obj = C()
674         obj.connect('my-boxed-signal', lambda obj: None)
675         sys.last_type = None
676         obj.emit('my-boxed-signal')
677         assert not sys.last_type
678
679
680 class TestSignalDecorator(unittest.TestCase):
681     class Decorated(GObject.GObject):
682         value = 0
683
684         @GObject.Signal
685         def pushed(self):
686             """this will push"""
687             self.value += 1
688
689         @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
690         def pulled(self):
691             self.value -= 1
692
693         stomped = GObject.Signal('stomped', arg_types=(int,), doc='this will stomp')
694         unnamed = GObject.Signal()
695
696     class DecoratedOverride(GObject.GObject):
697         overridden_closure_called = False
698         notify_called = False
699         value = GObject.Property(type=int, default=0)
700
701         @GObject.SignalOverride
702         def notify(self, *args, **kargs):
703             self.overridden_closure_called = True
704
705         def on_notify(self, obj, prop):
706             self.notify_called = True
707
708     def setUp(self):
709         self.unnamedCalled = False
710
711     def onUnnamed(self, obj):
712         self.unnamedCalled = True
713
714     def test_get_signal_args(self):
715         self.assertEqual(self.Decorated.pushed.get_signal_args(),
716                          (GObject.SignalFlags.RUN_FIRST, None, tuple(), None, None))
717         self.assertEqual(self.Decorated.pulled.get_signal_args(),
718                          (GObject.SignalFlags.RUN_LAST, None, tuple(), None, None))
719         self.assertEqual(self.Decorated.stomped.get_signal_args(),
720                          (GObject.SignalFlags.RUN_FIRST, None, (int,), None, None))
721
722     def test_closures_called(self):
723         decorated = self.Decorated()
724         self.assertEqual(decorated.value, 0)
725         decorated.pushed.emit()
726         self.assertEqual(decorated.value, 1)
727         decorated.pulled.emit()
728         self.assertEqual(decorated.value, 0)
729
730     def test_signal_copy(self):
731         blah = self.Decorated.stomped.copy('blah')
732         self.assertEqual(str(blah), blah)
733         self.assertEqual(blah.func, self.Decorated.stomped.func)
734         self.assertEqual(blah.flags, self.Decorated.stomped.flags)
735         self.assertEqual(blah.return_type, self.Decorated.stomped.return_type)
736         self.assertEqual(blah.arg_types, self.Decorated.stomped.arg_types)
737         self.assertEqual(blah.__doc__, self.Decorated.stomped.__doc__)
738
739     def test_doc_string(self):
740         # Test the two techniques for setting doc strings on the signals
741         # class variables, through the "doc" keyword or as the getter doc string.
742         self.assertEqual(self.Decorated.stomped.__doc__, 'this will stomp')
743         self.assertEqual(self.Decorated.pushed.__doc__, 'this will push')
744
745     def test_unnamed_signal_gets_named(self):
746         self.assertEqual(str(self.Decorated.unnamed), 'unnamed')
747
748     def test_unnamed_signal_gets_called(self):
749         obj = self.Decorated()
750         obj.connect('unnamed', self.onUnnamed)
751         self.assertEqual(self.unnamedCalled, False)
752         obj.emit('unnamed')
753         self.assertEqual(self.unnamedCalled, True)
754
755     def test_overridden_signal(self):
756         # Test that the pushed signal is called in with super and the override
757         # which should both increment the "value" to 3
758         obj = self.DecoratedOverride()
759         obj.connect("notify", obj.on_notify)
760         self.assertEqual(obj.value, 0)
761         obj.value = 1
762         self.assertEqual(obj.value, 1)
763         self.assertTrue(obj.overridden_closure_called)
764         self.assertTrue(obj.notify_called)
765
766
767 class TestSignalConnectors(unittest.TestCase):
768     class CustomButton(GObject.GObject):
769         on_notify_called = False
770         value = GObject.Property(type=int)
771
772         @GObject.Signal(arg_types=(int,))
773         def clicked(self, value):
774             self.value = value
775
776     def setUp(self):
777         self.obj = None
778         self.value = None
779
780     def on_clicked(self, obj, value):
781         self.obj = obj
782         self.value = value
783
784     def test_signal_notify(self):
785         def on_notify(obj, param):
786             obj.on_notify_called = True
787
788         obj = self.CustomButton()
789         obj.connect('notify', on_notify)
790         self.assertFalse(obj.on_notify_called)
791         obj.notify('value')
792         self.assertTrue(obj.on_notify_called)
793
794     def test_signal_emit(self):
795         # standard callback connection with different forms of emit.
796         obj = self.CustomButton()
797         obj.connect('clicked', self.on_clicked)
798
799         # vanilla
800         obj.emit('clicked', 1)
801         self.assertEqual(obj.value, 1)
802         self.assertEqual(obj, self.obj)
803         self.assertEqual(self.value, 1)
804
805         # using class signal as param
806         self.obj = None
807         self.value = None
808         obj.emit(self.CustomButton.clicked, 1)
809         self.assertEqual(obj, self.obj)
810         self.assertEqual(self.value, 1)
811
812         # using bound signal as param
813         self.obj = None
814         self.value = None
815         obj.emit(obj.clicked, 1)
816         self.assertEqual(obj, self.obj)
817         self.assertEqual(self.value, 1)
818
819         # using bound signal with emit
820         self.obj = None
821         self.value = None
822         obj.clicked.emit(1)
823         self.assertEqual(obj, self.obj)
824         self.assertEqual(self.value, 1)
825
826     def test_signal_class_connect(self):
827         obj = self.CustomButton()
828         obj.connect(self.CustomButton.clicked, self.on_clicked)
829         obj.emit('clicked', 2)
830         self.assertEqual(obj, self.obj)
831         self.assertEqual(self.value, 2)
832
833     def test_signal_bound_connect(self):
834         obj = self.CustomButton()
835         obj.clicked.connect(self.on_clicked)
836         obj.emit('clicked', 3)
837         self.assertEqual(obj, self.obj)
838         self.assertEqual(self.value, 3)
839
840
841 class _ConnectDataTestBase(object):
842     # Notes:
843     #  - self.Object is overridden in sub-classes.
844     #  - Numeric suffixes indicate the number of user data args passed in.
845     Object = None
846
847     def run_connect_test(self, emit_args, user_data, flags=0):
848         obj = self.Object()
849         callback_args = []
850
851         def callback(*args):
852             callback_args.append(args)
853             return 0
854
855         obj.connect_data('sig-with-int64-prop', callback, connect_flags=flags, *user_data)
856         obj.emit('sig-with-int64-prop', *emit_args)
857         self.assertEqual(len(callback_args), 1)
858         return callback_args[0]
859
860     def test_0(self):
861         obj, value = self.run_connect_test([GLib.MAXINT64], user_data=[])
862         self.assertIsInstance(obj, self.Object)
863         self.assertEqual(value, GLib.MAXINT64)
864
865     def test_1(self):
866         obj, value, data = self.run_connect_test([GLib.MAXINT64],
867                                                  user_data=['mydata'])
868         self.assertIsInstance(obj, self.Object)
869         self.assertEqual(value, GLib.MAXINT64)
870         self.assertEqual(data, 'mydata')
871
872     def test_after_0(self):
873         obj, value = self.run_connect_test([GLib.MAXINT64],
874                                            user_data=[],
875                                            flags=GObject.ConnectFlags.AFTER)
876         self.assertIsInstance(obj, self.Object)
877         self.assertEqual(value, GLib.MAXINT64)
878
879     def test_after_1(self):
880         obj, value, data = self.run_connect_test([GLib.MAXINT64],
881                                                  user_data=['mydata'],
882                                                  flags=GObject.ConnectFlags.AFTER)
883         self.assertIsInstance(obj, self.Object)
884         self.assertEqual(value, GLib.MAXINT64)
885         self.assertEqual(data, 'mydata')
886
887     def test_swaped_0(self):
888         # Swapped only works with a single user data argument.
889         with self.assertRaises(ValueError):
890             self.run_connect_test([GLib.MAXINT64],
891                                   user_data=[],
892                                   flags=GObject.ConnectFlags.SWAPPED)
893
894     def test_swaped_1(self):
895         # Notice obj and data are reversed in the return.
896         data, value, obj = self.run_connect_test([GLib.MAXINT64],
897                                                  user_data=['mydata'],
898                                                  flags=GObject.ConnectFlags.SWAPPED)
899         self.assertIsInstance(obj, self.Object)
900         self.assertEqual(value, GLib.MAXINT64)
901         self.assertEqual(data, 'mydata')
902
903     def test_swaped_2(self):
904         # Swapped only works with a single user data argument.
905         with self.assertRaises(ValueError):
906             self.run_connect_test([GLib.MAXINT64],
907                                   user_data=[1, 2],
908                                   flags=GObject.ConnectFlags.SWAPPED)
909
910     def test_after_and_swapped_0(self):
911         # Swapped only works with a single user data argument.
912         with self.assertRaises(ValueError):
913             self.run_connect_test([GLib.MAXINT64],
914                                   user_data=[],
915                                   flags=GObject.ConnectFlags.AFTER | GObject.ConnectFlags.SWAPPED)
916
917     def test_after_and_swapped_1(self):
918         # Notice obj and data are reversed in the return.
919         data, value, obj = self.run_connect_test([GLib.MAXINT64],
920                                                  user_data=['mydata'],
921                                                  flags=GObject.ConnectFlags.AFTER | GObject.ConnectFlags.SWAPPED)
922         self.assertIsInstance(obj, self.Object)
923         self.assertEqual(value, GLib.MAXINT64)
924         self.assertEqual(data, 'mydata')
925
926     def test_after_and_swapped_2(self):
927         # Swapped only works with a single user data argument.
928         with self.assertRaises(ValueError):
929             self.run_connect_test([GLib.MAXINT64],
930                                   user_data=[],
931                                   flags=GObject.ConnectFlags.AFTER | GObject.ConnectFlags.SWAPPED)
932
933
934 class TestConnectDataNonIntrospected(unittest.TestCase, _ConnectDataTestBase):
935     # This tests connect_data with non-introspected signals
936     # (created in Python in this case).
937     class Object(GObject.Object):
938         test = GObject.Signal()
939         sig_with_int64_prop = GObject.Signal(return_type=GObject.TYPE_INT64,
940                                              arg_types=[GObject.TYPE_INT64],
941                                              flags=GObject.SignalFlags.RUN_LAST)
942
943
944 class TestConnectDataIntrospected(unittest.TestCase, _ConnectDataTestBase):
945     # This tests connect_data with introspected signals brought in from Regress.
946     Object = Regress.TestObj
947
948
949 class TestInstallSignals(unittest.TestCase):
950     # These tests only test how signalhelper.install_signals works
951     # with the __gsignals__ dict and therefore does not need to use
952     # GObject as a base class because that would automatically call
953     # install_signals within the meta-class.
954     class Base(object):
955         __gsignals__ = {'test': (0, None, tuple())}
956
957     class Sub1(Base):
958         pass
959
960     class Sub2(Base):
961         @GObject.Signal
962         def sub2test(self):
963             pass
964
965     def setUp(self):
966         self.assertEqual(len(self.Base.__gsignals__), 1)
967         signalhelper.install_signals(self.Base)
968         self.assertEqual(len(self.Base.__gsignals__), 1)
969
970     def test_subclass_gets_empty_gsignals_dict(self):
971         # Installing signals will add the __gsignals__ dict to a class
972         # if it doesn't already exists.
973         self.assertFalse('__gsignals__' in self.Sub1.__dict__)
974         signalhelper.install_signals(self.Sub1)
975         self.assertTrue('__gsignals__' in self.Sub1.__dict__)
976         # Sub1 should only contain an empty signals dict, this tests:
977         # https://bugzilla.gnome.org/show_bug.cgi?id=686496
978         self.assertEqual(self.Sub1.__dict__['__gsignals__'], {})
979
980     def test_subclass_with_decorator_gets_gsignals_dict(self):
981         self.assertFalse('__gsignals__' in self.Sub2.__dict__)
982         signalhelper.install_signals(self.Sub2)
983         self.assertTrue('__gsignals__' in self.Sub2.__dict__)
984         self.assertEqual(len(self.Base.__gsignals__), 1)
985         self.assertEqual(len(self.Sub2.__gsignals__), 1)
986         self.assertTrue('sub2test' in self.Sub2.__gsignals__)
987
988         # Make sure the vfunc was added
989         self.assertTrue(hasattr(self.Sub2, 'do_sub2test'))
990
991
992 # For this test to work with both python2 and 3 we need to dynamically
993 # exec the given code due to the new syntax causing an error in python 2.
994 annotated_class_code = """
995 class AnnotatedSignalClass(GObject.GObject):
996     @GObject.Signal
997     def sig1(self, a:int, b:float):
998         pass
999
1000     @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
1001     def sig2_with_return(self, a:int, b:float) -> str:
1002         return "test"
1003 """
1004
1005
1006 @unittest.skipUnless(sys.version_info >= (3, 0),
1007                      'Argument annotations require Python 3')
1008 class TestPython3Signals(unittest.TestCase):
1009     AnnotatedClass = None
1010
1011     def setUp(self):
1012         exec(annotated_class_code, globals(), globals())
1013         self.assertTrue('AnnotatedSignalClass' in globals())
1014         self.AnnotatedClass = globals()['AnnotatedSignalClass']
1015
1016     def test_annotations(self):
1017         self.assertEqual(signalhelper.get_signal_annotations(self.AnnotatedClass.sig1.func),
1018                          (None, (int, float)))
1019         self.assertEqual(signalhelper.get_signal_annotations(self.AnnotatedClass.sig2_with_return.func),
1020                          (str, (int, float)))
1021
1022         self.assertEqual(self.AnnotatedClass.sig2_with_return.get_signal_args(),
1023                          (GObject.SignalFlags.RUN_LAST, str, (int, float), None, None))
1024         self.assertEqual(self.AnnotatedClass.sig2_with_return.arg_types,
1025                          (int, float))
1026         self.assertEqual(self.AnnotatedClass.sig2_with_return.return_type,
1027                          str)
1028
1029     def test_emit_return(self):
1030         obj = self.AnnotatedClass()
1031         self.assertEqual(obj.sig2_with_return.emit(1, 2.0),
1032                          'test')
1033
1034
1035 class TestSignalModuleLevelFunctions(unittest.TestCase):
1036     def test_signal_list_ids_with_invalid_type(self):
1037         with self.assertRaisesRegex(TypeError, 'type must be instantiable or an interface.*'):
1038             GObject.signal_list_ids(GObject.TYPE_INVALID)
1039
1040     def test_signal_list_ids(self):
1041         with self.assertRaisesRegex(TypeError, 'type must be instantiable or an interface.*'):
1042             GObject.signal_list_ids(GObject.TYPE_INT)
1043
1044         ids = GObject.signal_list_ids(C)
1045         self.assertEqual(len(ids), 1)
1046         # Note canonicalized names
1047         self.assertEqual(GObject.signal_name(ids[0]), 'my-signal')
1048         # There is no signal 0 in gobject
1049         self.assertEqual(GObject.signal_name(0), None)
1050
1051     def test_signal_lookup_with_invalid_type(self):
1052         with self.assertRaisesRegex(TypeError, 'type must be instantiable or an interface.*'):
1053             GObject.signal_lookup('NOT_A_SIGNAL_NAME', GObject.TYPE_INVALID)
1054
1055     def test_signal_lookup(self):
1056         ids = GObject.signal_list_ids(C)
1057         self.assertEqual(ids[0], GObject.signal_lookup('my_signal', C))
1058         self.assertEqual(ids[0], GObject.signal_lookup('my-signal', C))
1059
1060         with self.assertRaisesRegex(TypeError, 'type must be instantiable or an interface.*'):
1061             GObject.signal_lookup('NOT_A_SIGNAL_NAME', GObject.TYPE_INT)
1062
1063         # Invalid signal names return 0 instead of raising
1064         self.assertEqual(GObject.signal_lookup('NOT_A_SIGNAL_NAME', C),
1065                          0)
1066
1067     def test_signal_query(self):
1068         my_signal_id, = GObject.signal_list_ids(C)
1069
1070         # Form is: (id, name, gtype, arg_count, return_type, (arg_type1, ...))
1071         my_signal_expected_query_result = [my_signal_id, 'my-signal', C.__gtype__,
1072                                            1, GObject.TYPE_NONE, (GObject.TYPE_INT,)]
1073         # signal_query(name, type)
1074         self.assertEqual(list(GObject.signal_query('my-signal', C)), my_signal_expected_query_result)
1075         # signal_query(signal_id)
1076         self.assertEqual(list(GObject.signal_query(my_signal_id)), my_signal_expected_query_result)
1077         # invalid query returns None instead of raising
1078         self.assertEqual(GObject.signal_query(0), None)
1079         self.assertEqual(GObject.signal_query('NOT_A_SIGNAL', C),
1080                          None)
1081
1082
1083 class TestIntrospectedSignals(unittest.TestCase):
1084     def test_object_param_signal(self):
1085         obj = Regress.TestObj()
1086
1087         def callback(obj, obj_param):
1088             self.assertEqual(obj_param.props.int, 3)
1089             self.assertGreater(obj_param.__grefcount__, 1)
1090             obj.called = True
1091
1092         obj.called = False
1093         obj.connect('sig-with-obj', callback)
1094         obj.emit_sig_with_obj()
1095         self.assertTrue(obj.called)
1096
1097     def test_connect_after(self):
1098         obj = Regress.TestObj()
1099
1100         def callback(obj, obj_param):
1101             obj.called = True
1102
1103         obj.called = False
1104         obj.connect_after('sig-with-obj', callback)
1105         obj.emit_sig_with_obj()
1106         self.assertTrue(obj.called)
1107
1108     def test_int64_param_from_py(self):
1109         obj = Regress.TestObj()
1110
1111         def callback(obj, i):
1112             obj.callback_i = i
1113             return i
1114
1115         obj.callback_i = None
1116         obj.connect('sig-with-int64-prop', callback)
1117         rv = obj.emit('sig-with-int64-prop', GLib.MAXINT64)
1118         self.assertEqual(rv, GLib.MAXINT64)
1119         self.assertEqual(obj.callback_i, GLib.MAXINT64)
1120
1121     def test_uint64_param_from_py(self):
1122         obj = Regress.TestObj()
1123
1124         def callback(obj, i):
1125             obj.callback_i = i
1126             return i
1127
1128         obj.callback_i = None
1129         obj.connect('sig-with-uint64-prop', callback)
1130         rv = obj.emit('sig-with-uint64-prop', GLib.MAXUINT64)
1131         self.assertEqual(rv, GLib.MAXUINT64)
1132         self.assertEqual(obj.callback_i, GLib.MAXUINT64)
1133
1134     def test_int64_param_from_c(self):
1135         obj = Regress.TestObj()
1136
1137         def callback(obj, i):
1138             obj.callback_i = i
1139             return i
1140
1141         obj.callback_i = None
1142
1143         obj.connect('sig-with-int64-prop', callback)
1144         obj.emit_sig_with_int64()
1145         self.assertEqual(obj.callback_i, GLib.MAXINT64)
1146
1147     def test_uint64_param_from_c(self):
1148         obj = Regress.TestObj()
1149
1150         def callback(obj, i):
1151             obj.callback_i = i
1152             return i
1153
1154         obj.callback_i = None
1155
1156         obj.connect('sig-with-uint64-prop', callback)
1157         obj.emit_sig_with_uint64()
1158         self.assertEqual(obj.callback_i, GLib.MAXUINT64)
1159
1160     def test_intarray_ret(self):
1161         obj = Regress.TestObj()
1162
1163         def callback(obj, i):
1164             obj.callback_i = i
1165             return [i, i + 1]
1166
1167         obj.callback_i = None
1168
1169         try:
1170             obj.connect('sig-with-intarray-ret', callback)
1171         except TypeError as e:
1172             # compat with g-i 1.34.x
1173             if 'unknown signal' in str(e):
1174                 return
1175             raise
1176
1177         rv = obj.emit('sig-with-intarray-ret', 42)
1178         self.assertEqual(obj.callback_i, 42)
1179         self.assertEqual(type(rv), GLib.Array)
1180         self.assertEqual(rv.len, 2)
1181
1182     @unittest.skip('https://bugzilla.gnome.org/show_bug.cgi?id=669496')
1183     def test_array_parm(self):
1184         obj = Regress.TestObj()
1185
1186         def callback(obj, arr):
1187             obj.callback_arr = arr
1188
1189         obj.connect('sig-with-array-prop', callback)
1190         obj.callback_arr = None
1191         self.assertEqual(obj.emit('sig-with-array-prop', [1, 2, GLib.MAXUINT]), None)
1192         self.assertEqual(obj.callback_arr, [1, 2, GLib.MAXUINT])
1193
1194     def test_held_struct_ref(self):
1195         held_structs = []
1196
1197         def callback(obj, struct):
1198             # The struct held by Python will become a copy after this callback exits.
1199             struct.some_int = 42
1200             struct.some_int8 = 42
1201             held_structs.append(struct)
1202
1203         struct = Regress.TestSimpleBoxedA()
1204         obj = Regress.TestObj()
1205
1206         self.assertEqual(struct.some_int, 0)
1207         self.assertEqual(struct.some_int8, 0)
1208
1209         obj.connect('test-with-static-scope-arg', callback)
1210         obj.emit('test-with-static-scope-arg', struct)
1211
1212         # The held struct will be a copy of the modified struct.
1213         self.assertEqual(len(held_structs), 1)
1214         held_struct = held_structs[0]
1215         self.assertEqual(held_struct.some_int, 42)
1216         self.assertEqual(held_struct.some_int8, 42)
1217
1218         # Boxed equality checks pointers by default.
1219         self.assertNotEqual(struct, held_struct)
1220
1221
1222 class _ConnectObjectTestBase(object):
1223     # Notes:
1224     #  - self.Object is overridden in sub-classes.
1225     #  - Numeric suffixes indicate the number of user data args passed in.
1226     Object = None
1227     SwapObject = None
1228
1229     def run_connect_test(self, emit_args, user_data, flags=0):
1230         obj = self.Object()
1231         callback_args = []
1232         swap_obj = self.SwapObject()
1233
1234         def callback(*args):
1235             callback_args.append(args)
1236             return 0
1237
1238         if flags & GObject.ConnectFlags.AFTER:
1239             connect_func = obj.connect_object_after
1240         else:
1241             connect_func = obj.connect_object
1242
1243         with capture_gi_deprecation_warnings():
1244             connect_func('sig-with-int64-prop', callback, swap_obj, *user_data)
1245         obj.emit('sig-with-int64-prop', *emit_args)
1246         self.assertEqual(len(callback_args), 1)
1247         return callback_args[0]
1248
1249     def test_0(self):
1250         obj, value = self.run_connect_test([GLib.MAXINT64], user_data=[])
1251         self.assertIsInstance(obj, self.SwapObject)
1252         self.assertEqual(value, GLib.MAXINT64)
1253
1254     def test_1(self):
1255         obj, value, data = self.run_connect_test([GLib.MAXINT64],
1256                                                  user_data=['mydata'])
1257         self.assertIsInstance(obj, self.SwapObject)
1258         self.assertEqual(value, GLib.MAXINT64)
1259         self.assertEqual(data, 'mydata')
1260
1261     def test_2(self):
1262         obj, value, data1, data2 = self.run_connect_test([GLib.MAXINT64],
1263                                                          user_data=['mydata1', 'mydata2'])
1264         self.assertIsInstance(obj, self.SwapObject)
1265         self.assertEqual(value, GLib.MAXINT64)
1266         self.assertEqual(data1, 'mydata1')
1267         self.assertEqual(data2, 'mydata2')
1268
1269     def test_after_0(self):
1270         obj, value = self.run_connect_test([GLib.MAXINT64],
1271                                            user_data=[],
1272                                            flags=GObject.ConnectFlags.AFTER)
1273         self.assertIsInstance(obj, self.SwapObject)
1274         self.assertEqual(value, GLib.MAXINT64)
1275
1276     def test_after_1(self):
1277         obj, value, data = self.run_connect_test([GLib.MAXINT64],
1278                                                  user_data=['mydata'],
1279                                                  flags=GObject.ConnectFlags.AFTER)
1280         self.assertIsInstance(obj, self.SwapObject)
1281         self.assertEqual(value, GLib.MAXINT64)
1282         self.assertEqual(data, 'mydata')
1283
1284     def test_after_2(self):
1285         obj, value, data1, data2 = self.run_connect_test([GLib.MAXINT64],
1286                                                          user_data=['mydata1', 'mydata2'],
1287                                                          flags=GObject.ConnectFlags.AFTER)
1288         self.assertIsInstance(obj, self.SwapObject)
1289         self.assertEqual(value, GLib.MAXINT64)
1290         self.assertEqual(data1, 'mydata1')
1291         self.assertEqual(data2, 'mydata2')
1292
1293
1294 class TestConnectGObjectNonIntrospected(unittest.TestCase, _ConnectObjectTestBase):
1295     # This tests connect_object with non-introspected signals
1296     # (created in Python in this case).
1297     class Object(GObject.Object):
1298         test = GObject.Signal()
1299         sig_with_int64_prop = GObject.Signal(return_type=GObject.TYPE_INT64,
1300                                              arg_types=[GObject.TYPE_INT64],
1301                                              flags=GObject.SignalFlags.RUN_LAST)
1302
1303     # Object passed for swapping is GObject based.
1304     class SwapObject(GObject.Object):
1305         pass
1306
1307
1308 class TestConnectGObjectIntrospected(unittest.TestCase, _ConnectObjectTestBase):
1309     # This tests connect_object with introspected signals brought in from Regress.
1310     Object = Regress.TestObj
1311
1312     # Object passed for swapping is GObject based.
1313     class SwapObject(GObject.Object):
1314         pass
1315
1316
1317 class TestConnectPyObjectNonIntrospected(unittest.TestCase, _ConnectObjectTestBase):
1318     # This tests connect_object with non-introspected signals
1319     # (created in Python in this case).
1320     class Object(GObject.Object):
1321         test = GObject.Signal()
1322         sig_with_int64_prop = GObject.Signal(return_type=GObject.TYPE_INT64,
1323                                              arg_types=[GObject.TYPE_INT64],
1324                                              flags=GObject.SignalFlags.RUN_LAST)
1325
1326     # Object passed for swapping is pure Python
1327     SwapObject = object
1328
1329
1330 class TestConnectPyObjectIntrospected(unittest.TestCase, _ConnectObjectTestBase):
1331     # This tests connect_object with introspected signals brought in from Regress.
1332     Object = Regress.TestObj
1333
1334     # Object passed for swapping is pure Python
1335     SwapObject = object
1336
1337
1338 class _RefCountTestBase(object):
1339     # NOTE: ref counts are always one more than expected because the getrefcount()
1340     # function adds a ref for the input argument.
1341
1342     # Sub-classes set this
1343     Object = None
1344
1345     class PyData(object):
1346         pass
1347
1348     def test_callback_ref_count_del(self):
1349         def callback(obj, value):
1350             return value // 2
1351
1352         callback_ref = weakref.ref(callback)
1353         self.assertEqual(sys.getrefcount(callback), 2)
1354
1355         obj = self.Object()
1356         obj.connect('sig-with-int64-prop', callback)
1357         self.assertEqual(sys.getrefcount(callback), 3)
1358
1359         del callback
1360         self.assertEqual(sys.getrefcount(callback_ref()), 2)
1361
1362         res = obj.emit('sig-with-int64-prop', 42)
1363         self.assertEqual(res, 21)
1364         self.assertEqual(sys.getrefcount(callback_ref), 2)
1365
1366         del obj
1367         self.assertIsNone(callback_ref())
1368
1369     def test_callback_ref_count_disconnect(self):
1370         def callback(obj, value):
1371             return value // 2
1372
1373         callback_ref = weakref.ref(callback)
1374         self.assertEqual(sys.getrefcount(callback), 2)
1375
1376         obj = self.Object()
1377         handler_id = obj.connect('sig-with-int64-prop', callback)
1378         self.assertEqual(sys.getrefcount(callback), 3)
1379
1380         del callback
1381         self.assertEqual(sys.getrefcount(callback_ref()), 2)
1382
1383         res = obj.emit('sig-with-int64-prop', 42)
1384         self.assertEqual(res, 21)
1385         self.assertEqual(sys.getrefcount(callback_ref), 2)
1386
1387         obj.disconnect(handler_id)
1388         self.assertIsNone(callback_ref())
1389
1390     def test_callback_ref_count_disconnect_by_func(self):
1391         def callback(obj, value):
1392             return value // 2
1393
1394         callback_ref = weakref.ref(callback)
1395         self.assertEqual(sys.getrefcount(callback), 2)
1396
1397         obj = self.Object()
1398         obj.connect('sig-with-int64-prop', callback)
1399         self.assertEqual(sys.getrefcount(callback), 3)
1400
1401         del callback
1402         self.assertEqual(sys.getrefcount(callback_ref()), 2)
1403
1404         res = obj.emit('sig-with-int64-prop', 42)
1405         self.assertEqual(res, 21)
1406         self.assertEqual(sys.getrefcount(callback_ref), 2)
1407
1408         obj.disconnect_by_func(callback_ref())
1409         self.assertIsNone(callback_ref())
1410
1411     def test_user_data_ref_count(self):
1412         def callback(obj, value, data):
1413             return value // 2
1414
1415         data = self.PyData()
1416         data_ref = weakref.ref(data)
1417         self.assertEqual(sys.getrefcount(data), 2)
1418
1419         obj = self.Object()
1420         obj.connect('sig-with-int64-prop', callback, data)
1421         self.assertEqual(sys.getrefcount(data), 3)
1422
1423         del data
1424         self.assertEqual(sys.getrefcount(data_ref()), 2)
1425
1426         res = obj.emit('sig-with-int64-prop', 42)
1427         self.assertEqual(res, 21)
1428         self.assertEqual(sys.getrefcount(data_ref()), 2)
1429
1430         del obj
1431         self.assertIsNone(data_ref())
1432
1433     @unittest.expectedFailure  # https://bugzilla.gnome.org/show_bug.cgi?id=688064
1434     def test_object_ref_count(self):
1435         # connect_object() should only weakly reference the object passed in
1436         # and auto-disconnect the signal when the object is destroyed.
1437         def callback(data, value):
1438             return value // 2
1439
1440         data = GObject.Object()
1441         data_ref = weakref.ref(data)
1442         self.assertEqual(sys.getrefcount(data), 2)
1443
1444         obj = self.Object()
1445         handler_id = obj.connect_object('sig-with-int64-prop', callback, data)
1446         self.assertEqual(sys.getrefcount(data), 2)
1447
1448         res = obj.emit('sig-with-int64-prop', 42)
1449         self.assertEqual(res, 21)
1450         self.assertEqual(sys.getrefcount(data), 2)
1451
1452         del data
1453
1454         self.assertIsNone(data_ref())
1455         self.assertFalse(obj.handler_is_connected(handler_id))
1456
1457
1458 class TestRefCountsNonIntrospected(unittest.TestCase, _RefCountTestBase):
1459     class Object(GObject.Object):
1460         sig_with_int64_prop = GObject.Signal(return_type=GObject.TYPE_INT64,
1461                                              arg_types=[GObject.TYPE_INT64],
1462                                              flags=GObject.SignalFlags.RUN_LAST)
1463
1464
1465 class TestRefCountsIntrospected(unittest.TestCase, _RefCountTestBase):
1466     Object = Regress.TestObj
1467
1468
1469 if __name__ == '__main__':
1470     unittest.main()