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