blob: 186d2e71b7ad5345c095eab61ddd313174e8119b [file] [log] [blame]
# 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