Files
2024-02-15 17:01:50 +00:00

379 lines
11 KiB
TypeScript

import {render, screen} from 'sentry-test/reactTestingLibrary';
import {textWithMarkupMatcher} from 'sentry-test/utils';
import {
analyzeFrameForRootCause,
analyzeFramesForRootCause,
} from 'sentry/components/events/interfaces/analyzeFrames';
import type {Event, Frame} from 'sentry/types/event';
import {EntryType, EventOrGroupType, LockType} from 'sentry/types/event';
const makeEventWithFrames = (frames: Frame[]): Event => {
const event: Event = {
id: '020eb33f6ce64ed6adc60f8993535816',
groupID: '68',
eventID: '020eb33f6ce64ed6adc60f8993535816',
projectID: '2',
size: 3481,
entries: [
{
data: {
values: [
{
type: 'ZeroDivisionError',
value: 'divided by 0',
mechanism: null,
threadId: null,
module: '',
stacktrace: {
frames: [
{
filename: 'puma (3.12.6) lib/puma/thread_pool.rb',
absPath: 'puma (3.12.6) lib/puma/thread_pool.rb',
module: null,
package: null,
platform: null,
instructionAddr: null,
symbolAddr: null,
function: 'block in spawn_thread',
rawFunction: null,
symbol: null,
context: [],
lineNo: 135,
colNo: null,
inApp: false,
trust: null,
vars: null,
},
...frames,
],
framesOmitted: null,
registers: null,
hasSystemFrames: true,
},
rawStacktrace: null,
frames: null,
},
],
hasSystemFrames: true,
excOmitted: null,
},
type: EntryType.EXCEPTION,
},
{
data: {
values: [
{
id: 13920,
current: true,
crashed: true,
name: 'puma 002',
stacktrace: null,
rawStacktrace: null,
state: 'WAITING',
},
],
},
type: EntryType.THREADS,
},
],
dist: null,
message: '',
title: 'ZeroDivisionError: divided by 0',
location: 'sentry/controllers/welcome_controller.rb',
user: null,
contexts: {},
sdk: null,
context: {},
packages: {},
type: EventOrGroupType.ERROR,
metadata: {
display_title_with_tree_label: false,
filename: 'sentry/controllers/welcome_controller.rb',
finest_tree_label: [
{filebase: 'welcome_controller.rb', function: '/'},
{filebase: 'welcome_controller.rb', function: 'index'},
],
function: '/',
type: 'ZeroDivisionError',
value: 'divided by 0',
},
tags: [{key: 'level', value: 'error'}],
platform: 'other',
dateReceived: '2021-10-28T12:28:22.318469Z',
errors: [],
crashFile: null,
culprit: 'sentry/controllers/welcome_controller.rb in /',
dateCreated: '2021-10-28T12:28:22.318469Z',
fingerprints: ['58f1f47bea5239ea25397888dc9253d1'],
groupingConfig: {
enhancements: 'eJybzDRxY25-UmZOqpWRgZGhroGJroHRBABbUQb_',
id: 'mobile:2021-02-12',
},
release: null,
userReport: null,
sdkUpdates: [],
nextEventID: null,
previousEventID: null,
occurrence: null,
};
return event;
};
describe('analyzeAnrFrames', function () {
it('detects anr root cause', function () {
const event = makeEventWithFrames([
{
filename: 'SharedPreferencesImpl.java',
absPath: 'SharedPreferencesImpl.java',
module: 'android.app.SharedPreferencesImpl$EditorImpl$1',
package: null,
platform: null,
instructionAddr: null,
symbolAddr: null,
function: 'run',
rawFunction: null,
symbol: null,
context: [],
lineNo: 366,
colNo: null,
inApp: false,
trust: null,
vars: null,
},
]);
const rootCause = analyzeFramesForRootCause(event);
expect(rootCause?.resources).toEqual(
'SharedPreferences.apply will save data on background thread only if it happens before the activity/service finishes. Switch to SharedPreferences.commit and move commit to a background thread.'
);
expect(rootCause?.culprit).toEqual(
'/^android\\.app\\.SharedPreferencesImpl\\$EditorImpl\\$[0-9]/'
);
});
it('regex matches function names', function () {
const event = makeEventWithFrames([
{
filename: 'sqlite.SQLiteConnection',
absPath: 'sqlite.SQLiteConnection',
module: 'android.database.sqlite.SQLiteConnection',
package: null,
platform: null,
instructionAddr: null,
symbolAddr: null,
function: 'nativeBindArgs',
rawFunction: null,
symbol: null,
context: [],
lineNo: 366,
colNo: null,
inApp: false,
trust: null,
vars: null,
},
]);
const rootCause = analyzeFramesForRootCause(event);
expect(rootCause?.resources).toEqual(
'Database operations, such as querying, inserting, updating, or deleting data, can involve disk I/O, processing, and potentially long-running operations. Move database operations off the main thread to avoid this ANR.'
);
expect(rootCause?.culprit).toEqual('android.database.sqlite.SQLiteConnection');
});
it('picks anr root cause of the topmost frame', function () {
const event = makeEventWithFrames([
{
filename: 'Instrumentation.java',
absPath: 'Instrumentation.java',
module: 'android.app.Instrumentation',
package: null,
platform: null,
instructionAddr: null,
symbolAddr: null,
function: 'callApplicationOnCreate',
rawFunction: null,
symbol: null,
context: [],
lineNo: 1176,
colNo: null,
inApp: false,
trust: null,
vars: null,
},
{
filename: 'SharedPreferencesImpl.java',
absPath: 'SharedPreferencesImpl.java',
module: 'android.app.SharedPreferencesImpl$EditorImpl$1',
package: null,
platform: null,
instructionAddr: null,
symbolAddr: null,
function: 'run',
rawFunction: null,
symbol: null,
context: [],
lineNo: 366,
colNo: null,
inApp: false,
trust: null,
vars: null,
},
]);
const rootCause = analyzeFramesForRootCause(event);
expect(rootCause?.resources).toEqual(
'SharedPreferences.apply will save data on background thread only if it happens before the activity/service finishes. Switch to SharedPreferences.commit and move commit to a background thread.'
);
expect(rootCause?.culprit).toEqual(
'/^android\\.app\\.SharedPreferencesImpl\\$EditorImpl\\$[0-9]/'
);
});
it('given lock address returns frame with matching lock address', function () {
const frame1 = {
filename: 'Instrumentation.java',
absPath: 'Instrumentation.java',
module: 'android.app.Instrumentation',
package: null,
platform: null,
instructionAddr: null,
symbolAddr: null,
function: 'call',
rawFunction: null,
symbol: null,
context: [],
lineNo: 1176,
colNo: null,
inApp: false,
trust: null,
vars: null,
lock: {
type: LockType.BLOCKED,
address: '0x08a8651c',
package_name: 'io.sentry.samples',
class_name: 'Monitor',
thread_id: 12,
},
};
const frame2 = {
filename: 'MainActivity.java',
absPath: 'MainActivity.java',
module: 'com.example.MainActivity',
package: null,
platform: null,
instructionAddr: null,
symbolAddr: null,
function: 'onCreate',
rawFunction: null,
symbol: null,
context: [],
lineNo: 128,
colNo: null,
inApp: false,
trust: null,
vars: null,
lock: {
type: LockType.BLOCKED,
address: '0x07d7437b',
package_name: 'java.lang',
class_name: 'Object',
thread_id: 7,
},
};
const rootCause1 = analyzeFrameForRootCause(frame1, undefined, '<address>');
expect(rootCause1).toBeNull();
const rootCause2 = analyzeFrameForRootCause(frame2, undefined, '0x07d7437b');
render(<div>{rootCause2?.resources}</div>);
expect(
screen.getByText(
textWithMarkupMatcher(
'The main thread is blocked/waiting, trying to acquire lock 0x07d7437b (java.lang.Object) held by the suspect frame of this thread.'
)
)
).toBeInTheDocument();
});
it('when thread id is not provided, does not append "held by"', function () {
const frame = {
filename: 'MainActivity.java',
absPath: 'MainActivity.java',
module: 'com.example.MainActivity',
package: null,
platform: null,
instructionAddr: null,
symbolAddr: null,
function: 'onCreate',
rawFunction: null,
symbol: null,
context: [],
lineNo: 128,
colNo: null,
inApp: false,
trust: null,
vars: null,
lock: {
type: LockType.BLOCKED,
address: '0x07d7437b',
package_name: 'java.lang',
class_name: 'Object',
},
};
const rootCause2 = analyzeFrameForRootCause(frame, undefined, '0x07d7437b');
render(<div>{rootCause2?.resources}</div>);
expect(
screen.getByText(
textWithMarkupMatcher(
'The main thread is blocked/waiting, trying to acquire lock 0x07d7437b (java.lang.Object) .'
)
)
).toBeInTheDocument();
});
it('given main thread is locked returns it as root cause', function () {
const frame = {
filename: 'MainActivity.java',
absPath: 'MainActivity.java',
module: 'com.example.MainActivity',
package: null,
platform: null,
instructionAddr: null,
symbolAddr: null,
function: 'onCreate',
rawFunction: null,
symbol: null,
context: [],
lineNo: 128,
colNo: null,
inApp: false,
trust: null,
vars: null,
lock: {
type: LockType.BLOCKED,
address: '0x08a1321b',
package_name: 'java.lang',
class_name: 'Object',
thread_id: 7,
},
};
const thread = {
id: 13920,
current: true,
crashed: true,
name: 'puma 002',
stacktrace: null,
rawStacktrace: null,
state: 'BLOCKED',
};
const rootCause = analyzeFrameForRootCause(frame, thread);
render(<div>{rootCause?.resources}</div>);
expect(
screen.getByText(
textWithMarkupMatcher(
'The main thread is blocked/waiting, trying to acquire lock 0x08a1321b (java.lang.Object) held by the suspect frame of this thread.'
)
)
).toBeInTheDocument();
});
});