| # Copyright 2017 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| from recipe_engine import recipe_api |
| |
| import os |
| import re |
| import sys |
| import traceback |
| |
| |
| _STDLIB_PATH = os.path.dirname(traceback.__file__) |
| |
| _SKIPPING_MSG = ' <skipping stdlib frames that depend on python version>\n' |
| |
| _SANITIZED_DIRS = '|'.join(re.escape(d) for d in [ |
| '.recipe_deps', |
| 'infra', |
| 'recipe_engine', |
| 'recipe_modules', |
| 'recipes', |
| ]) |
| |
| |
| class TracebackApi(recipe_api.RecipeApi): |
| def _format_exc_filtered(self): |
| """Formats the exception, preprocessing and filtering stack frames. |
| |
| The implementation follows traceback.format_exc() with processing injected |
| in the middle. |
| """ |
| etype, value, tb = sys.exc_info() |
| if not tb: # pragma: no cover |
| return ''.join(traceback.format_exception_only(etype, value)) |
| |
| out = ['Traceback (most recent call last):\n'] |
| |
| pending = [] |
| for entry in traceback.extract_tb(tb): |
| entry = self._filter_frame(entry) |
| if entry: |
| pending.append(entry) |
| else: |
| # Stumbled upon a skipped frame. Flush all we have now and add a cut |
| # marker, if not already there. |
| out += traceback.format_list(pending) if pending else [] |
| pending = [] |
| if not out or out[-1] != _SKIPPING_MSG: |
| out.append(_SKIPPING_MSG) |
| out += traceback.format_list(pending) if pending else [] |
| out += traceback.format_exception_only(etype, value) |
| |
| out = [re.sub(r'line \d+,', 'line NA,', line) for line in out] |
| |
| return ''.join(out) |
| |
| def _filter_frame(self, frame): |
| filename, lineno, func, text = frame |
| |
| # Skip stdlib frames completely. They depend on version of Python. |
| if filename.startswith(_STDLIB_PATH): |
| return None |
| |
| # Make the traceback appear in "native" format by replacing the path |
| # separators. |
| filename = filename.replace(os.path.sep, self.m.path.sep) |
| |
| # Replace absolute paths to files with something that doesn't depend on the |
| # source checkout location. |
| filename = re.sub( |
| re.escape(self.m.path.sep).join(['.*', '('+ _SANITIZED_DIRS + ')', '']), |
| lambda m: self.m.path.sep.join(['<...>', m.group(1), '']), |
| filename) |
| |
| return filename, lineno, func, text |
| |
| def format_exc(self): |
| """Returns a string containing an exception traceback. |
| |
| Calls traceback.format_exc but during testing removes absolute paths. |
| """ |
| if self._test_data.enabled: |
| return self._format_exc_filtered() |
| return traceback.format_exc() # pragma: no cover |