Commit | Line | Data |
---|---|---|
32d2d479 MJ |
1 | # |
2 | # Copyright (C) 2019 EfficiOS Inc. | |
3 | # | |
4 | # This program is free software; you can redistribute it and/or | |
5 | # modify it under the terms of the GNU General Public License | |
6 | # as published by the Free Software Foundation; only version 2 | |
7 | # of the License. | |
8 | # | |
9 | # This program is distributed in the hope that it will be useful, | |
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
12 | # GNU General Public License for more details. | |
13 | # | |
14 | # You should have received a copy of the GNU General Public License | |
15 | # along with this program; if not, write to the Free Software | |
16 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
17 | # | |
18 | ||
f6a5e476 | 19 | import unittest |
f6a5e476 | 20 | import bt2 |
e9e22c06 | 21 | import sys |
fac7b25a | 22 | from utils import TestOutputPortMessageIterator |
f6a5e476 PP |
23 | |
24 | ||
fa4c33e3 | 25 | class UserMessageIteratorTestCase(unittest.TestCase): |
f6a5e476 | 26 | @staticmethod |
692f1a01 | 27 | def _create_graph(src_comp_cls, flt_comp_cls=None): |
f6a5e476 | 28 | class MySink(bt2._UserSinkComponent): |
b20382e2 | 29 | def __init__(self, params, obj): |
f6a5e476 PP |
30 | self._add_input_port('in') |
31 | ||
819d0ae7 | 32 | def _user_consume(self): |
fa4c33e3 | 33 | next(self._msg_iter) |
f6a5e476 | 34 | |
819d0ae7 | 35 | def _user_graph_is_configured(self): |
692f1a01 PP |
36 | self._msg_iter = self._create_input_port_message_iterator( |
37 | self._input_ports['in'] | |
38 | ) | |
f6a5e476 PP |
39 | |
40 | graph = bt2.Graph() | |
41 | src_comp = graph.add_component(src_comp_cls, 'src') | |
692f1a01 PP |
42 | |
43 | if flt_comp_cls is not None: | |
44 | flt_comp = graph.add_component(flt_comp_cls, 'flt') | |
45 | ||
f6a5e476 | 46 | sink_comp = graph.add_component(MySink, 'sink') |
692f1a01 PP |
47 | |
48 | if flt_comp_cls is not None: | |
49 | assert flt_comp is not None | |
50 | graph.connect_ports( | |
51 | src_comp.output_ports['out'], flt_comp.input_ports['in'] | |
52 | ) | |
53 | out_port = flt_comp.output_ports['out'] | |
54 | else: | |
55 | out_port = src_comp.output_ports['out'] | |
56 | ||
57 | graph.connect_ports(out_port, sink_comp.input_ports['in']) | |
f6a5e476 PP |
58 | return graph |
59 | ||
60 | def test_init(self): | |
a4dcfa96 SM |
61 | the_output_port_from_source = None |
62 | the_output_port_from_iter = None | |
63 | ||
fa4c33e3 | 64 | class MyIter(bt2._UserMessageIterator): |
a4dcfa96 | 65 | def __init__(self, self_port_output): |
f6a5e476 | 66 | nonlocal initialized |
a4dcfa96 | 67 | nonlocal the_output_port_from_iter |
f6a5e476 | 68 | initialized = True |
a4dcfa96 | 69 | the_output_port_from_iter = self_port_output |
f6a5e476 | 70 | |
61d96b89 | 71 | class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter): |
b20382e2 | 72 | def __init__(self, params, obj): |
a4dcfa96 | 73 | nonlocal the_output_port_from_source |
03ec9ebd | 74 | the_output_port_from_source = self._add_output_port('out', 'user data') |
f6a5e476 PP |
75 | |
76 | initialized = False | |
77 | graph = self._create_graph(MySource) | |
a4dcfa96 | 78 | graph.run() |
f6a5e476 | 79 | self.assertTrue(initialized) |
61d96b89 FD |
80 | self.assertEqual( |
81 | the_output_port_from_source.addr, the_output_port_from_iter.addr | |
82 | ) | |
03ec9ebd | 83 | self.assertEqual(the_output_port_from_iter.user_data, 'user data') |
f6a5e476 | 84 | |
692f1a01 PP |
85 | def test_create_from_message_iterator(self): |
86 | class MySourceIter(bt2._UserMessageIterator): | |
87 | def __init__(self, self_port_output): | |
88 | nonlocal src_iter_initialized | |
89 | src_iter_initialized = True | |
90 | ||
91 | class MySource(bt2._UserSourceComponent, message_iterator_class=MySourceIter): | |
b20382e2 | 92 | def __init__(self, params, obj): |
692f1a01 PP |
93 | self._add_output_port('out') |
94 | ||
95 | class MyFilterIter(bt2._UserMessageIterator): | |
96 | def __init__(self, self_port_output): | |
97 | nonlocal flt_iter_initialized | |
98 | flt_iter_initialized = True | |
99 | self._up_iter = self._create_input_port_message_iterator( | |
100 | self._component._input_ports['in'] | |
101 | ) | |
102 | ||
103 | def __next__(self): | |
104 | return next(self._up_iter) | |
105 | ||
106 | class MyFilter(bt2._UserFilterComponent, message_iterator_class=MyFilterIter): | |
b20382e2 | 107 | def __init__(self, params, obj): |
692f1a01 PP |
108 | self._add_input_port('in') |
109 | self._add_output_port('out') | |
110 | ||
111 | src_iter_initialized = False | |
112 | flt_iter_initialized = False | |
113 | graph = self._create_graph(MySource, MyFilter) | |
114 | graph.run() | |
115 | self.assertTrue(src_iter_initialized) | |
116 | self.assertTrue(flt_iter_initialized) | |
117 | ||
f6a5e476 | 118 | def test_finalize(self): |
fa4c33e3 | 119 | class MyIter(bt2._UserMessageIterator): |
819d0ae7 | 120 | def _user_finalize(self): |
f6a5e476 PP |
121 | nonlocal finalized |
122 | finalized = True | |
123 | ||
61d96b89 | 124 | class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter): |
b20382e2 | 125 | def __init__(self, params, obj): |
f6a5e476 PP |
126 | self._add_output_port('out') |
127 | ||
128 | finalized = False | |
129 | graph = self._create_graph(MySource) | |
a4dcfa96 | 130 | graph.run() |
f6a5e476 PP |
131 | del graph |
132 | self.assertTrue(finalized) | |
133 | ||
134 | def test_component(self): | |
fa4c33e3 | 135 | class MyIter(bt2._UserMessageIterator): |
a4dcfa96 | 136 | def __init__(self, self_port_output): |
f6a5e476 PP |
137 | nonlocal salut |
138 | salut = self._component._salut | |
139 | ||
61d96b89 | 140 | class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter): |
b20382e2 | 141 | def __init__(self, params, obj): |
f6a5e476 PP |
142 | self._add_output_port('out') |
143 | self._salut = 23 | |
144 | ||
145 | salut = None | |
146 | graph = self._create_graph(MySource) | |
a4dcfa96 | 147 | graph.run() |
f6a5e476 PP |
148 | self.assertEqual(salut, 23) |
149 | ||
150 | def test_addr(self): | |
fa4c33e3 | 151 | class MyIter(bt2._UserMessageIterator): |
a4dcfa96 | 152 | def __init__(self, self_port_output): |
f6a5e476 PP |
153 | nonlocal addr |
154 | addr = self.addr | |
155 | ||
61d96b89 | 156 | class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter): |
b20382e2 | 157 | def __init__(self, params, obj): |
f6a5e476 PP |
158 | self._add_output_port('out') |
159 | ||
160 | addr = None | |
161 | graph = self._create_graph(MySource) | |
a4dcfa96 | 162 | graph.run() |
f6a5e476 PP |
163 | self.assertIsNotNone(addr) |
164 | self.assertNotEqual(addr, 0) | |
165 | ||
4e853135 SM |
166 | # Test that messages returned by _UserMessageIterator.__next__ remain valid |
167 | # and can be re-used. | |
168 | def test_reuse_message(self): | |
169 | class MyIter(bt2._UserMessageIterator): | |
170 | def __init__(self, port): | |
171 | tc, sc, ec = port.user_data | |
172 | trace = tc() | |
173 | stream = trace.create_stream(sc) | |
174 | packet = stream.create_packet() | |
175 | ||
176 | # This message will be returned twice by __next__. | |
177 | event_message = self._create_event_message(ec, packet) | |
178 | ||
179 | self._msgs = [ | |
180 | self._create_stream_beginning_message(stream), | |
4e853135 SM |
181 | self._create_packet_beginning_message(packet), |
182 | event_message, | |
183 | event_message, | |
184 | ] | |
185 | ||
186 | def __next__(self): | |
187 | return self._msgs.pop(0) | |
188 | ||
189 | class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter): | |
b20382e2 | 190 | def __init__(self, params, obj): |
4e853135 | 191 | tc = self._create_trace_class() |
37a93d41 | 192 | sc = tc.create_stream_class(supports_packets=True) |
4e853135 SM |
193 | ec = sc.create_event_class() |
194 | self._add_output_port('out', (tc, sc, ec)) | |
195 | ||
196 | graph = bt2.Graph() | |
197 | src = graph.add_component(MySource, 'src') | |
fac7b25a | 198 | it = TestOutputPortMessageIterator(graph, src.output_ports['out']) |
4e853135 SM |
199 | |
200 | # Skip beginning messages. | |
b7cbc799 | 201 | msg = next(it) |
c946c9de | 202 | self.assertIsInstance(msg, bt2._StreamBeginningMessage) |
b7cbc799 | 203 | msg = next(it) |
c946c9de | 204 | self.assertIsInstance(msg, bt2._PacketBeginningMessage) |
4e853135 SM |
205 | |
206 | msg_ev1 = next(it) | |
207 | msg_ev2 = next(it) | |
208 | ||
c946c9de PP |
209 | self.assertIsInstance(msg_ev1, bt2._EventMessage) |
210 | self.assertIsInstance(msg_ev2, bt2._EventMessage) | |
4e853135 SM |
211 | self.assertEqual(msg_ev1.addr, msg_ev2.addr) |
212 | ||
39ddfa44 | 213 | @staticmethod |
fac7b25a | 214 | def _setup_seek_beginning_test(sink_cls): |
39ddfa44 SM |
215 | # Use a source, a filter and an output port iterator. This allows us |
216 | # to test calling `seek_beginning` on both a _OutputPortMessageIterator | |
217 | # and a _UserComponentInputPortMessageIterator, on top of checking that | |
218 | # _UserMessageIterator._seek_beginning is properly called. | |
219 | ||
220 | class MySourceIter(bt2._UserMessageIterator): | |
221 | def __init__(self, port): | |
222 | tc, sc, ec = port.user_data | |
223 | trace = tc() | |
224 | stream = trace.create_stream(sc) | |
225 | packet = stream.create_packet() | |
226 | ||
227 | self._msgs = [ | |
228 | self._create_stream_beginning_message(stream), | |
39ddfa44 SM |
229 | self._create_packet_beginning_message(packet), |
230 | self._create_event_message(ec, packet), | |
231 | self._create_event_message(ec, packet), | |
232 | self._create_packet_end_message(packet), | |
39ddfa44 SM |
233 | self._create_stream_end_message(stream), |
234 | ] | |
235 | self._at = 0 | |
236 | ||
819d0ae7 | 237 | def _user_seek_beginning(self): |
39ddfa44 SM |
238 | self._at = 0 |
239 | ||
240 | def __next__(self): | |
241 | if self._at < len(self._msgs): | |
242 | msg = self._msgs[self._at] | |
243 | self._at += 1 | |
244 | return msg | |
245 | else: | |
246 | raise StopIteration | |
247 | ||
61d96b89 | 248 | class MySource(bt2._UserSourceComponent, message_iterator_class=MySourceIter): |
b20382e2 | 249 | def __init__(self, params, obj): |
39ddfa44 | 250 | tc = self._create_trace_class() |
37a93d41 | 251 | sc = tc.create_stream_class(supports_packets=True) |
39ddfa44 SM |
252 | ec = sc.create_event_class() |
253 | ||
254 | self._add_output_port('out', (tc, sc, ec)) | |
255 | ||
256 | class MyFilterIter(bt2._UserMessageIterator): | |
257 | def __init__(self, port): | |
258 | input_port = port.user_data | |
692f1a01 PP |
259 | self._upstream_iter = self._create_input_port_message_iterator( |
260 | input_port | |
261 | ) | |
39ddfa44 SM |
262 | |
263 | def __next__(self): | |
264 | return next(self._upstream_iter) | |
265 | ||
819d0ae7 | 266 | def _user_seek_beginning(self): |
39ddfa44 SM |
267 | self._upstream_iter.seek_beginning() |
268 | ||
269 | @property | |
819d0ae7 | 270 | def _user_can_seek_beginning(self): |
39ddfa44 SM |
271 | return self._upstream_iter.can_seek_beginning |
272 | ||
273 | class MyFilter(bt2._UserFilterComponent, message_iterator_class=MyFilterIter): | |
b20382e2 | 274 | def __init__(self, params, obj): |
39ddfa44 SM |
275 | input_port = self._add_input_port('in') |
276 | self._add_output_port('out', input_port) | |
277 | ||
39ddfa44 SM |
278 | graph = bt2.Graph() |
279 | src = graph.add_component(MySource, 'src') | |
280 | flt = graph.add_component(MyFilter, 'flt') | |
fac7b25a | 281 | sink = graph.add_component(sink_cls, 'sink') |
39ddfa44 | 282 | graph.connect_ports(src.output_ports['out'], flt.input_ports['in']) |
fac7b25a PP |
283 | graph.connect_ports(flt.output_ports['out'], sink.input_ports['in']) |
284 | return MySourceIter, graph | |
39ddfa44 SM |
285 | |
286 | def test_can_seek_beginning(self): | |
fac7b25a PP |
287 | class MySink(bt2._UserSinkComponent): |
288 | def __init__(self, params, obj): | |
289 | self._add_input_port('in') | |
290 | ||
291 | def _user_graph_is_configured(self): | |
292 | self._msg_iter = self._create_input_port_message_iterator( | |
293 | self._input_ports['in'] | |
294 | ) | |
295 | ||
296 | def _user_consume(self): | |
297 | nonlocal can_seek_beginning | |
298 | can_seek_beginning = self._msg_iter.can_seek_beginning | |
299 | ||
300 | MySourceIter, graph = self._setup_seek_beginning_test(MySink) | |
39ddfa44 | 301 | |
819d0ae7 | 302 | def _user_can_seek_beginning(self): |
fac7b25a PP |
303 | nonlocal input_port_iter_can_seek_beginning |
304 | return input_port_iter_can_seek_beginning | |
39ddfa44 | 305 | |
819d0ae7 | 306 | MySourceIter._user_can_seek_beginning = property(_user_can_seek_beginning) |
39ddfa44 | 307 | |
fac7b25a PP |
308 | input_port_iter_can_seek_beginning = True |
309 | can_seek_beginning = None | |
310 | graph.run_once() | |
311 | self.assertTrue(can_seek_beginning) | |
39ddfa44 | 312 | |
fac7b25a PP |
313 | input_port_iter_can_seek_beginning = False |
314 | can_seek_beginning = None | |
315 | graph.run_once() | |
316 | self.assertFalse(can_seek_beginning) | |
39ddfa44 SM |
317 | |
318 | # Once can_seek_beginning returns an error, verify that it raises when | |
319 | # _can_seek_beginning has/returns the wrong type. | |
320 | ||
321 | # Remove the _can_seek_beginning method, we now rely on the presence of | |
322 | # a _seek_beginning method to know whether the iterator can seek to | |
323 | # beginning or not. | |
819d0ae7 | 324 | del MySourceIter._user_can_seek_beginning |
fac7b25a PP |
325 | can_seek_beginning = None |
326 | graph.run_once() | |
327 | self.assertTrue(can_seek_beginning) | |
39ddfa44 | 328 | |
819d0ae7 | 329 | del MySourceIter._user_seek_beginning |
fac7b25a PP |
330 | can_seek_beginning = None |
331 | graph.run_once() | |
332 | self.assertFalse(can_seek_beginning) | |
39ddfa44 SM |
333 | |
334 | def test_seek_beginning(self): | |
fac7b25a PP |
335 | class MySink(bt2._UserSinkComponent): |
336 | def __init__(self, params, obj): | |
337 | self._add_input_port('in') | |
39ddfa44 | 338 | |
fac7b25a PP |
339 | def _user_graph_is_configured(self): |
340 | self._msg_iter = self._create_input_port_message_iterator( | |
341 | self._input_ports['in'] | |
342 | ) | |
343 | ||
344 | def _user_consume(self): | |
345 | nonlocal do_seek_beginning | |
346 | nonlocal msg | |
347 | ||
348 | if do_seek_beginning: | |
349 | self._msg_iter.seek_beginning() | |
350 | return | |
351 | ||
352 | msg = next(self._msg_iter) | |
353 | ||
354 | do_seek_beginning = False | |
355 | msg = None | |
356 | MySourceIter, graph = self._setup_seek_beginning_test(MySink) | |
357 | graph.run_once() | |
c946c9de | 358 | self.assertIsInstance(msg, bt2._StreamBeginningMessage) |
fac7b25a | 359 | graph.run_once() |
c946c9de | 360 | self.assertIsInstance(msg, bt2._PacketBeginningMessage) |
fac7b25a PP |
361 | do_seek_beginning = True |
362 | graph.run_once() | |
363 | do_seek_beginning = False | |
364 | graph.run_once() | |
365 | self.assertIsInstance(msg, bt2._StreamBeginningMessage) | |
39ddfa44 | 366 | |
fac7b25a PP |
367 | def test_seek_beginning_user_error(self): |
368 | class MySink(bt2._UserSinkComponent): | |
369 | def __init__(self, params, obj): | |
370 | self._add_input_port('in') | |
39ddfa44 | 371 | |
fac7b25a PP |
372 | def _user_graph_is_configured(self): |
373 | self._msg_iter = self._create_input_port_message_iterator( | |
374 | self._input_ports['in'] | |
375 | ) | |
39ddfa44 | 376 | |
fac7b25a PP |
377 | def _user_consume(self): |
378 | self._msg_iter.seek_beginning() | |
39ddfa44 | 379 | |
fac7b25a | 380 | MySourceIter, graph = self._setup_seek_beginning_test(MySink) |
39ddfa44 | 381 | |
819d0ae7 | 382 | def _user_seek_beginning_error(self): |
61d96b89 | 383 | raise ValueError('ouch') |
39ddfa44 | 384 | |
819d0ae7 | 385 | MySourceIter._user_seek_beginning = _user_seek_beginning_error |
39ddfa44 | 386 | |
614743a5 | 387 | with self.assertRaises(bt2._Error): |
fac7b25a | 388 | graph.run_once() |
39ddfa44 | 389 | |
752f4edf SM |
390 | # Try consuming many times from an iterator that always returns TryAgain. |
391 | # This verifies that we are not missing an incref of Py_None, making the | |
392 | # refcount of Py_None reach 0. | |
393 | def test_try_again_many_times(self): | |
394 | class MyIter(bt2._UserMessageIterator): | |
395 | def __next__(self): | |
396 | raise bt2.TryAgain | |
397 | ||
398 | class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter): | |
b20382e2 | 399 | def __init__(self, params, obj): |
752f4edf SM |
400 | self._add_output_port('out') |
401 | ||
402 | graph = bt2.Graph() | |
403 | src = graph.add_component(MySource, 'src') | |
fac7b25a | 404 | it = TestOutputPortMessageIterator(graph, src.output_ports['out']) |
752f4edf | 405 | |
e9e22c06 PP |
406 | # Three times the initial ref count of `None` iterations should |
407 | # be enough to catch the bug even if there are small differences | |
752f4edf | 408 | # between configurations. |
e9e22c06 PP |
409 | none_ref_count = sys.getrefcount(None) * 3 |
410 | ||
411 | for i in range(none_ref_count): | |
752f4edf SM |
412 | with self.assertRaises(bt2.TryAgain): |
413 | next(it) | |
414 | ||
39ddfa44 | 415 | |
39ddfa44 SM |
416 | if __name__ == '__main__': |
417 | unittest.main() |