@@ -19,41 +19,40 @@ import org.mockito.kotlin.mock
1919class 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 {
0 commit comments