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