Commit | Line | Data |
---|---|---|
0235b0db | 1 | # SPDX-License-Identifier: GPL-2.0-only |
ce4923b0 SM |
2 | # |
3 | # Copyright (C) 2019 EfficiOS Inc. | |
4 | # | |
ce4923b0 | 5 | |
ce4923b0 SM |
6 | import unittest |
7 | ||
5995b304 SM |
8 | import bt2 |
9 | from bt2 import native_bt | |
10 | ||
ce4923b0 SM |
11 | |
12 | class FailingIter(bt2._UserMessageIterator): | |
13 | def __next__(self): | |
f5567ea8 | 14 | raise ValueError("User message iterator is failing") |
ce4923b0 SM |
15 | |
16 | ||
17 | class SourceWithFailingIter( | |
18 | bt2._UserSourceComponent, message_iterator_class=FailingIter | |
19 | ): | |
59225a3e | 20 | def __init__(self, config, params, obj): |
f5567ea8 | 21 | self._add_output_port("out") |
ce4923b0 SM |
22 | |
23 | ||
24 | class SourceWithFailingInit( | |
25 | bt2._UserSourceComponent, message_iterator_class=FailingIter | |
26 | ): | |
59225a3e | 27 | def __init__(self, config, params, obj): |
f5567ea8 | 28 | raise ValueError("Source is failing") |
ce4923b0 SM |
29 | |
30 | ||
31 | class WorkingSink(bt2._UserSinkComponent): | |
59225a3e | 32 | def __init__(self, config, params, obj): |
f5567ea8 | 33 | self._in = self._add_input_port("in") |
ce4923b0 | 34 | |
6a91742b | 35 | def _user_graph_is_configured(self): |
9a2c8b8e | 36 | self._iter = self._create_message_iterator(self._in) |
ce4923b0 | 37 | |
6a91742b | 38 | def _user_consume(self): |
ce4923b0 SM |
39 | next(self._iter) |
40 | ||
41 | ||
42 | class SinkWithExceptionChaining(bt2._UserSinkComponent): | |
59225a3e | 43 | def __init__(self, config, params, obj): |
f5567ea8 | 44 | self._in = self._add_input_port("in") |
ce4923b0 | 45 | |
6a91742b | 46 | def _user_graph_is_configured(self): |
9a2c8b8e | 47 | self._iter = self._create_message_iterator(self._in) |
ce4923b0 | 48 | |
6a91742b | 49 | def _user_consume(self): |
ce4923b0 | 50 | try: |
ce4923b0 | 51 | next(self._iter) |
694c792b | 52 | except bt2._Error as e: |
f5567ea8 | 53 | raise ValueError("oops") from e |
ce4923b0 SM |
54 | |
55 | ||
56 | class SinkWithFailingQuery(bt2._UserSinkComponent): | |
6a91742b | 57 | def _user_consume(self): |
ce4923b0 SM |
58 | pass |
59 | ||
60 | @staticmethod | |
7c14d641 | 61 | def _user_query(priv_executor, obj, params, method_obj): |
f5567ea8 | 62 | raise ValueError("Query is failing") |
ce4923b0 SM |
63 | |
64 | ||
65 | class ErrorTestCase(unittest.TestCase): | |
66 | def _run_failing_graph(self, source_cc, sink_cc): | |
694c792b | 67 | with self.assertRaises(bt2._Error) as ctx: |
ce4923b0 | 68 | graph = bt2.Graph() |
f5567ea8 FD |
69 | src = graph.add_component(source_cc, "src") |
70 | snk = graph.add_component(sink_cc, "snk") | |
71 | graph.connect_ports(src.output_ports["out"], snk.input_ports["in"]) | |
ce4923b0 SM |
72 | graph.run() |
73 | ||
74 | return ctx.exception | |
75 | ||
76 | def test_current_thread_error_none(self): | |
694c792b | 77 | # When a bt2._Error is raised, it steals the current thread's error. |
ce4923b0 | 78 | # Verify that it is now NULL. |
082db648 | 79 | self._run_failing_graph(SourceWithFailingInit, WorkingSink) |
ce4923b0 SM |
80 | self.assertIsNone(native_bt.current_thread_take_error()) |
81 | ||
82 | def test_len(self): | |
83 | exc = self._run_failing_graph(SourceWithFailingIter, WorkingSink) | |
84 | ||
85 | # The exact number of causes is not too important (it can change if we | |
86 | # append more or less causes along the way), but the idea is to verify is | |
87 | # has a value that makes sense. | |
88 | self.assertEqual(len(exc), 4) | |
89 | ||
90 | def test_iter(self): | |
91 | exc = self._run_failing_graph(SourceWithFailingIter, WorkingSink) | |
92 | ||
93 | for c in exc: | |
94 | # Each cause is an instance of _ErrorCause (including subclasses). | |
3fb99a22 | 95 | self.assertIsInstance(c, bt2._ErrorCause) |
ce4923b0 SM |
96 | |
97 | def test_getitem(self): | |
98 | exc = self._run_failing_graph(SourceWithFailingIter, WorkingSink) | |
99 | ||
100 | for i in range(len(exc)): | |
101 | c = exc[i] | |
102 | # Each cause is an instance of _ErrorCause (including subclasses). | |
3fb99a22 | 103 | self.assertIsInstance(c, bt2._ErrorCause) |
ce4923b0 SM |
104 | |
105 | def test_getitem_indexerror(self): | |
106 | exc = self._run_failing_graph(SourceWithFailingIter, WorkingSink) | |
107 | ||
108 | with self.assertRaises(IndexError): | |
109 | exc[len(exc)] | |
110 | ||
111 | def test_exception_chaining(self): | |
112 | # Test that if we do: | |
113 | # | |
114 | # try: | |
115 | # ... | |
694c792b | 116 | # except bt2._Error as exc: |
ce4923b0 SM |
117 | # raise ValueError('oh noes') from exc |
118 | # | |
694c792b | 119 | # We are able to fetch the causes of the original bt2._Error in the |
ce4923b0 SM |
120 | # exception chain. Also, each exception in the chain should become one |
121 | # cause once caught. | |
122 | exc = self._run_failing_graph(SourceWithFailingIter, SinkWithExceptionChaining) | |
123 | ||
124 | self.assertEqual(len(exc), 5) | |
125 | ||
3fb99a22 | 126 | self.assertIsInstance(exc[0], bt2._MessageIteratorErrorCause) |
f5567ea8 FD |
127 | self.assertEqual(exc[0].component_class_name, "SourceWithFailingIter") |
128 | self.assertIn("ValueError: User message iterator is failing", exc[0].message) | |
ce4923b0 | 129 | |
3fb99a22 | 130 | self.assertIsInstance(exc[1], bt2._ErrorCause) |
ce4923b0 | 131 | |
3fb99a22 | 132 | self.assertIsInstance(exc[2], bt2._ComponentErrorCause) |
f5567ea8 | 133 | self.assertEqual(exc[2].component_class_name, "SinkWithExceptionChaining") |
ce4923b0 | 134 | self.assertIn( |
f5567ea8 | 135 | "unexpected error: cannot advance the message iterator", exc[2].message |
ce4923b0 SM |
136 | ) |
137 | ||
3fb99a22 | 138 | self.assertIsInstance(exc[3], bt2._ComponentErrorCause) |
f5567ea8 FD |
139 | self.assertEqual(exc[3].component_class_name, "SinkWithExceptionChaining") |
140 | self.assertIn("ValueError: oops", exc[3].message) | |
ce4923b0 | 141 | |
3fb99a22 | 142 | self.assertIsInstance(exc[4], bt2._ErrorCause) |
ce4923b0 SM |
143 | |
144 | def _common_cause_tests(self, cause): | |
145 | self.assertIsInstance(cause.module_name, str) | |
146 | self.assertIsInstance(cause.file_name, str) | |
147 | self.assertIsInstance(cause.line_number, int) | |
148 | ||
149 | def test_unknown_error_cause(self): | |
150 | exc = self._run_failing_graph(SourceWithFailingIter, SinkWithExceptionChaining) | |
151 | cause = exc[-1] | |
3fb99a22 | 152 | self.assertIs(type(cause), bt2._ErrorCause) |
ce4923b0 SM |
153 | self._common_cause_tests(cause) |
154 | ||
155 | def test_component_error_cause(self): | |
156 | exc = self._run_failing_graph(SourceWithFailingInit, SinkWithExceptionChaining) | |
157 | cause = exc[0] | |
3fb99a22 | 158 | self.assertIs(type(cause), bt2._ComponentErrorCause) |
ce4923b0 SM |
159 | self._common_cause_tests(cause) |
160 | ||
f5567ea8 FD |
161 | self.assertIn("Source is failing", cause.message) |
162 | self.assertEqual(cause.component_name, "src") | |
ce4923b0 | 163 | self.assertEqual(cause.component_class_type, bt2.ComponentClassType.SOURCE) |
f5567ea8 | 164 | self.assertEqual(cause.component_class_name, "SourceWithFailingInit") |
ce4923b0 SM |
165 | self.assertIsNone(cause.plugin_name) |
166 | ||
167 | def test_component_class_error_cause(self): | |
f5567ea8 | 168 | q = bt2.QueryExecutor(SinkWithFailingQuery, "hello") |
ce4923b0 | 169 | |
694c792b | 170 | with self.assertRaises(bt2._Error) as ctx: |
3c729b9a | 171 | q.query() |
ce4923b0 SM |
172 | |
173 | cause = ctx.exception[0] | |
3fb99a22 | 174 | self.assertIs(type(cause), bt2._ComponentClassErrorCause) |
ce4923b0 SM |
175 | self._common_cause_tests(cause) |
176 | ||
f5567ea8 | 177 | self.assertIn("Query is failing", cause.message) |
ce4923b0 SM |
178 | |
179 | self.assertEqual(cause.component_class_type, bt2.ComponentClassType.SINK) | |
f5567ea8 | 180 | self.assertEqual(cause.component_class_name, "SinkWithFailingQuery") |
ce4923b0 SM |
181 | self.assertIsNone(cause.plugin_name) |
182 | ||
183 | def test_message_iterator_error_cause(self): | |
184 | exc = self._run_failing_graph(SourceWithFailingIter, SinkWithExceptionChaining) | |
185 | cause = exc[0] | |
3fb99a22 | 186 | self.assertIs(type(cause), bt2._MessageIteratorErrorCause) |
ce4923b0 SM |
187 | self._common_cause_tests(cause) |
188 | ||
f5567ea8 FD |
189 | self.assertIn("User message iterator is failing", cause.message) |
190 | self.assertEqual(cause.component_name, "src") | |
191 | self.assertEqual(cause.component_output_port_name, "out") | |
ce4923b0 | 192 | self.assertEqual(cause.component_class_type, bt2.ComponentClassType.SOURCE) |
f5567ea8 | 193 | self.assertEqual(cause.component_class_name, "SourceWithFailingIter") |
ce4923b0 | 194 | self.assertIsNone(cause.plugin_name) |
d14ddbba | 195 | |
8258c4bd SM |
196 | def test_str(self): |
197 | # Test __str__. We don't need to test the precise format used, but | |
198 | # just that it doesn't miserably crash and that it contains some | |
199 | # expected bits. | |
200 | exc = self._run_failing_graph(SourceWithFailingIter, SinkWithExceptionChaining) | |
201 | s = str(exc) | |
5038e256 | 202 | self.assertIn("[src (out): 'source.SourceWithFailingIter']", s) |
f5567ea8 | 203 | self.assertIn("ValueError: oops", s) |
8258c4bd | 204 | |
d14ddbba | 205 | |
f5567ea8 | 206 | if __name__ == "__main__": |
d14ddbba | 207 | unittest.main() |