Skip to content

Commit 9025dfe

Browse files
test(android): add proguard tombstone test cases (#5117)
* test(android): add proguard tombstone test cases * remove esr from the expected register set (used only for hard-faults) * Format code * re-enable sentryCompose and disable auto-installation in the gradle plugin * adapt parseTombstoneWithJavaFunctionName in TombstoneParserTest to epitaph and make the snapshot test independent of order. --------- Co-authored-by: Sentry Github Bot <bot+github-bot@sentry.io>
1 parent 10dd21c commit 9025dfe

File tree

5 files changed

+160
-154
lines changed

5 files changed

+160
-154
lines changed

sentry-android-core/src/test/java/io/sentry/android/core/internal/tombstone/TombstoneParserTest.kt

Lines changed: 152 additions & 154 deletions
Original file line numberDiff line numberDiff line change
@@ -19,41 +19,40 @@ import org.mockito.kotlin.mock
1919
class TombstoneParserTest {
2020
val expectedRegisters =
2121
setOf(
22+
"x0",
23+
"x1",
24+
"x2",
25+
"x3",
26+
"x4",
27+
"x5",
28+
"x6",
29+
"x7",
2230
"x8",
2331
"x9",
24-
"esr",
25-
"lr",
26-
"pst",
2732
"x10",
28-
"x12",
2933
"x11",
30-
"x14",
34+
"x12",
3135
"x13",
32-
"x16",
36+
"x14",
3337
"x15",
34-
"sp",
35-
"x18",
38+
"x16",
3639
"x17",
40+
"x18",
3741
"x19",
38-
"pc",
39-
"x21",
4042
"x20",
41-
"x0",
42-
"x23",
43-
"x1",
43+
"x21",
4444
"x22",
45-
"x2",
46-
"x25",
47-
"x3",
45+
"x23",
4846
"x24",
49-
"x4",
50-
"x27",
51-
"x5",
47+
"x25",
5248
"x26",
53-
"x6",
54-
"x29",
55-
"x7",
49+
"x27",
5650
"x28",
51+
"x29",
52+
"lr",
53+
"sp",
54+
"pc",
55+
"pst",
5756
)
5857

5958
val inAppIncludes = arrayListOf("io.sentry.samples.android")
@@ -64,84 +63,13 @@ class TombstoneParserTest {
6463
val parser = TombstoneParser(inAppIncludes, inAppExcludes, nativeLibraryDir)
6564

6665
@Test
67-
fun `parses a snapshot tombstone into Event`() {
68-
val tombstoneStream =
69-
GZIPInputStream(TombstoneParserTest::class.java.getResourceAsStream("/tombstone.pb.gz"))
70-
val streamParser =
71-
TombstoneParser(tombstoneStream, inAppIncludes, inAppExcludes, nativeLibraryDir)
72-
val event = streamParser.parse()
73-
74-
// top-level data
75-
assertNotNull(event.eventId)
76-
assertEquals(
77-
"Fatal signal SIGSEGV (11), SEGV_MAPERR (1), pid = 21891 (io.sentry.samples.android)",
78-
event.message!!.formatted,
79-
)
80-
assertEquals("native", event.platform)
81-
assertEquals("FATAL", event.level!!.name)
82-
83-
// exception
84-
// we only track one native exception (no nesting, one crashed thread)
85-
assertEquals(1, event.exceptions!!.size)
86-
val exception = event.exceptions!![0]
87-
assertEquals("SIGSEGV", exception.type)
88-
assertEquals("Segfault", exception.value)
89-
val crashedThreadId = exception.threadId
90-
assertNotNull(crashedThreadId)
91-
92-
val mechanism = exception.mechanism
93-
assertEquals("Tombstone", mechanism!!.type)
94-
assertEquals(false, mechanism.isHandled)
95-
assertEquals(true, mechanism.synthetic)
96-
assertEquals("SIGSEGV", mechanism.meta!!["name"])
97-
assertEquals(11, mechanism.meta!!["number"])
98-
assertEquals("SEGV_MAPERR", mechanism.meta!!["code_name"])
99-
assertEquals(1, mechanism.meta!!["code"])
100-
101-
// threads
102-
assertEquals(62, event.threads!!.size)
103-
for (thread in event.threads!!) {
104-
assertNotNull(thread.id)
105-
if (thread.id == crashedThreadId) {
106-
assert(thread.isCrashed == true)
107-
}
108-
assert(thread.stacktrace!!.frames!!.isNotEmpty())
109-
110-
for (frame in thread.stacktrace!!.frames!!) {
111-
assertNotNull(frame.function)
112-
if (frame.platform == "java") {
113-
// Java frames have module instead of package/instructionAddr
114-
assertNotNull(frame.module)
115-
} else {
116-
assertNotNull(frame.`package`)
117-
assertNotNull(frame.instructionAddr)
118-
}
119-
120-
if (thread.id == crashedThreadId) {
121-
if (frame.isInApp!!) {
122-
assert(
123-
frame.module?.startsWith(inAppIncludes[0]) == true ||
124-
frame.function!!.startsWith(inAppIncludes[0]) ||
125-
frame.`package`?.startsWith(nativeLibraryDir) == true
126-
)
127-
}
128-
}
129-
}
130-
131-
assert(thread.stacktrace!!.registers!!.keys.containsAll(expectedRegisters))
132-
}
66+
fun `parses tombstone into Event`() {
67+
assertTombstoneParsesCorrectly("/tombstone.pb.gz")
68+
}
13369

134-
// debug-meta
135-
assertEquals(352, event.debugMeta!!.images!!.size)
136-
for (image in event.debugMeta!!.images!!) {
137-
assertEquals("elf", image.type)
138-
assertNotNull(image.debugId)
139-
assertNotNull(image.codeId)
140-
assertNotNull(image.codeFile)
141-
val imageAddress = image.imageAddr!!.removePrefix("0x").toLong(16)
142-
assert(imageAddress > 0)
143-
assert(image.imageSize!! > 0)
144-
}
70+
@Test
71+
fun `parses tombstone_r8 with OAT frames into Event`() {
72+
assertTombstoneParsesCorrectly("/tombstone_r8.pb.gz")
14573
}
14674

14775
@Test
@@ -425,32 +353,13 @@ class TombstoneParserTest {
425353
}
426354

427355
@Test
428-
fun `java frames snapshot test for all threads`() {
429-
val tombstoneStream =
430-
GZIPInputStream(TombstoneParserTest::class.java.getResourceAsStream("/tombstone.pb.gz"))
431-
val parser = TombstoneParser(tombstoneStream, inAppIncludes, inAppExcludes, nativeLibraryDir)
432-
val event = parser.parse()
433-
434-
val logger = mock<ILogger>()
435-
val writer = StringWriter()
436-
val jsonWriter = JsonObjectWriter(writer, 100)
437-
jsonWriter.beginObject()
438-
for (thread in event.threads!!) {
439-
val javaFrames = thread.stacktrace!!.frames!!.filter { it.platform == "java" }
440-
if (javaFrames.isEmpty()) continue
441-
jsonWriter.name(thread.id.toString())
442-
jsonWriter.beginArray()
443-
for (frame in javaFrames) {
444-
frame.serialize(jsonWriter, logger)
445-
}
446-
jsonWriter.endArray()
447-
}
448-
jsonWriter.endObject()
449-
450-
val actualJson = writer.toString()
451-
val expectedJson = readGzippedResourceFile("/tombstone_java_frames.json.gz")
356+
fun `java frames snapshot test`() {
357+
assertJavaFramesSnapshot("/tombstone.pb.gz", "/tombstone_java_frames.json.gz")
358+
}
452359

453-
assertEquals(expectedJson, actualJson)
360+
@Test
361+
fun `tombstone_r8 java frames snapshot test`() {
362+
assertJavaFramesSnapshot("/tombstone_r8.pb.gz", "/tombstone_r8_java_frames.json.gz")
454363
}
455364

456365
@Test
@@ -531,39 +440,128 @@ class TombstoneParserTest {
531440

532441
private fun parseTombstoneWithJavaFunctionName(functionName: String): io.sentry.SentryEvent {
533442
val tombstone =
534-
TombstoneProtos.Tombstone.newBuilder()
535-
.setPid(1234)
536-
.setTid(1234)
537-
.setSignalInfo(
538-
TombstoneProtos.Signal.newBuilder()
539-
.setNumber(11)
540-
.setName("SIGSEGV")
541-
.setCode(1)
542-
.setCodeName("SEGV_MAPERR")
543-
)
544-
.putThreads(
545-
1234,
546-
TombstoneProtos.Thread.newBuilder()
547-
.setId(1234)
548-
.setName("main")
549-
.addCurrentBacktrace(
550-
TombstoneProtos.BacktraceFrame.newBuilder()
551-
.setPc(0x1000)
552-
.setFunctionName(functionName)
553-
.setFileName("/data/app/base.apk!classes.oat")
554-
)
555-
.build(),
443+
Tombstone.Builder()
444+
.pid(1234)
445+
.tid(1234)
446+
.signal(Signal(11, "SIGSEGV", 1, "SEGV_MAPERR", false, 0, 0, false, 0, null))
447+
.addThread(
448+
TombstoneThread(
449+
1234,
450+
"main",
451+
emptyList(),
452+
emptyList(),
453+
emptyList(),
454+
listOf(
455+
BacktraceFrame(0, 0x1000, 0, functionName, 0, "/data/app/base.apk!classes.oat", 0, "")
456+
),
457+
emptyList(),
458+
0,
459+
0,
460+
)
556461
)
557462
.build()
558463

559-
val parser =
560-
TombstoneParser(
561-
ByteArrayInputStream(tombstone.toByteArray()),
562-
inAppIncludes,
563-
inAppExcludes,
564-
nativeLibraryDir,
565-
)
566-
return parser.parse()
464+
val parser = TombstoneParser(inAppIncludes, inAppExcludes, nativeLibraryDir)
465+
return parser.parse(tombstone)
466+
}
467+
468+
private fun assertTombstoneParsesCorrectly(tombstoneResource: String) {
469+
val tombstoneStream =
470+
GZIPInputStream(TombstoneParserTest::class.java.getResourceAsStream(tombstoneResource))
471+
val parser = TombstoneParser(tombstoneStream, inAppIncludes, inAppExcludes, nativeLibraryDir)
472+
val event = parser.parse()
473+
474+
// top-level data
475+
assertNotNull(event.eventId)
476+
assertNotNull(event.message!!.formatted)
477+
assertEquals("native", event.platform)
478+
assertEquals("FATAL", event.level!!.name)
479+
480+
// exception
481+
assertEquals(1, event.exceptions!!.size)
482+
val exception = event.exceptions!![0]
483+
assertNotNull(exception.type)
484+
val crashedThreadId = exception.threadId
485+
assertNotNull(crashedThreadId)
486+
487+
val mechanism = exception.mechanism
488+
assertNotNull(mechanism)
489+
assertEquals("Tombstone", mechanism.type)
490+
assertEquals(false, mechanism.isHandled)
491+
assertEquals(true, mechanism.synthetic)
492+
493+
// threads
494+
assert(event.threads!!.isNotEmpty())
495+
var hasCrashedThread = false
496+
for (thread in event.threads!!) {
497+
assertNotNull(thread.id)
498+
if (thread.id == crashedThreadId) {
499+
assert(thread.isCrashed == true)
500+
hasCrashedThread = true
501+
}
502+
assert(thread.stacktrace!!.frames!!.isNotEmpty())
503+
504+
for (frame in thread.stacktrace!!.frames!!) {
505+
assertNotNull(frame.function)
506+
if (frame.platform == "java") {
507+
assertNotNull(frame.module)
508+
assert(frame.function!!.isNotEmpty()) {
509+
"Java frame has empty function name in thread ${thread.id}"
510+
}
511+
assertNotNull(frame.isInApp)
512+
} else {
513+
assertNotNull(frame.`package`)
514+
assertNotNull(frame.instructionAddr)
515+
}
516+
}
517+
518+
assert(thread.stacktrace!!.registers!!.keys.containsAll(expectedRegisters)) {
519+
"Thread ${thread.id} is missing registers: ${expectedRegisters - thread.stacktrace!!.registers!!.keys}"
520+
}
521+
}
522+
assert(hasCrashedThread) { "No crashed thread found matching exception threadId" }
523+
524+
// debug-meta
525+
assertNotNull(event.debugMeta)
526+
assert(event.debugMeta!!.images!!.isNotEmpty())
527+
for (image in event.debugMeta!!.images!!) {
528+
assertEquals("elf", image.type)
529+
assertNotNull(image.debugId)
530+
assertNotNull(image.codeId)
531+
assertNotNull(image.codeFile)
532+
val imageAddress = image.imageAddr!!.removePrefix("0x").toLong(16)
533+
assert(imageAddress > 0)
534+
assert(image.imageSize!! > 0)
535+
}
536+
}
537+
538+
private fun assertJavaFramesSnapshot(tombstoneResource: String, snapshotResource: String) {
539+
val tombstoneStream =
540+
GZIPInputStream(TombstoneParserTest::class.java.getResourceAsStream(tombstoneResource))
541+
val parser = TombstoneParser(tombstoneStream, inAppIncludes, inAppExcludes, nativeLibraryDir)
542+
val event = parser.parse()
543+
544+
val logger = mock<ILogger>()
545+
val writer = StringWriter()
546+
val jsonWriter = JsonObjectWriter(writer, 100)
547+
jsonWriter.beginObject()
548+
// Sort threads by ID for deterministic output (epitaph uses HashMap for threads)
549+
for (thread in event.threads!!.sortedBy { it.id }) {
550+
val javaFrames = thread.stacktrace!!.frames!!.filter { it.platform == "java" }
551+
if (javaFrames.isEmpty()) continue
552+
jsonWriter.name(thread.id.toString())
553+
jsonWriter.beginArray()
554+
for (frame in javaFrames) {
555+
frame.serialize(jsonWriter, logger)
556+
}
557+
jsonWriter.endArray()
558+
}
559+
jsonWriter.endObject()
560+
561+
val actualJson = writer.toString()
562+
val expectedJson = readGzippedResourceFile(snapshotResource)
563+
564+
assertEquals(expectedJson, actualJson)
567565
}
568566

569567
private fun serializeDebugMeta(debugMeta: DebugMeta): String {
Binary file not shown.
64.4 KB
Binary file not shown.
Binary file not shown.

sentry-samples/sentry-samples-android/build.gradle.kts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ plugins {
77
id("com.android.application")
88
alias(libs.plugins.kotlin.android)
99
alias(libs.plugins.kotlin.compose)
10+
alias(libs.plugins.sentry)
1011
}
1112

1213
android {
@@ -116,6 +117,13 @@ android {
116117
@Suppress("UnstableApiUsage") packagingOptions { jniLibs { useLegacyPackaging = true } }
117118
}
118119

120+
sentry {
121+
autoUploadProguardMapping = false
122+
autoUploadNativeSymbols = false
123+
autoUploadSourceContext = false
124+
autoInstallation { enabled = false }
125+
}
126+
119127
dependencies {
120128
implementation(
121129
kotlin(Config.kotlinStdLib, org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION)

0 commit comments

Comments
 (0)