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