Coverage for tests/test_dispatch/test_dispatch.py: 98%
218 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-16 09:17 +0330
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-16 09:17 +0330
1import logging
2import re
3import weakref
4from types import TracebackType
6import pytest
8from signals.dispatch import Signal, receiver
9from signals.dispatch.dispatcher import _make_id
10from signals.test.utils import garbage_collect
12from lazy_settings.test.utils import override_settings
15def receiver_1_arg(val, **kwargs):
16 return val
19class Callable:
20 def __call__(self, val, **kwargs):
21 return val
23 def a(self, val, **kwargs):
24 return val
27a_signal = Signal()
28b_signal = Signal()
29c_signal = Signal()
30d_signal = Signal(use_caching=True)
33class TestDispatcher:
34 def assert_test_is_clean(self, signal:Signal):
35 assert not signal.has_listeners()
36 assert signal.receivers == []
38 @override_settings(DEBUG=True)
39 def test_cannot_connect_no_kwargs(self):
40 def receiver_no_kwargs(sender):
41 pass
43 msg = re.escape("Signal receivers must accept keyword arguments (**kwargs).")
44 with pytest.raises(ValueError, match=msg):
45 a_signal.connect(receiver_no_kwargs)
46 self.assert_test_is_clean(a_signal)
48 @override_settings(DEBUG=True)
49 def test_cannot_connect_non_callable(self):
50 msg = "Signal receivers must be callable."
51 with pytest.raises(TypeError, match=msg):
52 a_signal.connect(object())
53 self.assert_test_is_clean(a_signal)
55 def test_send(self):
56 a_signal.connect(receiver_1_arg, sender=self)
57 result = a_signal.send(sender=self, val="test")
58 assert result == [(receiver_1_arg, "test")]
59 a_signal.disconnect(receiver_1_arg, sender=self)
60 self.assert_test_is_clean(a_signal)
62 def test_send_no_receivers(self):
63 result = a_signal.send(sender=self, val="test")
64 assert result == []
66 def test_send_connected_no_sender(self):
67 a_signal.connect(receiver_1_arg)
68 result = a_signal.send(sender=self, val="test")
69 assert result == [(receiver_1_arg, "test")]
70 a_signal.disconnect(receiver_1_arg)
71 self.assert_test_is_clean(a_signal)
73 def test_send_different_no_sender(self):
74 a_signal.connect(receiver_1_arg, sender=object)
75 result = a_signal.send(sender=self, val="test")
76 assert result == []
77 a_signal.disconnect(receiver_1_arg, sender=object)
78 self.assert_test_is_clean(a_signal)
80 def test_unweakrefable_sender(self):
81 sender = object()
82 a_signal.connect(receiver_1_arg, sender=sender)
83 result = a_signal.send(sender=sender, val="test")
84 assert result == [(receiver_1_arg, "test")]
85 a_signal.disconnect(receiver_1_arg, sender=sender)
86 self.assert_test_is_clean(a_signal)
88 def test_garbage_collected_receiver(self):
89 a = Callable()
90 a_signal.connect(a.a, sender=self)
91 del a
92 garbage_collect()
93 result = a_signal.send(sender=self, val="test")
94 assert result == []
95 self.assert_test_is_clean(a_signal)
97 def test_garbage_collected_sender(self, mocker):
98 signal = Signal()
100 class Sender:
101 pass
103 def make_id(target):
104 """
105 Simulate id() reuse for distinct senders with non-overlapping
106 lifetimes that would require memory contention to reproduce.
107 """
108 if isinstance(target, Sender):
109 return 0
110 return _make_id(target)
112 def first_receiver(attempt, **kwargs):
113 return attempt
115 def second_receiver(attempt, **kwargs):
116 return attempt
118 mocker.patch("signals.dispatch.dispatcher._make_id", make_id)
119 sender = Sender()
120 signal.connect(first_receiver, sender)
121 result = signal.send(sender, attempt="first")
122 assert result == [(first_receiver, "first")]
124 del sender
125 garbage_collect()
127 sender = Sender()
128 signal.connect(second_receiver, sender)
129 result = signal.send(sender, attempt="second")
130 assert result == [(second_receiver, "second")]
132 def test_cached_garbaged_collected(self):
133 """
134 Make sure signal caching sender receivers don't prevent garbage
135 collection of senders.
136 """
138 class sender:
139 pass
141 wref = weakref.ref(sender)
142 d_signal.connect(receiver_1_arg)
143 d_signal.send(sender, val="garbage")
144 del sender
145 garbage_collect()
146 try:
147 assert wref() is None
148 finally:
149 # Disconnect after reference check since it flushes the tested cache.
150 d_signal.disconnect(receiver_1_arg)
152 def test_multiple_registration(self):
153 a = Callable()
154 a_signal.connect(a)
155 a_signal.connect(a)
156 a_signal.connect(a)
157 a_signal.connect(a)
158 a_signal.connect(a)
159 a_signal.connect(a)
160 result = a_signal.send(sender=self, val="test")
161 assert len(result) == 1
162 assert len(a_signal.receivers) == 1
163 del a
164 del result
165 garbage_collect()
166 self.assert_test_is_clean(a_signal)
168 def test_uid_registration(self):
169 def uid_based_receiver_1(**kwargs):
170 pass
172 def uid_based_receiver_2(**kwargs):
173 pass
175 a_signal.connect(uid_based_receiver_1, dispatch_uid="uid")
176 a_signal.connect(uid_based_receiver_2, dispatch_uid="uid")
177 assert len(a_signal.receivers) == 1
178 a_signal.disconnect(dispatch_uid="uid")
179 self.assert_test_is_clean(a_signal)
181 def test_send_robust_success(self):
182 a_signal.connect(receiver_1_arg)
183 result = a_signal.send_robust(sender=self, val="test")
184 assert result == [(receiver_1_arg, "test")]
185 a_signal.disconnect(receiver_1_arg)
186 self.assert_test_is_clean(a_signal)
188 def test_send_robust_no_receivers(self):
189 result = a_signal.send_robust(sender=self, val="test")
190 assert result == []
192 def test_send_robust_ignored_sender(self):
193 a_signal.connect(receiver_1_arg)
194 result = a_signal.send_robust(sender=self, val="test")
195 assert result == [(receiver_1_arg, "test")]
196 a_signal.disconnect(receiver_1_arg)
197 self.assert_test_is_clean(a_signal)
199 def test_send_robust_fail(self, caplog):
200 def fails(val, **kwargs):
201 raise ValueError("this")
203 a_signal.connect(fails)
204 try:
205 with caplog.at_level(logging.ERROR, logger="signals.dispatch"):
206 result = a_signal.send_robust(sender=self, val="test")
207 err = result[0][1]
208 assert isinstance(err, ValueError)
209 assert err.args == ("this",)
210 assert hasattr(err, "__traceback__")
211 assert isinstance(err.__traceback__, TracebackType)
213 log_record = caplog.records[0]
214 assert (
215 log_record.getMessage() ==
216 "Error calling "
217 "TestDispatcher.test_send_robust_fail.<locals>.fails in "
218 "Signal.send_robust() (this)"
219 )
220 assert log_record.exc_info
221 _, exc_value, _ = log_record.exc_info
222 assert isinstance(exc_value, ValueError)
223 assert str(exc_value) == "this"
224 finally:
225 a_signal.disconnect(fails)
227 self.assert_test_is_clean(a_signal)
229 def test_disconnection(self):
230 receiver_1 = Callable()
231 receiver_2 = Callable()
232 receiver_3 = Callable()
233 a_signal.connect(receiver_1)
234 a_signal.connect(receiver_2)
235 a_signal.connect(receiver_3)
236 a_signal.disconnect(receiver_1)
237 del receiver_2
238 garbage_collect()
239 a_signal.disconnect(receiver_3)
240 self.assert_test_is_clean(a_signal)
242 def test_values_returned_by_disconnection(self):
243 receiver_1 = Callable()
244 receiver_2 = Callable()
245 a_signal.connect(receiver_1)
246 receiver_1_disconnected = a_signal.disconnect(receiver_1)
247 receiver_2_disconnected = a_signal.disconnect(receiver_2)
248 assert receiver_1_disconnected
249 assert receiver_2_disconnected is False
250 self.assert_test_is_clean(a_signal)
252 def test_has_listeners(self):
253 assert a_signal.has_listeners() is False
254 assert a_signal.has_listeners() is False
255 receiver_1 = Callable()
256 a_signal.connect(receiver_1)
257 assert a_signal.has_listeners()
258 assert a_signal.has_listeners(sender=object())
259 a_signal.disconnect(receiver_1)
260 assert a_signal.has_listeners() is False
261 assert a_signal.has_listeners(sender=object()) is False
263class TestReceiver:
264 def test_receiver_single_signal(self):
265 @receiver(a_signal)
266 def f(val, **kwargs):
267 self.state = val
269 self.state = False
270 a_signal.send(sender=self, val=True)
271 assert self.state is True
273 def test_receiver_signal_list(self):
274 @receiver([a_signal, b_signal, c_signal])
275 def f(val, **kwargs):
276 self.state.append(val)
278 self.state = []
279 a_signal.send(sender=self, val="a")
280 c_signal.send(sender=self, val="c")
281 b_signal.send(sender=self, val="b")
282 assert "a" in self.state
283 assert "b" in self.state
284 assert "c" in self.state