OLD | NEW |
1 """ | 1 """ |
2 Internal global error types | 2 Internal global error types |
3 """ | 3 """ |
4 | 4 |
5 import sys, traceback | 5 import sys, traceback, threading, logging |
6 from traceback import format_exception | 6 from traceback import format_exception |
7 | 7 |
8 # Add names you want to be imported by 'from errors import *' to this list. | 8 # Add names you want to be imported by 'from errors import *' to this list. |
9 # This must be list not a tuple as we modify it to include all of our | 9 # This must be list not a tuple as we modify it to include all of our |
10 # the Exception classes we define below at the end of this file. | 10 # the Exception classes we define below at the end of this file. |
11 __all__ = ['format_error'] | 11 __all__ = ['format_error', 'context_aware', 'context', 'get_context', |
| 12 'exception_context'] |
12 | 13 |
13 | 14 |
14 def format_error(): | 15 def format_error(): |
15 t, o, tb = sys.exc_info() | 16 t, o, tb = sys.exc_info() |
16 trace = format_exception(t, o, tb) | 17 trace = format_exception(t, o, tb) |
17 # Clear the backtrace to prevent a circular reference | 18 # Clear the backtrace to prevent a circular reference |
18 # in the heap -- as per tutorial | 19 # in the heap -- as per tutorial |
19 tb = '' | 20 tb = '' |
20 | 21 |
21 return ''.join(trace) | 22 return ''.join(trace) |
22 | 23 |
23 | 24 |
| 25 # Exception context information: |
| 26 # ------------------------------ |
| 27 # Every function can have some context string associated with it. |
| 28 # The context string can be changed by calling context(str) and cleared by |
| 29 # calling context() with no parameters. |
| 30 # get_context() joins the current context strings of all functions in the |
| 31 # provided traceback. The result is a brief description of what the test was |
| 32 # doing in the provided traceback (which should be the traceback of a caught |
| 33 # exception). |
| 34 # |
| 35 # For example: assume a() calls b() and b() calls c(). |
| 36 # |
| 37 # @error.context_aware |
| 38 # def a(): |
| 39 # error.context("hello") |
| 40 # b() |
| 41 # error.context("world") |
| 42 # error.get_context() ----> 'world' |
| 43 # |
| 44 # @error.context_aware |
| 45 # def b(): |
| 46 # error.context("foo") |
| 47 # c() |
| 48 # |
| 49 # @error.context_aware |
| 50 # def c(): |
| 51 # error.context("bar") |
| 52 # error.get_context() ----> 'hello --> foo --> bar' |
| 53 # |
| 54 # The current context is automatically inserted into exceptions raised in |
| 55 # context_aware functions, so usually test code doesn't need to call |
| 56 # error.get_context(). |
| 57 |
| 58 ctx = threading.local() |
| 59 |
| 60 |
| 61 def _new_context(s=""): |
| 62 if not hasattr(ctx, "contexts"): |
| 63 ctx.contexts = [] |
| 64 ctx.contexts.append(s) |
| 65 |
| 66 |
| 67 def _pop_context(): |
| 68 ctx.contexts.pop() |
| 69 |
| 70 |
| 71 def context(s="", log=None): |
| 72 """ |
| 73 Set the context for the currently executing function and optionally log it. |
| 74 |
| 75 @param s: A string. If not provided, the context for the current function |
| 76 will be cleared. |
| 77 @param log: A logging function to pass the context message to. If None, no |
| 78 function will be called. |
| 79 """ |
| 80 ctx.contexts[-1] = s |
| 81 if s and log: |
| 82 log("Context: %s" % get_context()) |
| 83 |
| 84 |
| 85 def base_context(s="", log=None): |
| 86 """ |
| 87 Set the base context for the currently executing function and optionally |
| 88 log it. The base context is just another context level that is hidden by |
| 89 default. Functions that require a single context level should not use |
| 90 base_context(). |
| 91 |
| 92 @param s: A string. If not provided, the base context for the current |
| 93 function will be cleared. |
| 94 @param log: A logging function to pass the context message to. If None, no |
| 95 function will be called. |
| 96 """ |
| 97 ctx.contexts[-1] = "" |
| 98 ctx.contexts[-2] = s |
| 99 if s and log: |
| 100 log("Context: %s" % get_context()) |
| 101 |
| 102 |
| 103 def get_context(): |
| 104 """Return the current context (or None if none is defined).""" |
| 105 if hasattr(ctx, "contexts"): |
| 106 return " --> ".join([s for s in ctx.contexts if s]) |
| 107 |
| 108 |
| 109 def exception_context(e): |
| 110 """Return the context of a given exception (or None if none is defined).""" |
| 111 if hasattr(e, "_context"): |
| 112 return e._context |
| 113 |
| 114 |
| 115 def set_exception_context(e, s): |
| 116 """Set the context of a given exception.""" |
| 117 e._context = s |
| 118 |
| 119 |
| 120 def join_contexts(s1, s2): |
| 121 """Join two context strings.""" |
| 122 if s1: |
| 123 if s2: |
| 124 return "%s --> %s" % (s1, s2) |
| 125 else: |
| 126 return s1 |
| 127 else: |
| 128 return s2 |
| 129 |
| 130 |
| 131 def context_aware(fn): |
| 132 """A decorator that must be applied to functions that call context().""" |
| 133 def new_fn(*args, **kwargs): |
| 134 _new_context() |
| 135 _new_context("(%s)" % fn.__name__) |
| 136 try: |
| 137 try: |
| 138 return fn(*args, **kwargs) |
| 139 except Exception, e: |
| 140 if not exception_context(e): |
| 141 set_exception_context(e, get_context()) |
| 142 raise |
| 143 finally: |
| 144 _pop_context() |
| 145 _pop_context() |
| 146 new_fn.__name__ = fn.__name__ |
| 147 new_fn.__doc__ = fn.__doc__ |
| 148 new_fn.__dict__.update(fn.__dict__) |
| 149 return new_fn |
| 150 |
| 151 |
| 152 def _context_message(e): |
| 153 s = exception_context(e) |
| 154 if s: |
| 155 return " [context: %s]" % s |
| 156 else: |
| 157 return "" |
| 158 |
| 159 |
24 class JobContinue(SystemExit): | 160 class JobContinue(SystemExit): |
25 """Allow us to bail out requesting continuance.""" | 161 """Allow us to bail out requesting continuance.""" |
26 pass | 162 pass |
27 | 163 |
28 | 164 |
29 class JobComplete(SystemExit): | 165 class JobComplete(SystemExit): |
30 """Allow us to bail out indicating continuation not required.""" | 166 """Allow us to bail out indicating continuation not required.""" |
31 pass | 167 pass |
32 | 168 |
33 | 169 |
34 class AutotestError(Exception): | 170 class AutotestError(Exception): |
35 """The parent of all errors deliberatly thrown within the client code.""" | 171 """The parent of all errors deliberatly thrown within the client code.""" |
36 pass | 172 def __str__(self): |
| 173 return Exception.__str__(self) + _context_message(self) |
37 | 174 |
38 | 175 |
39 class JobError(AutotestError): | 176 class JobError(AutotestError): |
40 """Indicates an error which terminates and fails the whole job (ABORT).""" | 177 """Indicates an error which terminates and fails the whole job (ABORT).""" |
41 pass | 178 pass |
42 | 179 |
43 | 180 |
44 class UnhandledJobError(JobError): | 181 class UnhandledJobError(JobError): |
45 """Indicates an unhandled error in a job.""" | 182 """Indicates an unhandled error in a job.""" |
46 def __init__(self, unhandled_exception): | 183 def __init__(self, unhandled_exception): |
47 if isinstance(unhandled_exception, JobError): | 184 if isinstance(unhandled_exception, JobError): |
48 JobError.__init__(self, *unhandled_exception.args) | 185 JobError.__init__(self, *unhandled_exception.args) |
| 186 elif isinstance(unhandled_exception, str): |
| 187 JobError.__init__(self, unhandled_exception) |
49 else: | 188 else: |
50 msg = "Unhandled %s: %s" | 189 msg = "Unhandled %s: %s" |
51 msg %= (unhandled_exception.__class__.__name__, | 190 msg %= (unhandled_exception.__class__.__name__, |
52 unhandled_exception) | 191 unhandled_exception) |
| 192 if not isinstance(unhandled_exception, AutotestError): |
| 193 msg += _context_message(unhandled_exception) |
53 msg += "\n" + traceback.format_exc() | 194 msg += "\n" + traceback.format_exc() |
54 JobError.__init__(self, msg) | 195 JobError.__init__(self, msg) |
55 | 196 |
56 | 197 |
57 class TestBaseException(AutotestError): | 198 class TestBaseException(AutotestError): |
58 """The parent of all test exceptions.""" | 199 """The parent of all test exceptions.""" |
59 # Children are required to override this. Never instantiate directly. | 200 # Children are required to override this. Never instantiate directly. |
60 exit_status="NEVER_RAISE_THIS" | 201 exit_status="NEVER_RAISE_THIS" |
61 | 202 |
62 | 203 |
(...skipping 17 matching lines...) Expand all Loading... |
80 """Indicates that bad things (may) have happened, but not an explicit | 221 """Indicates that bad things (may) have happened, but not an explicit |
81 failure.""" | 222 failure.""" |
82 exit_status="WARN" | 223 exit_status="WARN" |
83 | 224 |
84 | 225 |
85 class UnhandledTestError(TestError): | 226 class UnhandledTestError(TestError): |
86 """Indicates an unhandled error in a test.""" | 227 """Indicates an unhandled error in a test.""" |
87 def __init__(self, unhandled_exception): | 228 def __init__(self, unhandled_exception): |
88 if isinstance(unhandled_exception, TestError): | 229 if isinstance(unhandled_exception, TestError): |
89 TestError.__init__(self, *unhandled_exception.args) | 230 TestError.__init__(self, *unhandled_exception.args) |
| 231 elif isinstance(unhandled_exception, str): |
| 232 TestError.__init__(self, unhandled_exception) |
90 else: | 233 else: |
91 msg = "Unhandled %s: %s" | 234 msg = "Unhandled %s: %s" |
92 msg %= (unhandled_exception.__class__.__name__, | 235 msg %= (unhandled_exception.__class__.__name__, |
93 unhandled_exception) | 236 unhandled_exception) |
| 237 if not isinstance(unhandled_exception, AutotestError): |
| 238 msg += _context_message(unhandled_exception) |
94 msg += "\n" + traceback.format_exc() | 239 msg += "\n" + traceback.format_exc() |
95 TestError.__init__(self, msg) | 240 TestError.__init__(self, msg) |
96 | 241 |
97 | 242 |
98 class UnhandledTestFail(TestFail): | 243 class UnhandledTestFail(TestFail): |
99 """Indicates an unhandled fail in a test.""" | 244 """Indicates an unhandled fail in a test.""" |
100 def __init__(self, unhandled_exception): | 245 def __init__(self, unhandled_exception): |
101 if isinstance(unhandled_exception, TestFail): | 246 if isinstance(unhandled_exception, TestFail): |
102 TestFail.__init__(self, *unhandled_exception.args) | 247 TestFail.__init__(self, *unhandled_exception.args) |
| 248 elif isinstance(unhandled_exception, str): |
| 249 TestFail.__init__(self, unhandled_exception) |
103 else: | 250 else: |
104 msg = "Unhandled %s: %s" | 251 msg = "Unhandled %s: %s" |
105 msg %= (unhandled_exception.__class__.__name__, | 252 msg %= (unhandled_exception.__class__.__name__, |
106 unhandled_exception) | 253 unhandled_exception) |
| 254 if not isinstance(unhandled_exception, AutotestError): |
| 255 msg += _context_message(unhandled_exception) |
107 msg += "\n" + traceback.format_exc() | 256 msg += "\n" + traceback.format_exc() |
108 TestFail.__init__(self, msg) | 257 TestFail.__init__(self, msg) |
109 | 258 |
110 | 259 |
111 class CmdError(TestError): | 260 class CmdError(TestError): |
112 """\ | 261 """\ |
113 Indicates that a command failed, is fatal to the test unless caught. | 262 Indicates that a command failed, is fatal to the test unless caught. |
114 """ | 263 """ |
115 def __init__(self, command, result_obj, additional_text=None): | 264 def __init__(self, command, result_obj, additional_text=None): |
116 TestError.__init__(self, command, result_obj, additional_text) | 265 TestError.__init__(self, command, result_obj, additional_text) |
117 self.command = command | 266 self.command = command |
118 self.result_obj = result_obj | 267 self.result_obj = result_obj |
119 self.additional_text = additional_text | 268 self.additional_text = additional_text |
120 | 269 |
121 | |
122 def __str__(self): | 270 def __str__(self): |
123 if self.result_obj.exit_status is None: | 271 if self.result_obj.exit_status is None: |
124 msg = "Command <%s> failed and is not responding to signals" | 272 msg = "Command <%s> failed and is not responding to signals" |
125 msg %= self.command | 273 msg %= self.command |
126 else: | 274 else: |
127 msg = "Command <%s> failed, rc=%d" | 275 msg = "Command <%s> failed, rc=%d" |
128 msg %= (self.command, self.result_obj.exit_status) | 276 msg %= (self.command, self.result_obj.exit_status) |
129 | 277 |
130 if self.additional_text: | 278 if self.additional_text: |
131 msg += ", " + self.additional_text | 279 msg += ", " + self.additional_text |
| 280 msg += _context_message(self) |
132 msg += '\n' + repr(self.result_obj) | 281 msg += '\n' + repr(self.result_obj) |
133 return msg | 282 return msg |
134 | 283 |
135 | 284 |
136 class PackageError(TestError): | 285 class PackageError(TestError): |
137 """Indicates an error trying to perform a package operation.""" | 286 """Indicates an error trying to perform a package operation.""" |
138 pass | 287 pass |
139 | 288 |
140 | 289 |
141 class BarrierError(JobError): | 290 class BarrierError(JobError): |
(...skipping 193 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
335 | 484 |
336 # This MUST remain at the end of the file. | 485 # This MUST remain at the end of the file. |
337 # Limit 'from error import *' to only import the exception instances. | 486 # Limit 'from error import *' to only import the exception instances. |
338 for _name, _thing in locals().items(): | 487 for _name, _thing in locals().items(): |
339 try: | 488 try: |
340 if issubclass(_thing, Exception): | 489 if issubclass(_thing, Exception): |
341 __all__.append(_name) | 490 __all__.append(_name) |
342 except TypeError: | 491 except TypeError: |
343 pass # _thing not a class | 492 pass # _thing not a class |
344 __all__ = tuple(__all__) | 493 __all__ = tuple(__all__) |
OLD | NEW |