def test_get_hash_with_only_required_vars(self): interface = Stacktrace(frames=[{ 'lineno': 1, 'filename': 'foo.py', }]) result = interface.get_hash() self.assertEquals(result, ['foo.py', 1])
def test_serialize_returns_frames(self): interface = Stacktrace(frames=[{ 'lineno': 1, 'filename': 'foo.py', }]) result = interface.serialize() assert 'frames' in result
def test_get_composite_hash_uses_exception_if_present(self): interface = Stacktrace( frames=[{"context_line": "foo bar", "lineno": 1, "filename": "foo.py", "function": "bar"}] ) interface_exc = Exception(type="exception", value="bar") result = interface.get_composite_hash({"sentry.interfaces.Exception": interface_exc}) self.assertEquals(result[-1], "exception")
def test_serialize_returns_frames(self): interface = Stacktrace(frames=[{ 'lineno': 1, 'filename': 'foo.py', }]) result = interface.serialize() self.assertTrue('frames' in result)
def test_get_composite_hash_uses_exception_value_if_no_type_or_stack(self): interface = Stacktrace(frames=[]) interface_exc = Exception(value='bar') result = interface.get_composite_hash({ 'sentry.interfaces.Exception': interface_exc, }) self.assertEquals(result[-1], 'bar')
def test_get_hash_ignores_filename_if_http(self): interface = Stacktrace(frames=[{ 'context_line': 'hello world', 'filename': 'http://foo.com/foo.py', }]) result = interface.get_hash() self.assertEquals(result, ['hello world'])
def test_get_hash_ignores_filename_if_https(self): interface = Stacktrace(frames=[{ 'context_line': 'hello world', 'filename': 'https://foo.com/foo.py', }]) result = interface.get_hash() self.assertEquals(result, ['hello world'])
def test_get_traceback_response(self, get_stacktrace): event = mock.Mock(spec=Event()) event.message = "foo" get_stacktrace.return_value = "bar" interface = Stacktrace(frames=[{"lineno": 1, "filename": "foo.py"}]) result = interface.get_traceback(event) get_stacktrace.assert_called_once_with(event, newest_first=None) self.assertEquals(result, "foo\n\nbar")
def test_get_hash_sanitizes_block_functions(self): # This is Ruby specific interface = Stacktrace(frames=[{ 'filename': 'foo.py', 'function': 'block in _conditional_callback_around_233', }]) result = interface.get_hash() self.assertEquals(result, ['foo.py', 'block'])
def test_get_traceback_response(self, get_stacktrace): event = mock.Mock(spec=Event()) event.message = 'foo' get_stacktrace.return_value = 'bar' interface = Stacktrace(frames=[{'lineno': 1, 'filename': 'foo.py'}]) result = interface.get_traceback(event) get_stacktrace.assert_called_once_with(event, newest_first=None) self.assertEquals(result, 'foo\n\nbar')
def test_get_traceback_response(self, get_stacktrace): event = mock.Mock(spec=Event) event.message = 'foo' get_stacktrace.return_value = 'bar' interface = Stacktrace(frames=[]) result = interface.get_traceback(event) get_stacktrace.assert_called_once_with(event) self.assertEquals(result, 'foo\n\nbar')
def test_get_hash_uses_module_over_filename(self): interface = Stacktrace(frames=[{ 'lineno': 1, 'filename': 'foo.py', 'module': 'foo' }]) result = interface.get_hash() self.assertEquals(result, ['foo', 1])
def test_to_html_response(self, get_traceback): event = mock.Mock(spec=Event()) event.message = 'foo' get_traceback.return_value = 'bar' interface = Stacktrace(frames=[{'lineno': 1, 'filename': 'foo.py'}]) result = interface.to_html(event) get_traceback.assert_called_once_with(event, newest_first=False) self.assertTrue('<div class="module">' in result)
def test_get_hash_uses_function_over_lineno(self): interface = Stacktrace(frames=[{ 'lineno': 1, 'filename': 'foo.py', 'function': 'bar' }]) result = interface.get_hash() self.assertEquals(result, ['foo.py', 'bar'])
def test_to_string_returns_stacktrace(self, get_stacktrace): event = mock.Mock(spec=Event()) interface = Stacktrace(frames=[]) result = interface.to_string(event) get_stacktrace.assert_called_once_with(event, system_frames=False, max_frames=5) self.assertEquals(result, get_stacktrace.return_value)
def test_get_stacktrace_with_module(self): event = mock.Mock(spec=Event()) interface = Stacktrace(frames=[{'module': 'foo'}, {'module': 'bar'}]) result = interface.get_stacktrace(event) self.assertEquals( result, 'Stacktrace (most recent call last):\n\n Module "foo"\n Module "bar"' )
def test_to_html_response(self, get_traceback): event = mock.Mock(spec=Event) event.message = 'foo' get_traceback.return_value = 'bar' interface = Stacktrace(frames=[]) result = interface.to_html(event) get_traceback.assert_called_once_with(event) self.assertTrue('<div id="traceback" class="module">' in result)
def test_get_hash_sanitizes_erb_templates(self): # This is Ruby specific interface = Stacktrace(frames=[{ 'filename': 'foo.html.erb', 'function': '_foo_html_erb__3327151541118998292_70361296749460', }]) result = interface.get_hash() self.assertEquals(result, [ 'foo.html.erb', '_foo_html_erb__<anon>_<anon>', ])
def test_requires_filename(self): with self.assertRaises(AssertionError): Stacktrace(frames=[{}]).validate() Stacktrace(frames=[{ 'filename': 'foo.py', }]).validate() Stacktrace(frames=[{ 'lineno': 1, 'filename': 'foo.py', }]).validate()
def test_requires_filename_and_lineno(self): self.assertRaises(AssertionError, Stacktrace, frames=[{ 'lineno': 1, }]) Stacktrace(frames=[{ 'filename': 'foo.py', }]) Stacktrace(frames=[{ 'lineno': 1, 'filename': 'foo.py', }])
def test_to_html_render_call(self, render_to_string, get_traceback): event = mock.Mock(spec=Event) get_traceback.return_value = 'bar' interface = Stacktrace(frames=[]) result = interface.to_html(event) get_traceback.assert_called_once_with(event) render_to_string.assert_called_once_with('sentry/partial/interfaces/stacktrace.html', { 'event': event, 'frames': [], 'stacktrace': 'bar', }) self.assertEquals(result, render_to_string.return_value)
def test_get_composite_hash_uses_exception_if_present(self): interface = Stacktrace(frames=[{ 'context_line': 'foo bar', 'lineno': 1, 'filename': 'foo.py', 'function': 'bar' }]) interface_exc = Exception(type='exception', value='bar') result = interface.get_composite_hash({ 'sentry.interfaces.Exception': interface_exc, }) self.assertEquals(result[-1], 'exception')
def test_get_stacktrace_with_filename_function_lineno_and_context(self): event = mock.Mock(spec=Event()) interface = Stacktrace( frames=[ {"filename": "foo", "function": "biz", "lineno": 3, "context_line": " def foo(r):"}, {"filename": "bar", "function": "baz", "lineno": 5, "context_line": " return None"}, ] ) result = interface.get_stacktrace(event) self.assertEquals( result, 'Stacktrace (most recent call last):\n\n File "foo", line 3, in biz\n def foo(r):\n File "bar", line 5, in baz\n return None', )
def test_get_stacktrace_with_filename_and_function(self): event = mock.Mock(spec=Event()) interface = Stacktrace(frames=[{ 'filename': 'foo', 'function': 'biz' }, { 'filename': 'bar', 'function': 'baz' }]) result = interface.get_stacktrace(event) self.assertEquals( result, 'Stacktrace (most recent call last):\n\n File "foo", in biz\n File "bar", in baz' )
def test_to_html_render_call(self, render_to_string, get_traceback): event = mock.Mock(spec=Event()) get_traceback.return_value = 'bar' interface = Stacktrace(frames=[{'lineno': 1, 'filename': 'foo.py'}]) result = interface.to_html(event) get_traceback.assert_called_once_with(event, newest_first=False) render_to_string.assert_called_once_with('sentry/partial/interfaces/stacktrace.html', { 'event': event, 'frames': [{'function': None, 'abs_path': None, 'start_lineno': None, 'lineno': 1, 'context': [], 'vars': [], 'in_app': True, 'filename': 'foo.py'}], 'stacktrace': 'bar', 'system_frames': 0, 'newest_first': False, }) self.assertEquals(result, render_to_string.return_value)
def test_to_html_render_call(self, get_frame_context, render_to_string, get_traceback): event = mock.Mock(spec=Event()) get_traceback.return_value = 'bar' interface = Stacktrace(frames=[{'lineno': 1, 'filename': 'foo.py'}]) result = interface.to_html(event) get_traceback.assert_called_once_with(event, newest_first=False) get_frame_context.assert_called_once_with(interface.frames[0], event=event, is_public=False) render_to_string.assert_called_once_with('sentry/partial/interfaces/stacktrace.html', { 'event': event, 'frames': [get_frame_context.return_value], 'stacktrace': 'bar', 'system_frames': 0, 'newest_first': False, 'is_public': False, }) self.assertEquals(result, render_to_string.return_value)
def test_legacy_interface(self): # Simple test to ensure legacy data works correctly with the ``Frame`` # objects event = self.event interface = Stacktrace(**event.data['sentry.interfaces.Stacktrace']) assert len(interface.frames) == 5 assert interface == event.interfaces['sentry.interfaces.Stacktrace']
def test_ignores_results_with_empty_path(self): interface = Stacktrace(frames=[{ 'lineno': 1, 'filename': 'http://foo.com', }]) frame = interface.frames[0] assert frame.filename == 'http://foo.com' assert frame.abs_path == frame.filename
def test_coerces_url_filenames(self): interface = Stacktrace(frames=[{ 'lineno': 1, 'filename': 'http://foo.com/foo.js', }]) frame = interface.frames[0] assert frame.filename == '/foo.js' assert frame.abs_path == 'http://foo.com/foo.js'
def interface(self): return Stacktrace(frames=[{ 'filename': 'foo/bar.py' }, { 'filename': 'foo/baz.py', 'lineno': 1, 'in_app': True, }])
def test_allows_abs_path_without_filename(self): interface = Stacktrace(frames=[{ 'lineno': 1, 'abs_path': 'foo/bar/baz.py', }]) frame = interface.frames[0] assert frame.filename == 'foo/bar/baz.py' assert frame.abs_path == frame.filename
def test_get_stacktrace_with_filename_function_lineno_and_context(self): event = mock.Mock(spec=Event()) interface = Stacktrace(frames=[{ 'filename': 'foo', 'function': 'biz', 'lineno': 3, 'context_line': ' def foo(r):' }, { 'filename': 'bar', 'function': 'baz', 'lineno': 5, 'context_line': ' return None' }]) result = interface.get_stacktrace(event) self.assertEquals( result, 'Stacktrace (most recent call last):\n\n File "foo", line 3, in biz\n def foo(r):\n File "bar", line 5, in baz\n return None' )
def test_to_html_render_call(self, get_frame_context, render_to_string, get_traceback): event = mock.Mock(spec=Event()) get_traceback.return_value = 'bar' interface = Stacktrace(frames=[{'lineno': 1, 'filename': 'foo.py'}]) result = interface.to_html(event) get_traceback.assert_called_once_with(event, newest_first=False) get_frame_context.assert_called_once_with(event=event, is_public=False) render_to_string.assert_called_once_with( 'sentry/partial/interfaces/stacktrace.html', { 'event': event, 'frames': [get_frame_context.return_value], 'stacktrace': 'bar', 'stack_id': 'stacktrace_1', 'system_frames': 0, 'newest_first': False, 'is_public': False, }) self.assertEquals(result, render_to_string.return_value)
def test_get_hash_sanitizes_versioned_filenames(self): # This is Ruby specific interface = Stacktrace(frames=[{ 'filename': '/data/foo/releases/20140114151955/app/views/foo.html.erb', 'context_line': '<% if @hotels.size > 0 %>', }]) result = interface.get_hash() self.assertEquals(result, [ '/data/foo/releases/<version>/app/views/foo.html.erb', '<% if @hotels.size > 0 %>', ]) interface = Stacktrace(frames=[{ 'filename': '20140114151955/app/views/foo.html.erb', 'context_line': '<% if @hotels.size > 0 %>', }]) result = interface.get_hash() self.assertEquals(result, [ '<version>/app/views/foo.html.erb', '<% if @hotels.size > 0 %>', ])
def test_to_html_render_call(self, get_frame_context, render_to_string, get_traceback): event = mock.Mock(spec=Event()) get_traceback.return_value = "bar" interface = Stacktrace(frames=[{"lineno": 1, "filename": "foo.py"}]) result = interface.to_html(event) get_traceback.assert_called_once_with(event, newest_first=False) get_frame_context.assert_called_once_with(event=event, is_public=False) render_to_string.assert_called_once_with( "sentry/partial/interfaces/stacktrace.html", { "event": event, "frames": [get_frame_context.return_value], "stacktrace": "bar", "stack_id": "stacktrace_1", "system_frames": 0, "newest_first": False, "is_public": False, "first_frame_omitted": None, "last_frame_omitted": None, }, ) self.assertEquals(result, render_to_string.return_value)
def test_get_hash_uses_context_line_over_function(self): interface = Stacktrace( frames=[{"context_line": "foo bar", "lineno": 1, "filename": "foo.py", "function": "bar"}] ) result = interface.get_hash() self.assertEquals(result, ["foo.py", "foo bar"])
def test_serialize_returns_frames(self): interface = Stacktrace(frames=[{"lineno": 1, "filename": "foo.py"}]) result = interface.serialize() assert "frames" in result
def test_get_stacktrace_with_module(self): event = mock.Mock(spec=Event()) interface = Stacktrace(frames=[{"module": "foo"}, {"module": "bar"}]) result = interface.get_stacktrace(event) self.assertEquals(result, 'Stacktrace (most recent call last):\n\n Module "foo"\n Module "bar"')
def expand_javascript_source(data, **kwargs): """ Attempt to fetch source code for javascript frames. Frames must match the following requirements: - lineno >= 0 - colno >= 0 - abs_path is the HTTP URI to the source - context_line is empty Mutates the input ``data`` with expanded context if available. """ from sentry.interfaces import Stacktrace try: stacktrace = Stacktrace(**data['sentry.interfaces.Stacktrace']) except KeyError: logger.debug('No stacktrace for event %r', data['event_id']) return # build list of frames that we can actually grab source for frames = [ f for f in stacktrace.frames if f.lineno is not None and f.is_url() ] if not frames: logger.debug('Event %r has no frames with enough context to fetch remote source', data['event_id']) return data file_list = set() sourcemap_capable = set() source_code = {} sourcemaps = {} for f in frames: file_list.add(f.abs_path) if f.colno is not None: sourcemap_capable.add(f.abs_path) while file_list: filename = file_list.pop() # TODO: respect cache-contro/max-age headers to some extent logger.debug('Fetching remote source %r', filename) result = fetch_url(filename) if result == BAD_SOURCE: continue # If we didn't have a colno, a sourcemap wont do us any good if filename not in sourcemap_capable: source_code[filename] = (result.body.splitlines(), None) continue # TODO: we're currently running splitlines twice sourcemap = discover_sourcemap(result, logger=logger) source_code[filename] = (result.body.splitlines(), sourcemap) if sourcemap: logger.debug('Found sourcemap %r for minified script %r', sourcemap, result.url) # pull down sourcemap if sourcemap and sourcemap not in sourcemaps: index = fetch_sourcemap(sourcemap, logger=logger) if not index: continue sourcemaps[sourcemap] = index # queue up additional source files for download for source in index.sources: if source not in source_code: file_list.add(urljoin(result.url, source)) has_changes = False for frame in frames: try: source, sourcemap = source_code[frame.abs_path] except KeyError: # we must've failed pulling down the source continue # may have had a failure pulling down the sourcemap previously if sourcemap in sourcemaps and frame.colno is not None: state = find_source(sourcemaps[sourcemap], frame.lineno, frame.colno) # TODO: is this urljoin right? (is it relative to the sourcemap or the originating file) abs_path = urljoin(sourcemap, state.src) logger.debug('Mapping compressed source %r to mapping in %r', frame.abs_path, abs_path) try: source, _ = source_code[abs_path] except KeyError: pass else: # Store original data in annotation frame.data = { 'orig_lineno': frame['lineno'], 'orig_colno': frame['colno'], 'orig_function': frame['function'], 'orig_abs_path': frame['abs_path'], 'orig_filename': frame['filename'], 'sourcemap': sourcemap, } # SourceMap's return zero-indexed lineno's frame.lineno = state.src_line + 1 frame.colno = state.src_col frame.function = state.name frame.abs_path = abs_path frame.filename = state.src has_changes = True # TODO: theoretically a minified source could point to another mapped, minified source frame.pre_context, frame.context_line, frame.post_context = get_source_context( source=source, lineno=frame.lineno) if has_changes: data['sentry.interfaces.Stacktrace'] = stacktrace.serialize()
def convert_legacy_kwargs(self, kwargs): from sentry.interfaces import Http, User, Exception, Stacktrace, Template from sentry.utils.template_info import get_template_info data = kwargs.pop('data', None) or {} sentry = data.pop('__sentry__', None) or {} result = { 'event_id': kwargs.pop('message_id', None), 'level': kwargs.pop('level', None), 'logger': kwargs.pop('logger', None), 'server_name': kwargs.pop('server_name', None), 'message': kwargs.pop('message', ''), 'culprit': kwargs.pop('view', None), 'timestamp': kwargs.pop('timestamp', None), } result = dict((k, v) for k, v in result.iteritems() if v is not None) class_name = kwargs.pop('class_name', None) if class_name: result['message'] = '%s: %s' % (class_name, result['message']) if 'url' in data or 'url' in kwargs and 'META' in data: meta = data.pop('META', {}) if 'GET' in data: del data['GET'] result['sentry.interfaces.Http'] = Http( url=data.pop('url', None) or kwargs['url'], method=meta.get('REQUEST_METHOD'), query_string=meta.get('QUERY_STRING'), data=data.pop('POST', None), cookies=data.pop('COOKIES', None), env=meta, ).serialize() if 'user' in sentry: user = sentry['user'] result['sentry.interfaces.User'] = User( **user ).serialize() if 'exception' in sentry: exc = sentry['exception'] result['sentry.interfaces.Exception'] = Exception( type=exc[0], value=u' '.join(itertools.imap(unicode, exc[1])), ).serialize() if 'frames' in sentry: frames = [] keys = ('filename', 'function', 'vars', 'pre_context', 'context_line', 'post_context', 'lineno') for frame in sentry['frames']: if 'vars' in frame: frame['vars'] = dict(frame['vars']) frames.append(dict((k, v) for k, v in frame.iteritems() if k in keys)) if frames: result['sentry.interfaces.Stacktrace'] = Stacktrace( frames=frames, ).serialize() if 'template' in sentry: template = sentry['template'] print get_template_info(template) result['sentry.interfaces.Template'] = Template( **get_template_info(template) ).serialize() result['extra'] = data return result
def test_get_stacktrace_with_only_filename(self): event = mock.Mock(spec=Event()) interface = Stacktrace(frames=[{'filename': 'foo'}, {'filename': 'bar'}]) result = interface.get_stacktrace(event) self.assertEquals(result, 'Stacktrace (most recent call last):\n\n File "foo"\n File "bar"')
def expand_javascript_source(data, **kwargs): """ Attempt to fetch source code for javascript frames. Frames must match the following requirements: - lineno >= 0 - colno >= 0 - abs_path is the HTTP URI to the source - context_line is empty Mutates the input ``data`` with expanded context if available. """ from sentry.interfaces import Stacktrace try: stacktraces = [ Stacktrace(**e['stacktrace']) for e in data['sentry.interfaces.Exception']['values'] if e.get('stacktrace') ] except KeyError: stacktraces = [] if not stacktraces: logger.debug('No stacktrace for event %r', data['event_id']) return # build list of frames that we can actually grab source for frames = [] for stacktrace in stacktraces: frames.extend([ f for f in stacktrace.frames if f.lineno is not None and f.is_url() ]) if not frames: logger.debug( 'Event %r has no frames with enough context to fetch remote source', data['event_id']) return data pending_file_list = set() done_file_list = set() sourcemap_capable = set() source_code = {} sourmap_idxs = {} for f in frames: pending_file_list.add(f.abs_path) if f.colno is not None: sourcemap_capable.add(f.abs_path) while pending_file_list: filename = pending_file_list.pop() done_file_list.add(filename) # TODO: respect cache-contro/max-age headers to some extent logger.debug('Fetching remote source %r', filename) result = fetch_url(filename) if result == BAD_SOURCE: logger.debug('Bad source file %r', filename) continue # If we didn't have a colno, a sourcemap wont do us any good if filename not in sourcemap_capable: logger.debug('Not capable of sourcemap: %r', filename) source_code[filename] = (result.body.splitlines(), None) continue sourcemap = discover_sourcemap(result) # TODO: we're currently running splitlines twice if not sourcemap: source_code[filename] = (result.body.splitlines(), None) for f in frames: if f.abs_path == filename: f.module = generate_module(filename) continue else: logger.debug('Found sourcemap %r for minified script %r', sourcemap[:256], result.url) sourcemap_key = hashlib.md5(sourcemap).hexdigest() source_code[filename] = (result.body.splitlines(), sourcemap_key) if sourcemap in sourmap_idxs: continue # pull down sourcemap index = fetch_sourcemap(sourcemap) if not index: logger.debug('Failed parsing sourcemap index: %r', sourcemap[:15]) continue if is_data_uri(sourcemap): sourmap_idxs[sourcemap_key] = (index, result.url) else: sourmap_idxs[sourcemap_key] = (index, sourcemap) # queue up additional source files for download for source in index.sources: next_filename = urljoin(sourcemap, source) if next_filename not in done_file_list: if index.content: source_code[next_filename] = (index.content[source], None) done_file_list.add(next_filename) else: pending_file_list.add(next_filename) last_state = None state = None has_changes = False for frame in frames: try: source, sourcemap = source_code[frame.abs_path] except KeyError: # we must've failed pulling down the source continue # may have had a failure pulling down the sourcemap previously if sourcemap in sourmap_idxs and frame.colno is not None: index, relative_to = sourmap_idxs[sourcemap] last_state = state state = find_source(index, frame.lineno, frame.colno) abs_path = urljoin(relative_to, state.src) logger.debug('Mapping compressed source %r to mapping in %r', frame.abs_path, abs_path) try: source, _ = source_code[abs_path] except KeyError: frame.data = { 'sourcemap': sourcemap, } logger.debug('Failed mapping path %r', abs_path) else: # Store original data in annotation frame.data = { 'orig_lineno': frame.lineno, 'orig_colno': frame.colno, 'orig_function': frame.function, 'orig_abs_path': frame.abs_path, 'orig_filename': frame.filename, 'sourcemap': sourcemap, } # SourceMap's return zero-indexed lineno's frame.lineno = state.src_line + 1 frame.colno = state.src_col # The offending function is always the previous function in the stack # Honestly, no idea what the bottom most frame is, so we're ignoring that atm frame.function = last_state.name if last_state else state.name frame.abs_path = abs_path frame.filename = state.src frame.module = generate_module(state.src) or '<unknown module>' elif sourcemap in sourmap_idxs: frame.data = { 'sourcemap': sourcemap, } has_changes = True # TODO: theoretically a minified source could point to another mapped, minified source frame.pre_context, frame.context_line, frame.post_context = get_source_context( source=source, lineno=frame.lineno) if has_changes: logger.debug('Updating stacktraces with expanded source context') for exception, stacktrace in itertools.izip( data['sentry.interfaces.Exception']['values'], stacktraces): exception['stacktrace'] = stacktrace.serialize() # Attempt to fix the culrpit now that we have useful information culprit_frame = stacktraces[0].frames[-1] if culprit_frame.module and culprit_frame.function: data['culprit'] = truncatechars(generate_culprit(culprit_frame), MAX_CULPRIT_LENGTH)
def test_get_stacktrace_with_filename_and_function(self): event = mock.Mock(spec=Event()) interface = Stacktrace(frames=[{'filename': 'foo', 'function': 'biz'}, {'filename': 'bar', 'function': 'baz'}]) result = interface.get_stacktrace(event) self.assertEquals(result, 'Stacktrace (most recent call last):\n\n File "foo", in biz\n File "bar", in baz')
def test_get_stacktrace_with_filename_function_lineno_and_context(self): event = mock.Mock(spec=Event()) interface = Stacktrace(frames=[{'filename': 'foo', 'function': 'biz', 'lineno': 3, 'context_line': ' def foo(r):'}, {'filename': 'bar', 'function': 'baz', 'lineno': 5, 'context_line': ' return None'}]) result = interface.get_stacktrace(event) self.assertEquals(result, 'Stacktrace (most recent call last):\n\n File "foo", line 3, in biz\n def foo(r):\n File "bar", line 5, in baz\n return None')
def test_get_hash_uses_module_over_filename(self): interface = Stacktrace(frames=[{"lineno": 1, "filename": "foo.py", "module": "foo"}]) result = interface.get_hash() self.assertEquals(result, ["foo", 1])
def test_to_string_returns_stacktrace(self, get_stacktrace): event = mock.Mock(spec=Event) interface = Stacktrace(frames=[]) result = interface.to_string(event) get_stacktrace.assert_called_once_with(event) self.assertEquals(result, get_stacktrace.return_value)
def expand_javascript_source(data, **kwargs): """ Attempt to fetch source code for javascript frames. Frames must match the following requirements: - lineno >= 0 - colno >= 0 - abs_path is the HTTP URI to the source - context_line is empty Mutates the input ``data`` with expanded context if available. """ from sentry.interfaces import Stacktrace try: stacktraces = [ Stacktrace(**e['stacktrace']) for e in data['sentry.interfaces.Exception']['values'] if e.get('stacktrace') ] except KeyError: stacktraces = [] if not stacktraces: logger.debug('No stacktrace for event %r', data['event_id']) return # build list of frames that we can actually grab source for frames = [] for stacktrace in stacktraces: frames.extend([ f for f in stacktrace.frames if f.lineno is not None and f.is_url() ]) if not frames: logger.debug( 'Event %r has no frames with enough context to fetch remote source', data['event_id']) return data pending_file_list = set() done_file_list = set() sourcemap_capable = set() source_code = {} sourmap_idxs = {} for f in frames: pending_file_list.add(f.abs_path) if f.colno is not None: sourcemap_capable.add(f.abs_path) while pending_file_list: filename = pending_file_list.pop() done_file_list.add(filename) # TODO: respect cache-contro/max-age headers to some extent logger.debug('Fetching remote source %r', filename) result = fetch_url(filename) if result == BAD_SOURCE: logger.debug('Bad source file %r', filename) continue # If we didn't have a colno, a sourcemap wont do us any good if filename not in sourcemap_capable: logger.debug('Not capable of sourcemap: %r', filename) source_code[filename] = (result.body.splitlines(), None) continue sourcemap = discover_sourcemap(result) source_code[filename] = (result.body.splitlines(), sourcemap) # TODO: we're currently running splitlines twice if sourcemap: logger.debug('Found sourcemap %r for minified script %r', sourcemap, result.url) elif sourcemap in sourmap_idxs or not sourcemap: continue # pull down sourcemap index = fetch_sourcemap(sourcemap) if not index: logger.debug('Failed parsing sourcemap index: %r', sourcemap[:15]) continue sourmap_idxs[sourcemap] = index # queue up additional source files for download for source in index.sources: next_filename = urljoin(result.url, source) if next_filename not in done_file_list: pending_file_list.add(next_filename) has_changes = False for frame in frames: try: source, sourcemap = source_code[frame.abs_path] except KeyError: # we must've failed pulling down the source continue # may have had a failure pulling down the sourcemap previously if sourcemap in sourmap_idxs and frame.colno is not None: state = find_source(sourmap_idxs[sourcemap], frame.lineno, frame.colno) # TODO: is this urljoin right? (is it relative to the sourcemap or the originating file) abs_path = urljoin(sourcemap, state.src) logger.debug('Mapping compressed source %r to mapping in %r', frame.abs_path, abs_path) try: source, _ = source_code[abs_path] except KeyError: frame.data = { 'sourcemap': sourcemap, } logger.debug('Failed mapping path %r', abs_path) else: # Store original data in annotation frame.data = { 'orig_lineno': frame['lineno'], 'orig_colno': frame['colno'], 'orig_function': frame['function'], 'orig_abs_path': frame['abs_path'], 'orig_filename': frame['filename'], 'sourcemap': sourcemap, } # SourceMap's return zero-indexed lineno's frame.lineno = state.src_line + 1 frame.colno = state.src_col frame.function = state.name frame.abs_path = abs_path frame.filename = state.src has_changes = True # TODO: theoretically a minified source could point to another mapped, minified source frame.pre_context, frame.context_line, frame.post_context = get_source_context( source=source, lineno=frame.lineno) if has_changes: logger.debug('Updating stacktraces with expanded source context') for exception, stacktrace in itertools.izip( data['sentry.interfaces.Exception']['values'], stacktraces): exception['stacktrace'] = stacktrace.serialize()