Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(775)

Side by Side Diff: client/common_lib/error.py

Issue 6246035: Merge remote branch 'cros/upstream' into master (Closed) Base URL: ssh://git@gitrw.chromium.org:9222/autotest.git@master
Patch Set: patch Created 9 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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__)
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698