python: run isort on everything
[babeltrace.git] / tests / bindings / python / bt2 / test_message_iterator.py
CommitLineData
0235b0db 1# SPDX-License-Identifier: GPL-2.0-only
d2d857a8
MJ
2#
3# Copyright (C) 2019 EfficiOS Inc.
4#
d2d857a8 5
5995b304 6import sys
811644b8 7import unittest
5995b304 8
811644b8 9import bt2
14503fb1 10from bt2 import port as bt2_port
8d8b141d 11from bt2 import message_iterator as bt2_message_iterator
5995b304 12from utils import TestOutputPortMessageIterator
811644b8
PP
13
14
0a6d7302
SM
15class SimpleSink(bt2._UserSinkComponent):
16 # Straightforward sink that creates one input port (`in`) and consumes from
17 # it.
811644b8 18
59225a3e 19 def __init__(self, config, params, obj):
f5567ea8 20 self._add_input_port("in")
811644b8 21
0a6d7302
SM
22 def _user_consume(self):
23 next(self._msg_iter)
811644b8 24
0a6d7302 25 def _user_graph_is_configured(self):
f5567ea8 26 self._msg_iter = self._create_message_iterator(self._input_ports["in"])
ca02df0a 27
ca02df0a 28
0a6d7302
SM
29def _create_graph(src_comp_cls, sink_comp_cls, flt_comp_cls=None):
30 graph = bt2.Graph()
ca02df0a 31
f5567ea8
FD
32 src_comp = graph.add_component(src_comp_cls, "src")
33 sink_comp = graph.add_component(sink_comp_cls, "sink")
ca02df0a 34
0a6d7302 35 if flt_comp_cls is not None:
f5567ea8
FD
36 flt_comp = graph.add_component(flt_comp_cls, "flt")
37 graph.connect_ports(src_comp.output_ports["out"], flt_comp.input_ports["in"])
38 graph.connect_ports(flt_comp.output_ports["out"], sink_comp.input_ports["in"])
0a6d7302 39 else:
f5567ea8 40 graph.connect_ports(src_comp.output_ports["out"], sink_comp.input_ports["in"])
811644b8 41
0a6d7302
SM
42 return graph
43
44
45class UserMessageIteratorTestCase(unittest.TestCase):
811644b8 46 def test_init(self):
c5f330cd
SM
47 the_output_port_from_source = None
48 the_output_port_from_iter = None
49
5602ef81 50 class MyIter(bt2._UserMessageIterator):
8d8b141d 51 def __init__(self, config, self_port_output):
811644b8 52 nonlocal initialized
c5f330cd 53 nonlocal the_output_port_from_iter
811644b8 54 initialized = True
c5f330cd 55 the_output_port_from_iter = self_port_output
811644b8 56
cfbd7cf3 57 class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter):
59225a3e 58 def __init__(self, config, params, obj):
c5f330cd 59 nonlocal the_output_port_from_source
f5567ea8 60 the_output_port_from_source = self._add_output_port("out", "user data")
811644b8
PP
61
62 initialized = False
0a6d7302 63 graph = _create_graph(MySource, SimpleSink)
c5f330cd 64 graph.run()
811644b8 65 self.assertTrue(initialized)
cfbd7cf3
FD
66 self.assertEqual(
67 the_output_port_from_source.addr, the_output_port_from_iter.addr
68 )
f5567ea8 69 self.assertEqual(the_output_port_from_iter.user_data, "user data")
811644b8 70
ca02df0a
PP
71 def test_create_from_message_iterator(self):
72 class MySourceIter(bt2._UserMessageIterator):
8d8b141d 73 def __init__(self, config, self_port_output):
ca02df0a
PP
74 nonlocal src_iter_initialized
75 src_iter_initialized = True
76
77 class MySource(bt2._UserSourceComponent, message_iterator_class=MySourceIter):
59225a3e 78 def __init__(self, config, params, obj):
f5567ea8 79 self._add_output_port("out")
ca02df0a
PP
80
81 class MyFilterIter(bt2._UserMessageIterator):
8d8b141d 82 def __init__(self, config, self_port_output):
ca02df0a
PP
83 nonlocal flt_iter_initialized
84 flt_iter_initialized = True
9a2c8b8e 85 self._up_iter = self._create_message_iterator(
f5567ea8 86 self._component._input_ports["in"]
ca02df0a
PP
87 )
88
89 def __next__(self):
90 return next(self._up_iter)
91
92 class MyFilter(bt2._UserFilterComponent, message_iterator_class=MyFilterIter):
59225a3e 93 def __init__(self, config, params, obj):
f5567ea8
FD
94 self._add_input_port("in")
95 self._add_output_port("out")
ca02df0a
PP
96
97 src_iter_initialized = False
98 flt_iter_initialized = False
0a6d7302 99 graph = _create_graph(MySource, SimpleSink, MyFilter)
ca02df0a
PP
100 graph.run()
101 self.assertTrue(src_iter_initialized)
102 self.assertTrue(flt_iter_initialized)
103
415d43a1
SM
104 # Test that creating a message iterator from a sink component on a
105 # non-connected inport port raises.
106 def test_create_from_sink_component_unconnected_port_raises(self):
107 class MySink(bt2._UserSinkComponent):
108 def __init__(comp_self, config, params, obj):
f5567ea8 109 comp_self._input_port = comp_self._add_input_port("in")
415d43a1
SM
110
111 def _user_graph_is_configured(comp_self):
f5567ea8 112 with self.assertRaisesRegex(ValueError, "input port is not connected"):
415d43a1
SM
113 comp_self._create_message_iterator(comp_self._input_port)
114
115 nonlocal seen
116 seen = True
117
118 def _user_consume(self):
119 raise bt2.Stop
120
121 seen = False
122 graph = bt2.Graph()
f5567ea8 123 graph.add_component(MySink, "snk")
415d43a1
SM
124 graph.run()
125 self.assertTrue(seen)
126
127 # Test that creating a message iterator from a message iteartor on a
128 # non-connected inport port raises.
129 def test_create_from_message_iterator_unconnected_port_raises(self):
130 class MyFilterIter(bt2._UserMessageIterator):
131 def __init__(iter_self, config, port):
f5567ea8 132 input_port = iter_self._component._input_ports["in"]
415d43a1 133
f5567ea8 134 with self.assertRaisesRegex(ValueError, "input port is not connected"):
415d43a1
SM
135 iter_self._create_message_iterator(input_port)
136
137 nonlocal seen
138 seen = True
139
140 class MyFilter(bt2._UserFilterComponent, message_iterator_class=MyFilterIter):
141 def __init__(comp_self, config, params, obj):
f5567ea8
FD
142 comp_self._add_input_port("in")
143 comp_self._add_output_port("out")
415d43a1
SM
144
145 class MySink(bt2._UserSinkComponent):
146 def __init__(comp_self, config, params, obj):
f5567ea8 147 comp_self._input_port = comp_self._add_input_port("in")
415d43a1
SM
148
149 def _user_graph_is_configured(comp_self):
150 comp_self._input_iter = comp_self._create_message_iterator(
151 comp_self._input_port
152 )
153
154 def _user_consume(self):
155 raise bt2.Stop
156
157 seen = False
158 graph = bt2.Graph()
f5567ea8
FD
159 flt = graph.add_component(MyFilter, "flt")
160 snk = graph.add_component(MySink, "snk")
161 graph.connect_ports(flt.output_ports["out"], snk.input_ports["in"])
415d43a1
SM
162 graph.run()
163 self.assertTrue(seen)
164
e803df70
SM
165 def test_create_user_error(self):
166 # This tests both error handling by
9a2c8b8e
PP
167 # _UserSinkComponent._create_message_iterator
168 # and _UserMessageIterator._create_message_iterator, as they
e803df70
SM
169 # are both used in the graph.
170 class MySourceIter(bt2._UserMessageIterator):
8d8b141d 171 def __init__(self, config, self_port_output):
f5567ea8 172 raise ValueError("Very bad error")
e803df70
SM
173
174 class MySource(bt2._UserSourceComponent, message_iterator_class=MySourceIter):
59225a3e 175 def __init__(self, config, params, obj):
f5567ea8 176 self._add_output_port("out")
e803df70
SM
177
178 class MyFilterIter(bt2._UserMessageIterator):
8d8b141d 179 def __init__(self, config, self_port_output):
e803df70
SM
180 # This is expected to raise because of the error in
181 # MySourceIter.__init__.
f5567ea8 182 self._create_message_iterator(self._component._input_ports["in"])
e803df70
SM
183
184 class MyFilter(bt2._UserFilterComponent, message_iterator_class=MyFilterIter):
59225a3e 185 def __init__(self, config, params, obj):
f5567ea8
FD
186 self._add_input_port("in")
187 self._add_output_port("out")
e803df70 188
0a6d7302 189 graph = _create_graph(MySource, SimpleSink, MyFilter)
e803df70
SM
190
191 with self.assertRaises(bt2._Error) as ctx:
192 graph.run()
193
194 exc = ctx.exception
195 cause = exc[0]
196
197 self.assertIsInstance(cause, bt2._MessageIteratorErrorCause)
f5567ea8
FD
198 self.assertEqual(cause.component_name, "src")
199 self.assertEqual(cause.component_output_port_name, "out")
200 self.assertIn("ValueError: Very bad error", cause.message)
e803df70 201
811644b8 202 def test_finalize(self):
5602ef81 203 class MyIter(bt2._UserMessageIterator):
6a91742b 204 def _user_finalize(self):
811644b8
PP
205 nonlocal finalized
206 finalized = True
207
cfbd7cf3 208 class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter):
59225a3e 209 def __init__(self, config, params, obj):
f5567ea8 210 self._add_output_port("out")
811644b8
PP
211
212 finalized = False
0a6d7302 213 graph = _create_graph(MySource, SimpleSink)
c5f330cd 214 graph.run()
811644b8
PP
215 del graph
216 self.assertTrue(finalized)
217
8d8b141d
SM
218 def test_config_parameter(self):
219 class MyIter(bt2._UserMessageIterator):
220 def __init__(self, config, port):
221 nonlocal config_type
222 config_type = type(config)
223
224 class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter):
225 def __init__(self, config, params, obj):
f5567ea8 226 self._add_output_port("out")
8d8b141d
SM
227
228 config_type = None
229 graph = _create_graph(MySource, SimpleSink)
230 graph.run()
231 self.assertIs(config_type, bt2_message_iterator._MessageIteratorConfiguration)
232
233 def _test_config_can_seek_forward(self, set_can_seek_forward):
234 class MyIter(bt2._UserMessageIterator):
235 def __init__(self, config, port):
236 if set_can_seek_forward:
237 config.can_seek_forward = True
238
239 class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter):
240 def __init__(self, config, params, obj):
f5567ea8 241 self._add_output_port("out")
8d8b141d
SM
242
243 class MySink(bt2._UserSinkComponent):
244 def __init__(self, config, params, obj):
f5567ea8 245 self._add_input_port("in")
8d8b141d
SM
246
247 def _user_graph_is_configured(self):
f5567ea8 248 self._msg_iter = self._create_message_iterator(self._input_ports["in"])
8d8b141d
SM
249
250 def _user_consume(self):
251 nonlocal can_seek_forward
252 can_seek_forward = self._msg_iter.can_seek_forward
253
254 can_seek_forward = None
255 graph = _create_graph(MySource, MySink)
256 graph.run_once()
257 self.assertIs(can_seek_forward, set_can_seek_forward)
258
259 def test_config_can_seek_forward_default(self):
260 self._test_config_can_seek_forward(False)
261
262 def test_config_can_seek_forward(self):
263 self._test_config_can_seek_forward(True)
264
265 def test_config_can_seek_forward_wrong_type(self):
266 class MyIter(bt2._UserMessageIterator):
267 def __init__(self, config, port):
268 config.can_seek_forward = 1
269
270 class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter):
271 def __init__(self, config, params, obj):
f5567ea8 272 self._add_output_port("out")
8d8b141d
SM
273
274 graph = _create_graph(MySource, SimpleSink)
275 with self.assertRaises(bt2._Error) as ctx:
276 graph.run()
277
278 root_cause = ctx.exception[0]
279 self.assertIn("TypeError: 'int' is not a 'bool' object", root_cause.message)
280
811644b8 281 def test_component(self):
5602ef81 282 class MyIter(bt2._UserMessageIterator):
8d8b141d 283 def __init__(self, config, self_port_output):
811644b8
PP
284 nonlocal salut
285 salut = self._component._salut
286
cfbd7cf3 287 class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter):
59225a3e 288 def __init__(self, config, params, obj):
f5567ea8 289 self._add_output_port("out")
811644b8
PP
290 self._salut = 23
291
292 salut = None
0a6d7302 293 graph = _create_graph(MySource, SimpleSink)
c5f330cd 294 graph.run()
811644b8
PP
295 self.assertEqual(salut, 23)
296
14503fb1
SM
297 def test_port(self):
298 class MyIter(bt2._UserMessageIterator):
8d8b141d 299 def __init__(self_iter, config, self_port_output):
14503fb1
SM
300 nonlocal called
301 called = True
302 port = self_iter._port
303 self.assertIs(type(self_port_output), bt2_port._UserComponentOutputPort)
304 self.assertIs(type(port), bt2_port._UserComponentOutputPort)
305 self.assertEqual(self_port_output.addr, port.addr)
306
307 class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter):
59225a3e 308 def __init__(self, config, params, obj):
f5567ea8 309 self._add_output_port("out")
14503fb1
SM
310
311 called = False
0a6d7302 312 graph = _create_graph(MySource, SimpleSink)
14503fb1
SM
313 graph.run()
314 self.assertTrue(called)
315
811644b8 316 def test_addr(self):
5602ef81 317 class MyIter(bt2._UserMessageIterator):
8d8b141d 318 def __init__(self, config, self_port_output):
811644b8
PP
319 nonlocal addr
320 addr = self.addr
321
cfbd7cf3 322 class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter):
59225a3e 323 def __init__(self, config, params, obj):
f5567ea8 324 self._add_output_port("out")
811644b8
PP
325
326 addr = None
0a6d7302 327 graph = _create_graph(MySource, SimpleSink)
c5f330cd 328 graph.run()
811644b8
PP
329 self.assertIsNotNone(addr)
330 self.assertNotEqual(addr, 0)
331
d79a8353
SM
332 # Test that messages returned by _UserMessageIterator.__next__ remain valid
333 # and can be re-used.
334 def test_reuse_message(self):
335 class MyIter(bt2._UserMessageIterator):
8d8b141d 336 def __init__(self, config, port):
d79a8353
SM
337 tc, sc, ec = port.user_data
338 trace = tc()
339 stream = trace.create_stream(sc)
340 packet = stream.create_packet()
341
342 # This message will be returned twice by __next__.
343 event_message = self._create_event_message(ec, packet)
344
345 self._msgs = [
346 self._create_stream_beginning_message(stream),
d79a8353
SM
347 self._create_packet_beginning_message(packet),
348 event_message,
349 event_message,
350 ]
351
352 def __next__(self):
353 return self._msgs.pop(0)
354
355 class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter):
59225a3e 356 def __init__(self, config, params, obj):
d79a8353 357 tc = self._create_trace_class()
26fc5aed 358 sc = tc.create_stream_class(supports_packets=True)
d79a8353 359 ec = sc.create_event_class()
f5567ea8 360 self._add_output_port("out", (tc, sc, ec))
d79a8353
SM
361
362 graph = bt2.Graph()
f5567ea8
FD
363 src = graph.add_component(MySource, "src")
364 it = TestOutputPortMessageIterator(graph, src.output_ports["out"])
d79a8353
SM
365
366 # Skip beginning messages.
188edac1 367 msg = next(it)
f0a42b33 368 self.assertIs(type(msg), bt2._StreamBeginningMessageConst)
188edac1 369 msg = next(it)
f0a42b33 370 self.assertIs(type(msg), bt2._PacketBeginningMessageConst)
d79a8353
SM
371
372 msg_ev1 = next(it)
373 msg_ev2 = next(it)
374
f0a42b33
FD
375 self.assertIs(type(msg_ev1), bt2._EventMessageConst)
376 self.assertIs(type(msg_ev2), bt2._EventMessageConst)
d79a8353
SM
377 self.assertEqual(msg_ev1.addr, msg_ev2.addr)
378
0a6d7302
SM
379 # Try consuming many times from an iterator that always returns TryAgain.
380 # This verifies that we are not missing an incref of Py_None, making the
381 # refcount of Py_None reach 0.
382 def test_try_again_many_times(self):
383 class MyIter(bt2._UserMessageIterator):
f00b8d40 384 def __next__(self):
0a6d7302 385 raise bt2.TryAgain
f00b8d40 386
0a6d7302 387 class MySource(bt2._UserSourceComponent, message_iterator_class=MyIter):
59225a3e 388 def __init__(self, config, params, obj):
f5567ea8 389 self._add_output_port("out")
f00b8d40
SM
390
391 class MyFilterIter(bt2._UserMessageIterator):
392 def __init__(self, port):
393 input_port = port.user_data
9a2c8b8e 394 self._upstream_iter = self._create_message_iterator(input_port)
f00b8d40
SM
395
396 def __next__(self):
397 return next(self._upstream_iter)
398
6a91742b 399 def _user_seek_beginning(self):
f00b8d40
SM
400 self._upstream_iter.seek_beginning()
401
6a91742b 402 def _user_can_seek_beginning(self):
14cfc8ce 403 return self._upstream_iter.can_seek_beginning()
f00b8d40
SM
404
405 class MyFilter(bt2._UserFilterComponent, message_iterator_class=MyFilterIter):
59225a3e 406 def __init__(self, config, params, obj):
f5567ea8
FD
407 input_port = self._add_input_port("in")
408 self._add_output_port("out", input_port)
f00b8d40 409
f00b8d40 410 graph = bt2.Graph()
f5567ea8
FD
411 src = graph.add_component(MySource, "src")
412 it = TestOutputPortMessageIterator(graph, src.output_ports["out"])
0a6d7302
SM
413
414 # Three times the initial ref count of `None` iterations should
415 # be enough to catch the bug even if there are small differences
416 # between configurations.
417 none_ref_count = sys.getrefcount(None) * 3
418
419 for i in range(none_ref_count):
420 with self.assertRaises(bt2.TryAgain):
421 next(it)
422
fca28f75
SM
423 def test_error_in_iterator_with_cycle_after_having_created_upstream_iterator(self):
424 # Test a failure that triggered an abort in libbabeltrace2, in this situation:
425 #
426 # - The filter iterator creates an upstream iterator.
427 # - The filter iterator creates a reference cycle, including itself.
428 # - An exception is raised, causing the filter iterator's
429 # initialization method to fail.
430 class MySourceIter(bt2._UserMessageIterator):
431 pass
432
433 class MySource(bt2._UserSourceComponent, message_iterator_class=MySourceIter):
434 def __init__(self, config, params, obj):
f5567ea8 435 self._add_output_port("out")
fca28f75
SM
436
437 class MyFilterIter(bt2._UserMessageIterator):
438 def __init__(self, config, port):
439 # First, create an upstream iterator.
9a2c8b8e 440 self._upstream_iter = self._create_message_iterator(
f5567ea8 441 self._component._input_ports["in"]
fca28f75
SM
442 )
443
444 # Then, voluntarily make a reference cycle that will keep this
445 # Python object alive, which will keep the upstream iterator
446 # Babeltrace object alive.
447 self._self = self
448
449 # Finally, raise an exception to make __init__ fail.
f5567ea8 450 raise ValueError("woops")
fca28f75
SM
451
452 class MyFilter(bt2._UserFilterComponent, message_iterator_class=MyFilterIter):
453 def __init__(self, config, params, obj):
f5567ea8
FD
454 self._in = self._add_input_port("in")
455 self._out = self._add_output_port("out")
fca28f75
SM
456
457 class MySink(bt2._UserSinkComponent):
458 def __init__(self, config, params, obj):
f5567ea8 459 self._input_port = self._add_input_port("in")
fca28f75
SM
460
461 def _user_graph_is_configured(self):
9a2c8b8e 462 self._upstream_iter = self._create_message_iterator(self._input_port)
fca28f75
SM
463
464 def _user_consume(self):
465 # We should not reach this.
466 assert False
467
468 g = bt2.Graph()
f5567ea8
FD
469 src = g.add_component(MySource, "src")
470 flt = g.add_component(MyFilter, "flt")
471 snk = g.add_component(MySink, "snk")
472 g.connect_ports(src.output_ports["out"], flt.input_ports["in"])
473 g.connect_ports(flt.output_ports["out"], snk.input_ports["in"])
fca28f75 474
f5567ea8 475 with self.assertRaisesRegex(bt2._Error, "ValueError: woops"):
fca28f75
SM
476 g.run()
477
0a6d7302 478
c182d7dd
SM
479def _setup_seek_test(
480 sink_cls,
481 user_seek_beginning=None,
482 user_can_seek_beginning=None,
483 user_seek_ns_from_origin=None,
484 user_can_seek_ns_from_origin=None,
c0e46a7c 485 can_seek_forward=False,
c182d7dd 486):
0a6d7302 487 class MySourceIter(bt2._UserMessageIterator):
8d8b141d 488 def __init__(self, config, port):
0a6d7302
SM
489 tc, sc, ec = port.user_data
490 trace = tc()
491 stream = trace.create_stream(sc)
492 packet = stream.create_packet()
493
494 self._msgs = [
495 self._create_stream_beginning_message(stream),
496 self._create_packet_beginning_message(packet),
497 self._create_event_message(ec, packet),
498 self._create_event_message(ec, packet),
499 self._create_packet_end_message(packet),
500 self._create_stream_end_message(stream),
501 ]
502 self._at = 0
c0e46a7c 503 config.can_seek_forward = can_seek_forward
0a6d7302
SM
504
505 def __next__(self):
506 if self._at < len(self._msgs):
507 msg = self._msgs[self._at]
508 self._at += 1
509 return msg
510 else:
511 raise StopIteration
512
513 if user_seek_beginning is not None:
514 MySourceIter._user_seek_beginning = user_seek_beginning
515
516 if user_can_seek_beginning is not None:
14cfc8ce 517 MySourceIter._user_can_seek_beginning = user_can_seek_beginning
0a6d7302 518
c182d7dd
SM
519 if user_seek_ns_from_origin is not None:
520 MySourceIter._user_seek_ns_from_origin = user_seek_ns_from_origin
521
522 if user_can_seek_ns_from_origin is not None:
523 MySourceIter._user_can_seek_ns_from_origin = user_can_seek_ns_from_origin
524
0a6d7302 525 class MySource(bt2._UserSourceComponent, message_iterator_class=MySourceIter):
59225a3e 526 def __init__(self, config, params, obj):
0a6d7302
SM
527 tc = self._create_trace_class()
528 sc = tc.create_stream_class(supports_packets=True)
529 ec = sc.create_event_class()
530
f5567ea8 531 self._add_output_port("out", (tc, sc, ec))
0a6d7302
SM
532
533 class MyFilterIter(bt2._UserMessageIterator):
8d8b141d 534 def __init__(self, config, port):
9a2c8b8e 535 self._upstream_iter = self._create_message_iterator(
f5567ea8 536 self._component._input_ports["in"]
0a6d7302 537 )
c0e46a7c 538 config.can_seek_forward = self._upstream_iter.can_seek_forward
0a6d7302
SM
539
540 def __next__(self):
541 return next(self._upstream_iter)
542
0a6d7302 543 def _user_can_seek_beginning(self):
14cfc8ce 544 return self._upstream_iter.can_seek_beginning()
0a6d7302
SM
545
546 def _user_seek_beginning(self):
547 self._upstream_iter.seek_beginning()
548
c182d7dd
SM
549 def _user_can_seek_ns_from_origin(self, ns_from_origin):
550 return self._upstream_iter.can_seek_ns_from_origin(ns_from_origin)
551
552 def _user_seek_ns_from_origin(self, ns_from_origin):
553 self._upstream_iter.seek_ns_from_origin(ns_from_origin)
554
0a6d7302 555 class MyFilter(bt2._UserFilterComponent, message_iterator_class=MyFilterIter):
59225a3e 556 def __init__(self, config, params, obj):
f5567ea8
FD
557 self._add_input_port("in")
558 self._add_output_port("out")
0a6d7302
SM
559
560 return _create_graph(MySource, sink_cls, flt_comp_cls=MyFilter)
f00b8d40 561
0a6d7302
SM
562
563class UserMessageIteratorSeekBeginningTestCase(unittest.TestCase):
2e1b5615
SM
564 def test_can_seek_beginning_without_seek_beginning(self):
565 with self.assertRaisesRegex(
566 bt2._IncompleteUserClass,
567 "cannot create component class 'MySource': message iterator class implements _user_can_seek_beginning but not _user_seek_beginning",
568 ):
569 _setup_seek_test(SimpleSink, user_can_seek_beginning=lambda: None)
570
f00b8d40 571 def test_can_seek_beginning(self):
6c373cc9 572 class MySink(bt2._UserSinkComponent):
59225a3e 573 def __init__(self, config, params, obj):
f5567ea8 574 self._add_input_port("in")
6c373cc9
PP
575
576 def _user_graph_is_configured(self):
f5567ea8 577 self._msg_iter = self._create_message_iterator(self._input_ports["in"])
6c373cc9
PP
578
579 def _user_consume(self):
580 nonlocal can_seek_beginning
14cfc8ce 581 can_seek_beginning = self._msg_iter.can_seek_beginning()
6c373cc9 582
6a91742b 583 def _user_can_seek_beginning(self):
6c373cc9
PP
584 nonlocal input_port_iter_can_seek_beginning
585 return input_port_iter_can_seek_beginning
f00b8d40 586
0a6d7302 587 graph = _setup_seek_test(
2e1b5615
SM
588 MySink,
589 user_can_seek_beginning=_user_can_seek_beginning,
590 user_seek_beginning=lambda: None,
0a6d7302 591 )
f00b8d40 592
6c373cc9
PP
593 input_port_iter_can_seek_beginning = True
594 can_seek_beginning = None
595 graph.run_once()
7f0c21bb 596 self.assertIs(can_seek_beginning, True)
f00b8d40 597
6c373cc9
PP
598 input_port_iter_can_seek_beginning = False
599 can_seek_beginning = None
600 graph.run_once()
7f0c21bb 601 self.assertIs(can_seek_beginning, False)
f00b8d40 602
0a6d7302
SM
603 def test_no_can_seek_beginning_with_seek_beginning(self):
604 # Test an iterator without a _user_can_seek_beginning method, but with
605 # a _user_seek_beginning method.
606 class MySink(bt2._UserSinkComponent):
59225a3e 607 def __init__(self, config, params, obj):
f5567ea8 608 self._add_input_port("in")
0a6d7302
SM
609
610 def _user_graph_is_configured(self):
f5567ea8 611 self._msg_iter = self._create_message_iterator(self._input_ports["in"])
0a6d7302
SM
612
613 def _user_consume(self):
614 nonlocal can_seek_beginning
14cfc8ce 615 can_seek_beginning = self._msg_iter.can_seek_beginning()
0a6d7302
SM
616
617 def _user_seek_beginning(self):
618 pass
f00b8d40 619
0a6d7302 620 graph = _setup_seek_test(MySink, user_seek_beginning=_user_seek_beginning)
6c373cc9
PP
621 can_seek_beginning = None
622 graph.run_once()
7f0c21bb 623 self.assertIs(can_seek_beginning, True)
f00b8d40 624
0a6d7302
SM
625 def test_no_can_seek_beginning(self):
626 # Test an iterator without a _user_can_seek_beginning method, without
627 # a _user_seek_beginning method.
628 class MySink(bt2._UserSinkComponent):
59225a3e 629 def __init__(self, config, params, obj):
f5567ea8 630 self._add_input_port("in")
0a6d7302
SM
631
632 def _user_graph_is_configured(self):
f5567ea8 633 self._msg_iter = self._create_message_iterator(self._input_ports["in"])
0a6d7302
SM
634
635 def _user_consume(self):
636 nonlocal can_seek_beginning
14cfc8ce 637 can_seek_beginning = self._msg_iter.can_seek_beginning()
0a6d7302
SM
638
639 graph = _setup_seek_test(MySink)
6c373cc9
PP
640 can_seek_beginning = None
641 graph.run_once()
7f0c21bb 642 self.assertIs(can_seek_beginning, False)
f00b8d40 643
f2fb1b32
SM
644 def test_can_seek_beginning_user_error(self):
645 class MySink(bt2._UserSinkComponent):
59225a3e 646 def __init__(self, config, params, obj):
f5567ea8 647 self._add_input_port("in")
f2fb1b32
SM
648
649 def _user_graph_is_configured(self):
f5567ea8 650 self._msg_iter = self._create_message_iterator(self._input_ports["in"])
f2fb1b32
SM
651
652 def _user_consume(self):
653 # This is expected to raise.
14cfc8ce 654 self._msg_iter.can_seek_beginning()
f2fb1b32
SM
655
656 def _user_can_seek_beginning(self):
f5567ea8 657 raise ValueError("moustiquaire")
f2fb1b32
SM
658
659 graph = _setup_seek_test(
2e1b5615
SM
660 MySink,
661 user_can_seek_beginning=_user_can_seek_beginning,
662 user_seek_beginning=lambda: None,
f2fb1b32
SM
663 )
664
665 with self.assertRaises(bt2._Error) as ctx:
666 graph.run_once()
667
668 cause = ctx.exception[0]
f5567ea8 669 self.assertIn("ValueError: moustiquaire", cause.message)
f2fb1b32
SM
670
671 def test_can_seek_beginning_wrong_return_value(self):
672 class MySink(bt2._UserSinkComponent):
59225a3e 673 def __init__(self, config, params, obj):
f5567ea8 674 self._add_input_port("in")
f2fb1b32
SM
675
676 def _user_graph_is_configured(self):
f5567ea8 677 self._msg_iter = self._create_message_iterator(self._input_ports["in"])
f2fb1b32
SM
678
679 def _user_consume(self):
680 # This is expected to raise.
14cfc8ce 681 self._msg_iter.can_seek_beginning()
f2fb1b32
SM
682
683 def _user_can_seek_beginning(self):
f5567ea8 684 return "Amqui"
f2fb1b32
SM
685
686 graph = _setup_seek_test(
2e1b5615
SM
687 MySink,
688 user_can_seek_beginning=_user_can_seek_beginning,
689 user_seek_beginning=lambda: None,
f2fb1b32
SM
690 )
691
692 with self.assertRaises(bt2._Error) as ctx:
693 graph.run_once()
694
695 cause = ctx.exception[0]
696 self.assertIn("TypeError: 'str' is not a 'bool' object", cause.message)
697
f00b8d40 698 def test_seek_beginning(self):
6c373cc9 699 class MySink(bt2._UserSinkComponent):
59225a3e 700 def __init__(self, config, params, obj):
f5567ea8 701 self._add_input_port("in")
f00b8d40 702
6c373cc9 703 def _user_graph_is_configured(self):
f5567ea8 704 self._msg_iter = self._create_message_iterator(self._input_ports["in"])
6c373cc9
PP
705
706 def _user_consume(self):
707 nonlocal do_seek_beginning
708 nonlocal msg
709
710 if do_seek_beginning:
711 self._msg_iter.seek_beginning()
712 return
713
714 msg = next(self._msg_iter)
715
0a6d7302
SM
716 def _user_seek_beginning(self):
717 self._at = 0
718
6c373cc9 719 msg = None
0a6d7302
SM
720 graph = _setup_seek_test(MySink, user_seek_beginning=_user_seek_beginning)
721
722 # Consume message.
723 do_seek_beginning = False
6c373cc9 724 graph.run_once()
f0a42b33 725 self.assertIs(type(msg), bt2._StreamBeginningMessageConst)
0a6d7302
SM
726
727 # Consume message.
6c373cc9 728 graph.run_once()
f0a42b33 729 self.assertIs(type(msg), bt2._PacketBeginningMessageConst)
0a6d7302
SM
730
731 # Seek beginning.
6c373cc9
PP
732 do_seek_beginning = True
733 graph.run_once()
0a6d7302
SM
734
735 # Consume message.
6c373cc9
PP
736 do_seek_beginning = False
737 graph.run_once()
f0a42b33 738 self.assertIs(type(msg), bt2._StreamBeginningMessageConst)
f00b8d40 739
6c373cc9
PP
740 def test_seek_beginning_user_error(self):
741 class MySink(bt2._UserSinkComponent):
59225a3e 742 def __init__(self, config, params, obj):
f5567ea8 743 self._add_input_port("in")
f00b8d40 744
6c373cc9 745 def _user_graph_is_configured(self):
f5567ea8 746 self._msg_iter = self._create_message_iterator(self._input_ports["in"])
f00b8d40 747
6c373cc9
PP
748 def _user_consume(self):
749 self._msg_iter.seek_beginning()
f00b8d40 750
0a6d7302 751 def _user_seek_beginning(self):
f5567ea8 752 raise ValueError("ouch")
f00b8d40 753
0a6d7302 754 graph = _setup_seek_test(MySink, user_seek_beginning=_user_seek_beginning)
f00b8d40 755
694c792b 756 with self.assertRaises(bt2._Error):
6c373cc9 757 graph.run_once()
f00b8d40
SM
758
759
c182d7dd 760class UserMessageIteratorSeekNsFromOriginTestCase(unittest.TestCase):
2e1b5615
SM
761 def test_can_seek_ns_from_origin_without_seek_ns_from_origin(self):
762 # Test the case where:
763 #
764 # - can_seek_ns_from_origin: Returns True (don't really care, as long
765 # as it's provided)
766 # - seek_ns_from_origin provided: No
767 # - can the iterator seek beginning: Don't care
768 # - can the iterator seek forward: Don't care
769 for can_seek_ns_from_origin in (False, True):
770 for iter_can_seek_beginning in (False, True):
771 for iter_can_seek_forward in (False, True):
772 with self.assertRaisesRegex(
773 bt2._IncompleteUserClass,
774 "cannot create component class 'MySource': message iterator class implements _user_can_seek_ns_from_origin but not _user_seek_ns_from_origin",
775 ):
776 self._can_seek_ns_from_origin_test(
777 None,
778 user_can_seek_ns_from_origin_ret_val=True,
779 user_seek_ns_from_origin_provided=False,
780 iter_can_seek_beginning=iter_can_seek_beginning,
781 iter_can_seek_forward=iter_can_seek_forward,
782 )
783
c0e46a7c
SM
784 def test_can_seek_ns_from_origin_returns_true(self):
785 # Test the case where:
786 #
787 # - can_seek_ns_from_origin: returns True
2e1b5615 788 # - seek_ns_from_origin provided: Yes
c0e46a7c
SM
789 # - can the iterator seek beginning: Don't care
790 # - can the iterator seek forward: Don't care
791 #
792 # We expect iter.can_seek_ns_from_origin to return True.
2e1b5615
SM
793 for iter_can_seek_beginning in (False, True):
794 for iter_can_seek_forward in (False, True):
795 self._can_seek_ns_from_origin_test(
796 expected_outcome=True,
797 user_can_seek_ns_from_origin_ret_val=True,
798 user_seek_ns_from_origin_provided=True,
799 iter_can_seek_beginning=iter_can_seek_beginning,
800 iter_can_seek_forward=iter_can_seek_forward,
801 )
c0e46a7c
SM
802
803 def test_can_seek_ns_from_origin_returns_false_can_seek_beginning_forward_seekable(
75882e97 804 self,
c0e46a7c
SM
805 ):
806 # Test the case where:
807 #
808 # - can_seek_ns_from_origin: returns False
2e1b5615 809 # - seek_ns_from_origin provided: Yes
c0e46a7c
SM
810 # - can the iterator seek beginning: Yes
811 # - can the iterator seek forward: Yes
812 #
813 # We expect iter.can_seek_ns_from_origin to return True.
2e1b5615
SM
814 self._can_seek_ns_from_origin_test(
815 expected_outcome=True,
816 user_can_seek_ns_from_origin_ret_val=False,
817 user_seek_ns_from_origin_provided=True,
818 iter_can_seek_beginning=True,
819 iter_can_seek_forward=True,
820 )
c182d7dd 821
c0e46a7c 822 def test_can_seek_ns_from_origin_returns_false_can_seek_beginning_not_forward_seekable(
75882e97 823 self,
c0e46a7c
SM
824 ):
825 # Test the case where:
826 #
827 # - can_seek_ns_from_origin: returns False
2e1b5615 828 # - seek_ns_from_origin provided: Yes
c0e46a7c
SM
829 # - can the iterator seek beginning: Yes
830 # - can the iterator seek forward: No
831 #
832 # We expect iter.can_seek_ns_from_origin to return False.
2e1b5615
SM
833 self._can_seek_ns_from_origin_test(
834 expected_outcome=False,
835 user_can_seek_ns_from_origin_ret_val=False,
836 user_seek_ns_from_origin_provided=True,
837 iter_can_seek_beginning=True,
838 iter_can_seek_forward=False,
839 )
c182d7dd 840
c0e46a7c 841 def test_can_seek_ns_from_origin_returns_false_cant_seek_beginning_forward_seekable(
75882e97 842 self,
c0e46a7c
SM
843 ):
844 # Test the case where:
845 #
846 # - can_seek_ns_from_origin: returns False
2e1b5615 847 # - seek_ns_from_origin provided: Yes
c0e46a7c
SM
848 # - can the iterator seek beginning: No
849 # - can the iterator seek forward: Yes
850 #
851 # We expect iter.can_seek_ns_from_origin to return False.
c0e46a7c
SM
852 self._can_seek_ns_from_origin_test(
853 expected_outcome=False,
854 user_can_seek_ns_from_origin_ret_val=False,
2e1b5615 855 user_seek_ns_from_origin_provided=True,
c0e46a7c
SM
856 iter_can_seek_beginning=False,
857 iter_can_seek_forward=True,
858 )
859
860 def test_can_seek_ns_from_origin_returns_false_cant_seek_beginning_not_forward_seekable(
75882e97 861 self,
c0e46a7c
SM
862 ):
863 # Test the case where:
864 #
865 # - can_seek_ns_from_origin: returns False
2e1b5615 866 # - seek_ns_from_origin provided: Yes
c0e46a7c
SM
867 # - can the iterator seek beginning: No
868 # - can the iterator seek forward: No
869 #
870 # We expect iter.can_seek_ns_from_origin to return False.
2e1b5615
SM
871 self._can_seek_ns_from_origin_test(
872 expected_outcome=False,
873 user_can_seek_ns_from_origin_ret_val=False,
874 user_seek_ns_from_origin_provided=True,
875 iter_can_seek_beginning=False,
876 iter_can_seek_forward=False,
877 )
c0e46a7c
SM
878
879 def test_no_can_seek_ns_from_origin_seek_ns_from_origin(self):
880 # Test the case where:
881 #
882 # - can_seek_ns_from_origin: Not provided
883 # - seek_ns_from_origin provided: Yes
884 # - can the iterator seek beginning: Don't care
885 # - can the iterator seek forward: Don't care
886 #
887 # We expect iter.can_seek_ns_from_origin to return True.
888 for iter_can_seek_beginning in (False, True):
889 for iter_can_seek_forward in (False, True):
890 self._can_seek_ns_from_origin_test(
891 expected_outcome=True,
892 user_can_seek_ns_from_origin_ret_val=None,
893 user_seek_ns_from_origin_provided=True,
894 iter_can_seek_beginning=iter_can_seek_beginning,
895 iter_can_seek_forward=iter_can_seek_forward,
c182d7dd
SM
896 )
897
c0e46a7c 898 def test_no_can_seek_ns_from_origin_no_seek_ns_from_origin_can_seek_beginning_forward_seekable(
75882e97 899 self,
c0e46a7c
SM
900 ):
901 # Test the case where:
902 #
903 # - can_seek_ns_from_origin: Not provided
904 # - seek_ns_from_origin provided: Not provided
905 # - can the iterator seek beginning: Yes
906 # - can the iterator seek forward: Yes
907 #
908 # We expect iter.can_seek_ns_from_origin to return True.
909 self._can_seek_ns_from_origin_test(
910 expected_outcome=True,
911 user_can_seek_ns_from_origin_ret_val=None,
912 user_seek_ns_from_origin_provided=False,
913 iter_can_seek_beginning=True,
914 iter_can_seek_forward=True,
915 )
c182d7dd 916
c0e46a7c 917 def test_no_can_seek_ns_from_origin_no_seek_ns_from_origin_can_seek_beginning_not_forward_seekable(
75882e97 918 self,
c0e46a7c
SM
919 ):
920 # Test the case where:
921 #
922 # - can_seek_ns_from_origin: Not provided
923 # - seek_ns_from_origin provided: Not provided
924 # - can the iterator seek beginning: Yes
925 # - can the iterator seek forward: No
926 #
927 # We expect iter.can_seek_ns_from_origin to return False.
928 self._can_seek_ns_from_origin_test(
929 expected_outcome=False,
930 user_can_seek_ns_from_origin_ret_val=None,
931 user_seek_ns_from_origin_provided=False,
932 iter_can_seek_beginning=True,
933 iter_can_seek_forward=False,
c182d7dd
SM
934 )
935
c0e46a7c 936 def test_no_can_seek_ns_from_origin_no_seek_ns_from_origin_cant_seek_beginning_forward_seekable(
75882e97 937 self,
c0e46a7c
SM
938 ):
939 # Test the case where:
940 #
941 # - can_seek_ns_from_origin: Not provided
942 # - seek_ns_from_origin provided: Not provided
943 # - can the iterator seek beginning: No
944 # - can the iterator seek forward: Yes
945 #
946 # We expect iter.can_seek_ns_from_origin to return False.
947 self._can_seek_ns_from_origin_test(
948 expected_outcome=False,
949 user_can_seek_ns_from_origin_ret_val=None,
950 user_seek_ns_from_origin_provided=False,
951 iter_can_seek_beginning=False,
952 iter_can_seek_forward=True,
953 )
c182d7dd 954
c0e46a7c 955 def test_no_can_seek_ns_from_origin_no_seek_ns_from_origin_cant_seek_beginning_not_forward_seekable(
75882e97 956 self,
c0e46a7c
SM
957 ):
958 # Test the case where:
959 #
960 # - can_seek_ns_from_origin: Not provided
961 # - seek_ns_from_origin provided: Not provided
962 # - can the iterator seek beginning: No
963 # - can the iterator seek forward: No
964 #
965 # We expect iter.can_seek_ns_from_origin to return False.
966 self._can_seek_ns_from_origin_test(
967 expected_outcome=False,
968 user_can_seek_ns_from_origin_ret_val=None,
969 user_seek_ns_from_origin_provided=False,
970 iter_can_seek_beginning=False,
971 iter_can_seek_forward=False,
972 )
c182d7dd 973
c0e46a7c
SM
974 def _can_seek_ns_from_origin_test(
975 self,
976 expected_outcome,
977 user_can_seek_ns_from_origin_ret_val,
978 user_seek_ns_from_origin_provided,
979 iter_can_seek_beginning,
980 iter_can_seek_forward,
981 ):
c182d7dd 982 class MySink(bt2._UserSinkComponent):
59225a3e 983 def __init__(self, config, params, obj):
f5567ea8 984 self._add_input_port("in")
c182d7dd
SM
985
986 def _user_graph_is_configured(self):
f5567ea8 987 self._msg_iter = self._create_message_iterator(self._input_ports["in"])
c182d7dd
SM
988
989 def _user_consume(self):
990 nonlocal can_seek_ns_from_origin
c182d7dd 991 can_seek_ns_from_origin = self._msg_iter.can_seek_ns_from_origin(
c0e46a7c 992 passed_ns_from_origin
c182d7dd
SM
993 )
994
c0e46a7c 995 if user_can_seek_ns_from_origin_ret_val is not None:
c182d7dd 996
c0e46a7c
SM
997 def user_can_seek_ns_from_origin(self, ns_from_origin):
998 nonlocal received_ns_from_origin
999 received_ns_from_origin = ns_from_origin
1000 return user_can_seek_ns_from_origin_ret_val
c182d7dd 1001
c0e46a7c
SM
1002 else:
1003 user_can_seek_ns_from_origin = None
c182d7dd 1004
c0e46a7c 1005 if user_seek_ns_from_origin_provided:
c182d7dd 1006
c0e46a7c
SM
1007 def user_seek_ns_from_origin(self, ns_from_origin):
1008 pass
c182d7dd 1009
c0e46a7c
SM
1010 else:
1011 user_seek_ns_from_origin = None
c182d7dd 1012
c0e46a7c 1013 if iter_can_seek_beginning:
c182d7dd 1014
c0e46a7c
SM
1015 def user_seek_beginning(self):
1016 pass
c182d7dd 1017
c0e46a7c
SM
1018 else:
1019 user_seek_beginning = None
c182d7dd 1020
c0e46a7c
SM
1021 graph = _setup_seek_test(
1022 MySink,
1023 user_can_seek_ns_from_origin=user_can_seek_ns_from_origin,
1024 user_seek_ns_from_origin=user_seek_ns_from_origin,
1025 user_seek_beginning=user_seek_beginning,
1026 can_seek_forward=iter_can_seek_forward,
1027 )
c182d7dd 1028
c0e46a7c
SM
1029 passed_ns_from_origin = 77
1030 received_ns_from_origin = None
c182d7dd 1031 can_seek_ns_from_origin = None
c182d7dd 1032 graph.run_once()
c0e46a7c
SM
1033 self.assertIs(can_seek_ns_from_origin, expected_outcome)
1034
1035 if user_can_seek_ns_from_origin_ret_val is not None:
1036 self.assertEqual(received_ns_from_origin, passed_ns_from_origin)
c182d7dd
SM
1037
1038 def test_can_seek_ns_from_origin_user_error(self):
1039 class MySink(bt2._UserSinkComponent):
59225a3e 1040 def __init__(self, config, params, obj):
f5567ea8 1041 self._add_input_port("in")
c182d7dd
SM
1042
1043 def _user_graph_is_configured(self):
f5567ea8 1044 self._msg_iter = self._create_message_iterator(self._input_ports["in"])
c182d7dd
SM
1045
1046 def _user_consume(self):
1047 # This is expected to raise.
1048 self._msg_iter.can_seek_ns_from_origin(2)
1049
1050 def _user_can_seek_ns_from_origin(self, ns_from_origin):
f5567ea8 1051 raise ValueError("Joutel")
c182d7dd
SM
1052
1053 graph = _setup_seek_test(
2e1b5615
SM
1054 MySink,
1055 user_can_seek_ns_from_origin=_user_can_seek_ns_from_origin,
1056 user_seek_ns_from_origin=lambda: None,
c182d7dd
SM
1057 )
1058
1059 with self.assertRaises(bt2._Error) as ctx:
1060 graph.run_once()
1061
1062 cause = ctx.exception[0]
f5567ea8 1063 self.assertIn("ValueError: Joutel", cause.message)
c182d7dd
SM
1064
1065 def test_can_seek_ns_from_origin_wrong_return_value(self):
1066 class MySink(bt2._UserSinkComponent):
59225a3e 1067 def __init__(self, config, params, obj):
f5567ea8 1068 self._add_input_port("in")
c182d7dd
SM
1069
1070 def _user_graph_is_configured(self):
f5567ea8 1071 self._msg_iter = self._create_message_iterator(self._input_ports["in"])
c182d7dd
SM
1072
1073 def _user_consume(self):
1074 # This is expected to raise.
1075 self._msg_iter.can_seek_ns_from_origin(2)
1076
1077 def _user_can_seek_ns_from_origin(self, ns_from_origin):
f5567ea8 1078 return "Nitchequon"
c182d7dd
SM
1079
1080 graph = _setup_seek_test(
2e1b5615
SM
1081 MySink,
1082 user_can_seek_ns_from_origin=_user_can_seek_ns_from_origin,
1083 user_seek_ns_from_origin=lambda: None,
c182d7dd
SM
1084 )
1085
1086 with self.assertRaises(bt2._Error) as ctx:
1087 graph.run_once()
1088
1089 cause = ctx.exception[0]
1090 self.assertIn("TypeError: 'str' is not a 'bool' object", cause.message)
1091
1092 def test_seek_ns_from_origin(self):
1093 class MySink(bt2._UserSinkComponent):
59225a3e 1094 def __init__(self, config, params, obj):
f5567ea8 1095 self._add_input_port("in")
c182d7dd
SM
1096
1097 def _user_graph_is_configured(self):
f5567ea8 1098 self._msg_iter = self._create_message_iterator(self._input_ports["in"])
c182d7dd
SM
1099
1100 def _user_consume(self):
1101 self._msg_iter.seek_ns_from_origin(17)
1102
1103 def _user_seek_ns_from_origin(self, ns_from_origin):
1104 nonlocal actual_ns_from_origin
1105 actual_ns_from_origin = ns_from_origin
1106
c182d7dd
SM
1107 graph = _setup_seek_test(
1108 MySink, user_seek_ns_from_origin=_user_seek_ns_from_origin
1109 )
1110
1111 actual_ns_from_origin = None
1112 graph.run_once()
1113 self.assertEqual(actual_ns_from_origin, 17)
1114
1115
f5567ea8 1116if __name__ == "__main__":
f00b8d40 1117 unittest.main()
This page took 0.126459 seconds and 4 git commands to generate.