| OLD | NEW |
| 1 # Copyright 2013 The LUCI Authors. All rights reserved. | 1 # Copyright 2013 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 import contextlib | 5 import contextlib |
| 6 import datetime | 6 import datetime |
| 7 import functools | 7 import functools |
| 8 import logging | 8 import logging |
| 9 import os | 9 import os |
| 10 import sys | 10 import sys |
| (...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 133 for root, _dirs, files in os.walk(path): | 133 for root, _dirs, files in os.walk(path): |
| 134 for file_name in (f for f in files if predicate(f)): | 134 for file_name in (f for f in files if predicate(f)): |
| 135 file_path = os.path.join(root, file_name) | 135 file_path = os.path.join(root, file_name) |
| 136 yield file_path | 136 yield file_path |
| 137 | 137 |
| 138 | 138 |
| 139 BUG_LINK = ( | 139 BUG_LINK = ( |
| 140 'https://code.google.com/p/chromium/issues/entry?%s' % urllib.urlencode({ | 140 'https://code.google.com/p/chromium/issues/entry?%s' % urllib.urlencode({ |
| 141 'summary': 'Recipe engine bug: unexpected failure', | 141 'summary': 'Recipe engine bug: unexpected failure', |
| 142 'comment': 'Link to the failing build and paste the exception here', | 142 'comment': 'Link to the failing build and paste the exception here', |
| 143 'labels': 'Infra,Infra-Area-Recipes,Pri-1,Restrict-View-Google,Infra-Tro
opers', | 143 'labels': 'Infra,Infra-Area-Recipes,Pri-1,Restrict-View-Google,' |
| 144 'Infra-Troopers', |
| 144 'cc': 'martiniss@chromium.org,iannucci@chromium.org', | 145 'cc': 'martiniss@chromium.org,iannucci@chromium.org', |
| 145 })) | 146 })) |
| 146 | 147 |
| 147 | 148 |
| 148 @contextlib.contextmanager | 149 @contextlib.contextmanager |
| 149 def raises(exc_cls, stream_engine=None): | 150 def raises(exc_cls, stream_engine=None): |
| 150 """If the body raises an exception not in exc_cls, print and abort the engine. | 151 """If the body raises an exception not in exc_cls, print and abort the engine. |
| 151 | 152 |
| 152 This is so that we have something to go on when a function goes wrong, yet the | 153 This is so that we have something to go on when a function goes wrong, yet the |
| 153 exception is covered up by something else (e.g. an error in a finally block). | 154 exception is covered up by something else (e.g. an error in a finally block). |
| (...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 221 try: | 222 try: |
| 222 return f(*args, **kwargs) | 223 return f(*args, **kwargs) |
| 223 except Exception as e: | 224 except Exception as e: |
| 224 if (i+1) >= self.retries or not self.condition(e): | 225 if (i+1) >= self.retries or not self.condition(e): |
| 225 raise | 226 raise |
| 226 logging.exception('Exception encountered, retrying in %s', | 227 logging.exception('Exception encountered, retrying in %s', |
| 227 retry_delay) | 228 retry_delay) |
| 228 time.sleep(retry_delay.total_seconds()) | 229 time.sleep(retry_delay.total_seconds()) |
| 229 retry_delay *= 2 | 230 retry_delay *= 2 |
| 230 return wrapper | 231 return wrapper |
| 232 |
| 233 |
| 234 class MultiException(Exception): |
| 235 """An exception that aggregates multiple exceptions and summarizes them.""" |
| 236 |
| 237 class Builder(object): |
| 238 """Iteratively constructs a MultiException.""" |
| 239 |
| 240 def __init__(self): |
| 241 self._exceptions = [] |
| 242 |
| 243 def append(self, exc): |
| 244 if exc is not None: |
| 245 self._exceptions.append(exc) |
| 246 |
| 247 def get(self): |
| 248 """Returns (MultiException or None): The constructed MultiException. |
| 249 |
| 250 If no exceptions have been appended, None will be returned. |
| 251 """ |
| 252 return MultiException(*self._exceptions) if self._exceptions else (None) |
| 253 |
| 254 def raise_if_any(self): |
| 255 mexc = self.get() |
| 256 if mexc is not None: |
| 257 raise mexc |
| 258 |
| 259 @contextlib.contextmanager |
| 260 def catch(self, *exc_types): |
| 261 """ContextManager that catches any exception raised during its execution |
| 262 and adds them to the MultiException. |
| 263 |
| 264 Args: |
| 265 exc_types (list): A list of exception classes to catch. If empty, |
| 266 Exception will be used. |
| 267 """ |
| 268 exc_types = exc_types or (Exception,) |
| 269 try: |
| 270 yield |
| 271 except exc_types as exc: |
| 272 self.append(exc) |
| 273 |
| 274 |
| 275 def __init__(self, *base): |
| 276 super(MultiException, self).__init__() |
| 277 |
| 278 # Determine base Exception text. |
| 279 if len(base) == 0: |
| 280 self.message = 'No exceptions' |
| 281 elif len(base) == 1: |
| 282 self.message = str(base[0]) |
| 283 else: |
| 284 self.message = str(base[0]) + ', and %d more...' % (len(base)-1) |
| 285 self._inner = base |
| 286 |
| 287 def __nonzero__(self): |
| 288 return bool(self._inner) |
| 289 |
| 290 def __len__(self): |
| 291 return len(self._inner) |
| 292 |
| 293 def __getitem__(self, key): |
| 294 return self._inner[key] |
| 295 |
| 296 def __iter__(self): |
| 297 return iter(self._inner) |
| 298 |
| 299 def __str__(self): |
| 300 return '%s(%s)' % (type(self).__name__, self.message) |
| 301 |
| 302 |
| 303 @contextlib.contextmanager |
| 304 def map_defer_exceptions(fn, it, *exc_types): |
| 305 """Executes "fn" for each element in "it". Any exceptions thrown by "fn" will |
| 306 be deferred until the end of "it", then raised as a single MultiException. |
| 307 |
| 308 Args: |
| 309 fn (callable): A function to call for each element in "it". |
| 310 it (iterable): An iterable to traverse. |
| 311 exc_types (list): An optional list of specific exception types to defer. |
| 312 If empty, Exception will be used. Any Exceptions not referenced by this |
| 313 list will skip deferring and be immediately raised. |
| 314 """ |
| 315 mexc_builder = MultiException.Builder() |
| 316 for e in it: |
| 317 with mexc_builder.catch(*exc_types): |
| 318 fn(e) |
| 319 mexc_builder.raise_if_any() |
| OLD | NEW |