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