Imported Upstream version 3.13.3
[platform/upstream/pygobject2.git] / tests / test_signal.py
1 # -*- Mode: Python -*-
2
3 import gc
4 import unittest
5 import sys
6
7 from gi.repository import GObject, GLib
8 from gi import _signalhelper as signalhelper
9 import testhelper
10 from compathelper import _long
11
12 try:
13     import cairo
14     cairo  # PyFlakes
15     from gi.repository import Regress
16     has_cairo = True
17 except ImportError:
18     has_cairo = False
19
20
21 class C(GObject.GObject):
22     __gsignals__ = {'my_signal': (GObject.SignalFlags.RUN_FIRST, None,
23                                   (GObject.TYPE_INT,))}
24
25     def do_my_signal(self, arg):
26         self.arg = arg
27
28
29 class D(C):
30     def do_my_signal(self, arg2):
31         self.arg2 = arg2
32         C.do_my_signal(self, arg2)
33
34
35 class TestSignalCreation(unittest.TestCase):
36     # Bug 540376.
37     def test_illegals(self):
38         self.assertRaises(TypeError, lambda: GObject.signal_new('test',
39                                                                 None,
40                                                                 0,
41                                                                 None,
42                                                                 (GObject.TYPE_LONG,)))
43
44
45 class TestChaining(unittest.TestCase):
46     def setUp(self):
47         self.inst = C()
48         self.inst.connect("my_signal", self.my_signal_handler_cb, 1, 2, 3)
49
50     def my_signal_handler_cb(self, *args):
51         assert len(args) == 5
52         assert isinstance(args[0], C)
53         assert args[0] == self.inst
54
55         assert isinstance(args[1], int)
56         assert args[1] == 42
57
58         assert args[2:] == (1, 2, 3)
59
60     def test_chaining(self):
61         self.inst.emit("my_signal", 42)
62         assert self.inst.arg == 42
63
64     def test_chaining2(self):
65         inst2 = D()
66         inst2.emit("my_signal", 44)
67         assert inst2.arg == 44
68         assert inst2.arg2 == 44
69
70 # This is for bug 153718
71
72
73 class TestGSignalsError(unittest.TestCase):
74     def test_invalid_type(self, *args):
75         def foo():
76             class Foo(GObject.GObject):
77                 __gsignals__ = None
78         self.assertRaises(TypeError, foo)
79         gc.collect()
80
81     def test_invalid_name(self, *args):
82         def foo():
83             class Foo(GObject.GObject):
84                 __gsignals__ = {'not-exists': 'override'}
85         # do not stumble over the warning thrown by GLib
86         old_mask = GLib.log_set_always_fatal(GLib.LogLevelFlags.LEVEL_CRITICAL |
87                                              GLib.LogLevelFlags.LEVEL_ERROR)
88         try:
89             self.assertRaises(TypeError, foo)
90         finally:
91             GLib.log_set_always_fatal(old_mask)
92         gc.collect()
93
94
95 class TestGPropertyError(unittest.TestCase):
96     def test_invalid_type(self, *args):
97         def foo():
98             class Foo(GObject.GObject):
99                 __gproperties__ = None
100         self.assertRaises(TypeError, foo)
101         gc.collect()
102
103     def test_invalid_name(self, *args):
104         def foo():
105             class Foo(GObject.GObject):
106                 __gproperties__ = {None: None}
107
108         self.assertRaises(TypeError, foo)
109         gc.collect()
110
111
112 class TestList(unittest.TestCase):
113     def test_list_names(self):
114         self.assertEqual(GObject.signal_list_names(C), ('my-signal',))
115
116
117 def my_accumulator(ihint, return_accu, handler_return, user_data):
118     """An accumulator that stops emission when the sum of handler
119     returned values reaches 3"""
120     assert user_data == "accum data"
121     if return_accu >= 3:
122         return False, return_accu
123     return True, return_accu + handler_return
124
125
126 class Foo(GObject.GObject):
127     my_acc_signal = GObject.Signal(return_type=GObject.TYPE_INT,
128                                    flags=GObject.SignalFlags.RUN_LAST,
129                                    accumulator=my_accumulator,
130                                    accu_data="accum data")
131
132     my_other_acc_signal = GObject.Signal(return_type=GObject.TYPE_BOOLEAN,
133                                          flags=GObject.SignalFlags.RUN_LAST,
134                                          accumulator=GObject.signal_accumulator_true_handled)
135
136     my_acc_first_wins = GObject.Signal(return_type=GObject.TYPE_BOOLEAN,
137                                        flags=GObject.SignalFlags.RUN_LAST,
138                                        accumulator=GObject.signal_accumulator_first_wins)
139
140
141 class TestAccumulator(unittest.TestCase):
142
143     def test_accumulator(self):
144         inst = Foo()
145         inst.my_acc_signal.connect(lambda obj: 1)
146         inst.my_acc_signal.connect(lambda obj: 2)
147         # the value returned in the following handler will not be
148         # considered, because at this point the accumulator already
149         # reached its limit.
150         inst.my_acc_signal.connect(lambda obj: 3)
151         retval = inst.my_acc_signal.emit()
152         self.assertEqual(retval, 3)
153
154     def test_accumulator_true_handled(self):
155         inst = Foo()
156         inst.my_other_acc_signal.connect(self._true_handler1)
157         inst.my_other_acc_signal.connect(self._true_handler2)
158         # the following handler will not be called because handler2
159         # returns True, so it should stop the emission.
160         inst.my_other_acc_signal.connect(self._true_handler3)
161         self.__true_val = None
162         inst.my_other_acc_signal.emit()
163         self.assertEqual(self.__true_val, 2)
164
165     def test_accumulator_first_wins(self):
166         # First signal hit will always win
167         inst = Foo()
168         inst.my_acc_first_wins.connect(self._true_handler3)
169         inst.my_acc_first_wins.connect(self._true_handler1)
170         inst.my_acc_first_wins.connect(self._true_handler2)
171         self.__true_val = None
172         inst.my_acc_first_wins.emit()
173         self.assertEqual(self.__true_val, 3)
174
175     def _true_handler1(self, obj):
176         self.__true_val = 1
177         return False
178
179     def _true_handler2(self, obj):
180         self.__true_val = 2
181         return True
182
183     def _true_handler3(self, obj):
184         self.__true_val = 3
185         return False
186
187
188 class E(GObject.GObject):
189     __gsignals__ = {'signal': (GObject.SignalFlags.RUN_FIRST, None,
190                                ())}
191
192     # Property used to test detailed signal
193     prop = GObject.Property(type=int, default=0)
194
195     def __init__(self):
196         GObject.GObject.__init__(self)
197         self.status = 0
198
199     def do_signal(self):
200         assert self.status == 0
201         self.status = 1
202
203
204 class F(GObject.GObject):
205     __gsignals__ = {'signal': (GObject.SignalFlags.RUN_FIRST, None,
206                                ())}
207
208     def __init__(self):
209         GObject.GObject.__init__(self)
210         self.status = 0
211
212     def do_signal(self):
213         self.status += 1
214
215
216 class TestEmissionHook(unittest.TestCase):
217     def test_add(self):
218         self.hook = True
219         e = E()
220         e.connect('signal', self._callback)
221         GObject.add_emission_hook(E, "signal", self._emission_hook)
222         e.emit('signal')
223         self.assertEqual(e.status, 3)
224
225     def test_remove(self):
226         self.hook = False
227         e = E()
228         e.connect('signal', self._callback)
229         hook_id = GObject.add_emission_hook(E, "signal", self._emission_hook)
230         GObject.remove_emission_hook(E, "signal", hook_id)
231         e.emit('signal')
232         self.assertEqual(e.status, 3)
233
234     def _emission_hook(self, e):
235         self.assertEqual(e.status, 1)
236         e.status = 2
237
238     def _callback(self, e):
239         if self.hook:
240             self.assertEqual(e.status, 2)
241         else:
242             self.assertEqual(e.status, 1)
243         e.status = 3
244
245     def test_callback_return_false(self):
246         self.hook = False
247         obj = F()
248
249         def _emission_hook(obj):
250             obj.status += 1
251             return False
252         GObject.add_emission_hook(obj, "signal", _emission_hook)
253         obj.emit('signal')
254         obj.emit('signal')
255         self.assertEqual(obj.status, 3)
256
257     def test_callback_return_true(self):
258         self.hook = False
259         obj = F()
260
261         def _emission_hook(obj):
262             obj.status += 1
263             return True
264         hook_id = GObject.add_emission_hook(obj, "signal", _emission_hook)
265         obj.emit('signal')
266         obj.emit('signal')
267         GObject.remove_emission_hook(obj, "signal", hook_id)
268         self.assertEqual(obj.status, 4)
269
270     def test_callback_return_true_but_remove(self):
271         self.hook = False
272         obj = F()
273
274         def _emission_hook(obj):
275             obj.status += 1
276             return True
277         hook_id = GObject.add_emission_hook(obj, "signal", _emission_hook)
278         obj.emit('signal')
279         GObject.remove_emission_hook(obj, "signal", hook_id)
280         obj.emit('signal')
281         self.assertEqual(obj.status, 3)
282
283
284 class TestMatching(unittest.TestCase):
285     class Object(GObject.Object):
286         status = 0
287         prop = GObject.Property(type=int, default=0)
288
289         @GObject.Signal()
290         def my_signal(self):
291             pass
292
293     @unittest.expectedFailure  # https://bugzilla.gnome.org/show_bug.cgi?id=692918
294     def test_signal_handler_block_matching(self):
295         def dummy(*args):
296             "Hack to work around: "
297
298         def foo(obj):
299             obj.status += 1
300
301         obj = self.Object()
302         handler_id = GObject.signal_connect_closure(obj, 'my-signal', foo, after=False)
303         handler_id
304
305         self.assertEqual(obj.status, 0)
306         obj.emit('my-signal')
307         self.assertEqual(obj.status, 1)
308
309         # Blocking by match criteria disables the foo callback
310         signal_id, detail = GObject.signal_parse_name('my-signal', obj, True)
311         count = GObject.signal_handlers_block_matched(obj,
312                                                       GObject.SignalMatchType.ID | GObject.SignalMatchType.CLOSURE,
313                                                       signal_id=signal_id, detail=detail,
314                                                       closure=foo, func=dummy, data=dummy)
315         self.assertEqual(count, 1)
316         obj.emit('my-signal')
317         self.assertEqual(obj.status, 1)
318
319         # Unblocking by the same match criteria allows callback to work again
320         count = GObject.signal_handlers_unblock_matched(obj,
321                                                         GObject.SignalMatchType.ID | GObject.SignalMatchType.CLOSURE,
322                                                         signal_id=signal_id, detail=detail,
323                                                         closure=foo, func=dummy, data=dummy)
324         self.assertEqual(count, 1)
325         obj.emit('my-signal')
326         self.assertEqual(obj.status, 2)
327
328         # Disconnecting by match criteria completely removes the handler
329         count = GObject.signal_handlers_disconnect_matched(obj,
330                                                            GObject.SignalMatchType.ID | GObject.SignalMatchType.CLOSURE,
331                                                            signal_id=signal_id, detail=detail,
332                                                            closure=foo, func=dummy, data=dummy)
333         self.assertEqual(count, 1)
334         obj.emit('my-signal')
335         self.assertEqual(obj.status, 2)
336
337     def test_signal_handler_find(self):
338         def foo(obj):
339             obj.status += 1
340
341         obj = self.Object()
342         handler_id = GObject.signal_connect_closure(obj, 'my-signal', foo, after=False)
343
344         signal_id, detail = GObject.signal_parse_name('my-signal', obj, True)
345         found_id = GObject.signal_handler_find(obj,
346                                                GObject.SignalMatchType.ID,
347                                                signal_id=signal_id, detail=detail,
348                                                closure=None, func=0, data=0)
349         self.assertEqual(handler_id, found_id)
350
351
352 class TestClosures(unittest.TestCase):
353     def setUp(self):
354         self.count = 0
355         self.emission_stopped = False
356         self.emission_error = False
357         self.handler_pending = False
358
359     def _callback_handler_pending(self, e):
360         signal_id, detail = GObject.signal_parse_name('signal', e, True)
361         self.handler_pending = GObject.signal_has_handler_pending(e, signal_id, detail,
362                                                                   may_be_blocked=False)
363
364     def _callback(self, e):
365         self.count += 1
366
367     def _callback_stop_emission(self, obj, prop, stop_it):
368         if stop_it:
369             obj.stop_emission_by_name('notify::prop')
370             self.emission_stopped = True
371         else:
372             self.count += 1
373
374     def _callback_invalid_stop_emission_name(self, obj, prop):
375         # We expect a GLib warning but there currently is no way to test that
376         # This can at least make sure we don't crash
377         old_mask = GLib.log_set_always_fatal(GLib.LogLevelFlags.LEVEL_CRITICAL |
378                                              GLib.LogLevelFlags.LEVEL_ERROR)
379         try:
380             obj.stop_emission_by_name('notasignal::baddetail')
381         finally:
382             GLib.log_set_always_fatal(old_mask)
383             self.emission_error = True
384
385     def test_disconnect_by_func(self):
386         e = E()
387         e.connect('signal', self._callback)
388         e.disconnect_by_func(self._callback)
389         e.emit('signal')
390         self.assertEqual(self.count, 0)
391
392     def test_disconnect(self):
393         e = E()
394         handler_id = e.connect('signal', self._callback)
395         self.assertTrue(e.handler_is_connected(handler_id))
396         e.disconnect(handler_id)
397         e.emit('signal')
398         self.assertEqual(self.count, 0)
399         self.assertFalse(e.handler_is_connected(handler_id))
400
401     def test_stop_emission_by_name(self):
402         e = E()
403
404         # Sandwich a callback that stops emission in between a callback that increments
405         e.connect('notify::prop', self._callback_stop_emission, False)
406         e.connect('notify::prop', self._callback_stop_emission, True)
407         e.connect('notify::prop', self._callback_stop_emission, False)
408
409         e.set_property('prop', 1234)
410         self.assertEqual(e.get_property('prop'), 1234)
411         self.assertEqual(self.count, 1)
412         self.assertTrue(self.emission_stopped)
413
414     def test_stop_emission_by_name_error(self):
415         e = E()
416
417         e.connect('notify::prop', self._callback_invalid_stop_emission_name)
418         e.set_property('prop', 1234)
419         self.assertTrue(self.emission_error)
420
421     def test_handler_block(self):
422         e = E()
423         e.connect('signal', self._callback)
424         e.handler_block_by_func(self._callback)
425         e.emit('signal')
426         self.assertEqual(self.count, 0)
427
428     def test_handler_unblock(self):
429         e = E()
430         handler_id = e.connect('signal', self._callback)
431         e.handler_block(handler_id)
432         e.handler_unblock_by_func(self._callback)
433         e.emit('signal')
434         self.assertEqual(self.count, 1)
435
436     def test_handler_block_method(self):
437         # Filed as #375589
438         class A:
439             def __init__(self):
440                 self.a = 0
441
442             def callback(self, o):
443                 self.a = 1
444                 o.handler_block_by_func(self.callback)
445
446         inst = A()
447         e = E()
448         e.connect("signal", inst.callback)
449         e.emit('signal')
450         self.assertEqual(inst.a, 1)
451         gc.collect()
452
453     def test_gstring(self):
454         class C(GObject.GObject):
455             __gsignals__ = {'my_signal': (GObject.SignalFlags.RUN_LAST, GObject.TYPE_GSTRING,
456                                           (GObject.TYPE_GSTRING,))}
457
458             def __init__(self, test):
459                 GObject.GObject.__init__(self)
460                 self.test = test
461
462             def do_my_signal(self, data):
463                 self.data = data
464                 self.test.assertEqual(len(data), 3)
465                 return ''.join([data[2], data[1], data[0]])
466         c = C(self)
467         data = c.emit("my_signal", "\01\00\02")
468         self.assertEqual(data, "\02\00\01")
469
470     def test_handler_pending(self):
471         obj = F()
472         obj.connect('signal', self._callback_handler_pending)
473         obj.connect('signal', self._callback)
474
475         self.assertEqual(self.count, 0)
476         self.assertEqual(self.handler_pending, False)
477
478         obj.emit('signal')
479         self.assertEqual(self.count, 1)
480         self.assertEqual(self.handler_pending, True)
481
482     def test_signal_handlers_destroy(self):
483         obj = F()
484         obj.connect('signal', self._callback)
485         obj.connect('signal', self._callback)
486         obj.connect('signal', self._callback)
487
488         obj.emit('signal')
489         self.assertEqual(self.count, 3)
490
491         # count should remain at 3 after all handlers are destroyed
492         GObject.signal_handlers_destroy(obj)
493         obj.emit('signal')
494         self.assertEqual(self.count, 3)
495
496
497 class SigPropClass(GObject.GObject):
498     __gsignals__ = {'my_signal': (GObject.SignalFlags.RUN_FIRST, None,
499                                   (GObject.TYPE_INT,))}
500
501     __gproperties__ = {
502         'foo': (str, None, None, '', GObject.PARAM_WRITABLE | GObject.PARAM_CONSTRUCT),
503         }
504
505     signal_emission_failed = False
506
507     def do_my_signal(self, arg):
508         self.arg = arg
509
510     def do_set_property(self, pspec, value):
511         if pspec.name == 'foo':
512             self._foo = value
513         else:
514             raise AttributeError('unknown property %s' % pspec.name)
515         try:
516             self.emit("my-signal", 1)
517         except TypeError:
518             self.signal_emission_failed = True
519
520
521 class TestSigProp(unittest.TestCase):
522     def test_emit_in_property_setter(self):
523         obj = SigPropClass()
524         self.assertFalse(obj.signal_emission_failed)
525
526
527 class CM(GObject.GObject):
528     __gsignals__ = dict(
529         test1=(GObject.SignalFlags.RUN_FIRST, None, ()),
530         test2=(GObject.SignalFlags.RUN_LAST, None, (str,)),
531         test3=(GObject.SignalFlags.RUN_LAST, int, (GObject.TYPE_DOUBLE,)),
532         test4=(GObject.SignalFlags.RUN_FIRST, None,
533                (bool, _long, GObject.TYPE_FLOAT, GObject.TYPE_DOUBLE, int,
534                 GObject.TYPE_UINT, GObject.TYPE_ULONG)),
535         test_float=(GObject.SignalFlags.RUN_LAST, GObject.TYPE_FLOAT, (GObject.TYPE_FLOAT,)),
536         test_double=(GObject.SignalFlags.RUN_LAST, GObject.TYPE_DOUBLE, (GObject.TYPE_DOUBLE,)),
537         test_int64=(GObject.SignalFlags.RUN_LAST, GObject.TYPE_INT64, (GObject.TYPE_INT64,)),
538         test_string=(GObject.SignalFlags.RUN_LAST, str, (str,)),
539         test_object=(GObject.SignalFlags.RUN_LAST, object, (object,)),
540         test_paramspec=(GObject.SignalFlags.RUN_LAST, GObject.ParamSpec, ()),
541         test_paramspec_in=(GObject.SignalFlags.RUN_LAST, GObject.ParamSpec, (GObject.ParamSpec, )),
542         test_gvalue=(GObject.SignalFlags.RUN_LAST, GObject.Value, (GObject.Value,)),
543         test_gvalue_ret=(GObject.SignalFlags.RUN_LAST, GObject.Value, (GObject.TYPE_GTYPE,)),
544     )
545
546     testprop = GObject.Property(type=int)
547
548
549 class _TestCMarshaller:
550     def setUp(self):
551         self.obj = CM()
552         testhelper.connectcallbacks(self.obj)
553
554     def test_test1(self):
555         self.obj.emit("test1")
556
557     def test_test2(self):
558         self.obj.emit("test2", "string")
559
560     def test_test3(self):
561         rv = self.obj.emit("test3", 42.0)
562         self.assertEqual(rv, 20)
563
564     def test_test4(self):
565         self.obj.emit("test4", True, _long(10), 3.14, 1.78, 20, _long(30), _long(31))
566
567     def test_float(self):
568         rv = self.obj.emit("test-float", 1.234)
569         self.assertTrue(rv >= 1.233999 and rv <= 1.2400001, rv)
570
571     def test_double(self):
572         rv = self.obj.emit("test-double", 1.234)
573         self.assertEqual(rv, 1.234)
574
575     def test_int64(self):
576         rv = self.obj.emit("test-int64", 102030405)
577         self.assertEqual(rv, 102030405)
578
579         rv = self.obj.emit("test-int64", GObject.G_MAXINT64)
580         self.assertEqual(rv, GObject.G_MAXINT64 - 1)
581
582         rv = self.obj.emit("test-int64", GObject.G_MININT64)
583         self.assertEqual(rv, GObject.G_MININT64)
584
585     def test_string(self):
586         rv = self.obj.emit("test-string", "str")
587         self.assertEqual(rv, "str")
588
589     def test_object(self):
590         rv = self.obj.emit("test-object", self)
591         self.assertEqual(rv, self)
592
593     def test_paramspec(self):
594         rv = self.obj.emit("test-paramspec")
595         self.assertEqual(rv.name, "test-param")
596         self.assertEqual(rv.nick, "test")
597
598     @unittest.skipUnless(hasattr(GObject, 'param_spec_boolean'),
599                          'too old gobject-introspection')
600     def test_paramspec_in(self):
601         rv = GObject.param_spec_boolean('mybool', 'test-bool', 'do something',
602                                         True, GObject.ParamFlags.READABLE)
603
604         rv2 = self.obj.emit("test-paramspec-in", rv)
605         self.assertEqual(type(rv), type(rv2))
606         self.assertEqual(rv2.name, "mybool")
607         self.assertEqual(rv2.nick, "test-bool")
608
609     def test_C_paramspec(self):
610         self.notify_called = False
611
612         def cb_notify(obj, prop):
613             self.notify_called = True
614             self.assertEqual(obj, self.obj)
615             self.assertEqual(prop.name, "testprop")
616
617         self.obj.connect("notify", cb_notify)
618         self.obj.set_property("testprop", 42)
619         self.assertTrue(self.notify_called)
620
621     def test_gvalue(self):
622         # implicit int
623         rv = self.obj.emit("test-gvalue", 42)
624         self.assertEqual(rv, 42)
625
626         # explicit float
627         v = GObject.Value(GObject.TYPE_FLOAT, 1.234)
628         rv = self.obj.emit("test-gvalue", v)
629         self.assertAlmostEqual(rv, 1.234, 4)
630
631         # implicit float
632         rv = self.obj.emit("test-gvalue", 1.234)
633         self.assertAlmostEqual(rv, 1.234, 4)
634
635         # explicit int64
636         v = GObject.Value(GObject.TYPE_INT64, GObject.G_MAXINT64)
637         rv = self.obj.emit("test-gvalue", v)
638         self.assertEqual(rv, GObject.G_MAXINT64)
639
640         # explicit uint64
641         v = GObject.Value(GObject.TYPE_UINT64, GObject.G_MAXUINT64)
642         rv = self.obj.emit("test-gvalue", v)
643         self.assertEqual(rv, GObject.G_MAXUINT64)
644
645     @unittest.expectedFailure  # https://bugzilla.gnome.org/show_bug.cgi?id=705291
646     def test_gvalue_implicit_int64(self):
647         # implicit int64
648         rv = self.obj.emit("test-gvalue", GObject.G_MAXINT64)
649         self.assertEqual(rv, GObject.G_MAXINT64)
650
651         # implicit uint64
652         rv = self.obj.emit("test-gvalue", GObject.G_MAXUINT64)
653         self.assertEqual(rv, GObject.G_MAXUINT64)
654
655     def test_gvalue_ret(self):
656         self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_INT),
657                          GObject.G_MAXINT)
658         self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_UINT),
659                          GObject.G_MAXUINT)
660         self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_INT64),
661                          GObject.G_MAXINT64)
662         self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_UINT64),
663                          GObject.G_MAXUINT64)
664         self.assertEqual(self.obj.emit("test-gvalue-ret", GObject.TYPE_STRING),
665                          "hello")
666
667 if 'generic-c-marshaller' in GObject.features:
668     class TestCMarshaller(_TestCMarshaller, unittest.TestCase):
669         pass
670 else:
671     print()
672     print('** WARNING: LIBFFI disabled, not testing')
673     print()
674
675 # Test for 374653
676
677
678 class TestPyGValue(unittest.TestCase):
679     def test_none_null_boxed_conversion(self):
680         class C(GObject.GObject):
681             __gsignals__ = dict(my_boxed_signal=(
682                 GObject.SignalFlags.RUN_LAST,
683                 GObject.TYPE_STRV, ()))
684
685         obj = C()
686         obj.connect('my-boxed-signal', lambda obj: None)
687         sys.last_type = None
688         obj.emit('my-boxed-signal')
689         assert not sys.last_type
690
691
692 class TestSignalDecorator(unittest.TestCase):
693     class Decorated(GObject.GObject):
694         value = 0
695
696         @GObject.Signal
697         def pushed(self):
698             """this will push"""
699             self.value += 1
700
701         @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
702         def pulled(self):
703             self.value -= 1
704
705         stomped = GObject.Signal('stomped', arg_types=(int,), doc='this will stomp')
706         unnamed = GObject.Signal()
707
708     class DecoratedOverride(GObject.GObject):
709         overridden_closure_called = False
710         notify_called = False
711         value = GObject.Property(type=int, default=0)
712
713         @GObject.SignalOverride
714         def notify(self, *args, **kargs):
715             self.overridden_closure_called = True
716
717         def on_notify(self, obj, prop):
718             self.notify_called = True
719
720     def setUp(self):
721         self.unnamedCalled = False
722
723     def onUnnamed(self, obj):
724         self.unnamedCalled = True
725
726     def test_get_signal_args(self):
727         self.assertEqual(self.Decorated.pushed.get_signal_args(),
728                          (GObject.SignalFlags.RUN_FIRST, None, tuple(), None, None))
729         self.assertEqual(self.Decorated.pulled.get_signal_args(),
730                          (GObject.SignalFlags.RUN_LAST, None, tuple(), None, None))
731         self.assertEqual(self.Decorated.stomped.get_signal_args(),
732                          (GObject.SignalFlags.RUN_FIRST, None, (int,), None, None))
733
734     def test_closures_called(self):
735         decorated = self.Decorated()
736         self.assertEqual(decorated.value, 0)
737         decorated.pushed.emit()
738         self.assertEqual(decorated.value, 1)
739         decorated.pulled.emit()
740         self.assertEqual(decorated.value, 0)
741
742     def test_signal_copy(self):
743         blah = self.Decorated.stomped.copy('blah')
744         self.assertEqual(str(blah), blah)
745         self.assertEqual(blah.func, self.Decorated.stomped.func)
746         self.assertEqual(blah.flags, self.Decorated.stomped.flags)
747         self.assertEqual(blah.return_type, self.Decorated.stomped.return_type)
748         self.assertEqual(blah.arg_types, self.Decorated.stomped.arg_types)
749         self.assertEqual(blah.__doc__, self.Decorated.stomped.__doc__)
750
751     def test_doc_string(self):
752         # Test the two techniques for setting doc strings on the signals
753         # class variables, through the "doc" keyword or as the getter doc string.
754         self.assertEqual(self.Decorated.stomped.__doc__, 'this will stomp')
755         self.assertEqual(self.Decorated.pushed.__doc__, 'this will push')
756
757     def test_unnamed_signal_gets_named(self):
758         self.assertEqual(str(self.Decorated.unnamed), 'unnamed')
759
760     def test_unnamed_signal_gets_called(self):
761         obj = self.Decorated()
762         obj.connect('unnamed', self.onUnnamed)
763         self.assertEqual(self.unnamedCalled, False)
764         obj.emit('unnamed')
765         self.assertEqual(self.unnamedCalled, True)
766
767     def NOtest_overridden_signal(self):
768         # Test that the pushed signal is called in with super and the override
769         # which should both increment the "value" to 3
770         obj = self.DecoratedOverride()
771         obj.connect("notify", obj.on_notify)
772         self.assertEqual(obj.value, 0)
773         obj.value = 1
774         self.assertEqual(obj.value, 1)
775         self.assertTrue(obj.overridden_closure_called)
776         self.assertTrue(obj.notify_called)
777
778
779 class TestSignalConnectors(unittest.TestCase):
780     class CustomButton(GObject.GObject):
781         on_notify_called = False
782         value = GObject.Property(type=int)
783
784         @GObject.Signal(arg_types=(int,))
785         def clicked(self, value):
786             self.value = value
787
788     def setUp(self):
789         self.obj = None
790         self.value = None
791
792     def on_clicked(self, obj, value):
793         self.obj = obj
794         self.value = value
795
796     def test_signal_notify(self):
797         def on_notify(obj, param):
798             obj.on_notify_called = True
799
800         obj = self.CustomButton()
801         obj.connect('notify', on_notify)
802         self.assertFalse(obj.on_notify_called)
803         obj.notify('value')
804         self.assertTrue(obj.on_notify_called)
805
806     def test_signal_emit(self):
807         # standard callback connection with different forms of emit.
808         obj = self.CustomButton()
809         obj.connect('clicked', self.on_clicked)
810
811         # vanilla
812         obj.emit('clicked', 1)
813         self.assertEqual(obj.value, 1)
814         self.assertEqual(obj, self.obj)
815         self.assertEqual(self.value, 1)
816
817         # using class signal as param
818         self.obj = None
819         self.value = None
820         obj.emit(self.CustomButton.clicked, 1)
821         self.assertEqual(obj, self.obj)
822         self.assertEqual(self.value, 1)
823
824         # using bound signal as param
825         self.obj = None
826         self.value = None
827         obj.emit(obj.clicked, 1)
828         self.assertEqual(obj, self.obj)
829         self.assertEqual(self.value, 1)
830
831         # using bound signal with emit
832         self.obj = None
833         self.value = None
834         obj.clicked.emit(1)
835         self.assertEqual(obj, self.obj)
836         self.assertEqual(self.value, 1)
837
838     def test_signal_class_connect(self):
839         obj = self.CustomButton()
840         obj.connect(self.CustomButton.clicked, self.on_clicked)
841         obj.emit('clicked', 2)
842         self.assertEqual(obj, self.obj)
843         self.assertEqual(self.value, 2)
844
845     def test_signal_bound_connect(self):
846         obj = self.CustomButton()
847         obj.clicked.connect(self.on_clicked)
848         obj.emit('clicked', 3)
849         self.assertEqual(obj, self.obj)
850         self.assertEqual(self.value, 3)
851
852
853 class TestInstallSignals(unittest.TestCase):
854     # These tests only test how signalhelper.install_signals works
855     # with the __gsignals__ dict and therefore does not need to use
856     # GObject as a base class because that would automatically call
857     # install_signals within the meta-class.
858     class Base(object):
859         __gsignals__ = {'test': (0, None, tuple())}
860
861     class Sub1(Base):
862         pass
863
864     class Sub2(Base):
865         @GObject.Signal
866         def sub2test(self):
867             pass
868
869     def setUp(self):
870         self.assertEqual(len(self.Base.__gsignals__), 1)
871         signalhelper.install_signals(self.Base)
872         self.assertEqual(len(self.Base.__gsignals__), 1)
873
874     def test_subclass_gets_empty_gsignals_dict(self):
875         # Installing signals will add the __gsignals__ dict to a class
876         # if it doesn't already exists.
877         self.assertFalse('__gsignals__' in self.Sub1.__dict__)
878         signalhelper.install_signals(self.Sub1)
879         self.assertTrue('__gsignals__' in self.Sub1.__dict__)
880         # Sub1 should only contain an empty signals dict, this tests:
881         # https://bugzilla.gnome.org/show_bug.cgi?id=686496
882         self.assertEqual(self.Sub1.__dict__['__gsignals__'], {})
883
884     def test_subclass_with_decorator_gets_gsignals_dict(self):
885         self.assertFalse('__gsignals__' in self.Sub2.__dict__)
886         signalhelper.install_signals(self.Sub2)
887         self.assertTrue('__gsignals__' in self.Sub2.__dict__)
888         self.assertEqual(len(self.Base.__gsignals__), 1)
889         self.assertEqual(len(self.Sub2.__gsignals__), 1)
890         self.assertTrue('sub2test' in self.Sub2.__gsignals__)
891
892         # Make sure the vfunc was added
893         self.assertTrue(hasattr(self.Sub2, 'do_sub2test'))
894
895
896 # For this test to work with both python2 and 3 we need to dynamically
897 # exec the given code due to the new syntax causing an error in python 2.
898 annotated_class_code = """
899 class AnnotatedSignalClass(GObject.GObject):
900     @GObject.Signal
901     def sig1(self, a:int, b:float):
902         pass
903
904     @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST)
905     def sig2_with_return(self, a:int, b:float) -> str:
906         return "test"
907 """
908
909
910 @unittest.skipUnless(sys.version_info >= (3, 0),
911                      'Argument annotations require Python 3')
912 class TestPython3Signals(unittest.TestCase):
913     AnnotatedClass = None
914
915     def setUp(self):
916         exec(annotated_class_code, globals(), globals())
917         self.assertTrue('AnnotatedSignalClass' in globals())
918         self.AnnotatedClass = globals()['AnnotatedSignalClass']
919
920     def test_annotations(self):
921         self.assertEqual(signalhelper.get_signal_annotations(self.AnnotatedClass.sig1.func),
922                          (None, (int, float)))
923         self.assertEqual(signalhelper.get_signal_annotations(self.AnnotatedClass.sig2_with_return.func),
924                          (str, (int, float)))
925
926         self.assertEqual(self.AnnotatedClass.sig2_with_return.get_signal_args(),
927                          (GObject.SignalFlags.RUN_LAST, str, (int, float), None, None))
928         self.assertEqual(self.AnnotatedClass.sig2_with_return.arg_types,
929                          (int, float))
930         self.assertEqual(self.AnnotatedClass.sig2_with_return.return_type,
931                          str)
932
933     def test_emit_return(self):
934         obj = self.AnnotatedClass()
935         self.assertEqual(obj.sig2_with_return.emit(1, 2.0),
936                          'test')
937
938
939 class TestSignalModuleLevelFunctions(unittest.TestCase):
940     def test_signal_list_ids_with_invalid_type(self):
941         with self.assertRaisesRegex(TypeError, 'type must be instantiable or an interface.*'):
942             GObject.signal_list_ids(GObject.TYPE_INVALID)
943
944     def test_signal_list_ids(self):
945         with self.assertRaisesRegex(TypeError, 'type must be instantiable or an interface.*'):
946             GObject.signal_list_ids(GObject.TYPE_INT)
947
948         ids = GObject.signal_list_ids(C)
949         self.assertEqual(len(ids), 1)
950         # Note canonicalized names
951         self.assertEqual(GObject.signal_name(ids[0]), 'my-signal')
952         # There is no signal 0 in gobject
953         self.assertEqual(GObject.signal_name(0), None)
954
955     def test_signal_lookup_with_invalid_type(self):
956         with self.assertRaisesRegex(TypeError, 'type must be instantiable or an interface.*'):
957             GObject.signal_lookup('NOT_A_SIGNAL_NAME', GObject.TYPE_INVALID)
958
959     def test_signal_lookup(self):
960         ids = GObject.signal_list_ids(C)
961         self.assertEqual(ids[0], GObject.signal_lookup('my_signal', C))
962         self.assertEqual(ids[0], GObject.signal_lookup('my-signal', C))
963
964         with self.assertRaisesRegex(TypeError, 'type must be instantiable or an interface.*'):
965             GObject.signal_lookup('NOT_A_SIGNAL_NAME', GObject.TYPE_INT)
966
967         # Invalid signal names return 0 instead of raising
968         self.assertEqual(GObject.signal_lookup('NOT_A_SIGNAL_NAME', C),
969                          0)
970
971     def test_signal_query(self):
972         my_signal_id, = GObject.signal_list_ids(C)
973
974         # Form is: (id, name, gtype, arg_count, return_type, (arg_type1, ...))
975         my_signal_expected_query_result = [my_signal_id, 'my-signal', C.__gtype__,
976                                            1, GObject.TYPE_NONE, (GObject.TYPE_INT,)]
977         # signal_query(name, type)
978         self.assertEqual(list(GObject.signal_query('my-signal', C)), my_signal_expected_query_result)
979         # signal_query(signal_id)
980         self.assertEqual(list(GObject.signal_query(my_signal_id)), my_signal_expected_query_result)
981         # invalid query returns None instead of raising
982         self.assertEqual(GObject.signal_query(0), None)
983         self.assertEqual(GObject.signal_query('NOT_A_SIGNAL', C),
984                          None)
985
986
987 @unittest.skipUnless(has_cairo, 'built without cairo support')
988 class TestIntrospectedSignals(unittest.TestCase):
989     def test_object_param_signal(self):
990         obj = Regress.TestObj()
991
992         def callback(obj, obj_param):
993             self.assertEqual(obj_param.props.int, 3)
994             self.assertGreater(obj_param.__grefcount__, 1)
995             obj.called = True
996
997         obj.called = False
998         obj.connect('sig-with-obj', callback)
999         obj.emit_sig_with_obj()
1000         self.assertTrue(obj.called)
1001
1002     def test_connect_after(self):
1003         obj = Regress.TestObj()
1004
1005         def callback(obj, obj_param):
1006             obj.called = True
1007
1008         obj.called = False
1009         obj.connect_after('sig-with-obj', callback)
1010         obj.emit_sig_with_obj()
1011         self.assertTrue(obj.called)
1012
1013     def test_connect_object(self):
1014         obj = Regress.TestObj()
1015
1016         def callback(obj, obj_param):
1017             obj.called = True
1018
1019         obj.called = False
1020         obj.connect_object('sig-with-obj', callback, obj)
1021         obj.emit_sig_with_obj()
1022         self.assertTrue(obj.called)
1023
1024     def test_connect_object_after(self):
1025         obj = Regress.TestObj()
1026
1027         def callback(obj, obj_param):
1028             obj.called = True
1029
1030         obj.called = False
1031         obj.connect_object_after('sig-with-obj', callback, obj)
1032         obj.emit_sig_with_obj()
1033         self.assertTrue(obj.called)
1034
1035     def test_int64_param_from_py(self):
1036         obj = Regress.TestObj()
1037
1038         def callback(obj, i):
1039             obj.callback_i = i
1040             return i
1041
1042         obj.callback_i = None
1043         obj.connect('sig-with-int64-prop', callback)
1044         rv = obj.emit('sig-with-int64-prop', GObject.G_MAXINT64)
1045         self.assertEqual(rv, GObject.G_MAXINT64)
1046         self.assertEqual(obj.callback_i, GObject.G_MAXINT64)
1047
1048     def test_uint64_param_from_py(self):
1049         obj = Regress.TestObj()
1050
1051         def callback(obj, i):
1052             obj.callback_i = i
1053             return i
1054
1055         obj.callback_i = None
1056         obj.connect('sig-with-uint64-prop', callback)
1057         rv = obj.emit('sig-with-uint64-prop', GObject.G_MAXUINT64)
1058         self.assertEqual(rv, GObject.G_MAXUINT64)
1059         self.assertEqual(obj.callback_i, GObject.G_MAXUINT64)
1060
1061     def test_int64_param_from_c(self):
1062         obj = Regress.TestObj()
1063
1064         def callback(obj, i):
1065             obj.callback_i = i
1066             return i
1067
1068         obj.callback_i = None
1069
1070         obj.connect('sig-with-int64-prop', callback)
1071         obj.emit_sig_with_int64()
1072         self.assertEqual(obj.callback_i, GObject.G_MAXINT64)
1073
1074     def test_uint64_param_from_c(self):
1075         obj = Regress.TestObj()
1076
1077         def callback(obj, i):
1078             obj.callback_i = i
1079             return i
1080
1081         obj.callback_i = None
1082
1083         obj.connect('sig-with-uint64-prop', callback)
1084         obj.emit_sig_with_uint64()
1085         self.assertEqual(obj.callback_i, GObject.G_MAXUINT64)
1086
1087     def test_intarray_ret(self):
1088         obj = Regress.TestObj()
1089
1090         def callback(obj, i):
1091             obj.callback_i = i
1092             return [i, i + 1]
1093
1094         obj.callback_i = None
1095
1096         try:
1097             obj.connect('sig-with-intarray-ret', callback)
1098         except TypeError as e:
1099             # compat with g-i 1.34.x
1100             if 'unknown signal' in str(e):
1101                 return
1102             raise
1103
1104         rv = obj.emit('sig-with-intarray-ret', 42)
1105         self.assertEqual(obj.callback_i, 42)
1106         self.assertEqual(type(rv), GLib.Array)
1107         self.assertEqual(rv.len, 2)
1108
1109
1110 if __name__ == '__main__':
1111     unittest.main()