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