Skip to content

Commit f20587a

Browse files
committed
ensure native crash survives the merge
1 parent c8dafdd commit f20587a

File tree

2 files changed

+77
-5
lines changed

2 files changed

+77
-5
lines changed

sentry-android-core/src/main/java/io/sentry/android/core/TombstoneIntegration.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,11 @@ public boolean shouldReportHistorical() {
191191
final Hint hint = HintUtils.createWithTypeCheckHint(tombstoneHint);
192192

193193
try {
194-
mergeWithMatchingNativeEvents(tombstoneTimestamp, event, hint);
194+
final @Nullable SentryEvent mergedEvent =
195+
mergeWithMatchingNativeEvents(tombstoneTimestamp, event, hint);
196+
if (mergedEvent != null) {
197+
event = mergedEvent;
198+
}
195199
} catch (Throwable e) {
196200
options
197201
.getLogger()
@@ -204,15 +208,21 @@ public boolean shouldReportHistorical() {
204208
return new ApplicationExitInfoHistoryDispatcher.Report(event, hint, tombstoneHint);
205209
}
206210

207-
private void mergeWithMatchingNativeEvents(
208-
long tombstoneTimestamp, SentryEvent event, Hint hint) {
211+
/**
212+
* Attempts to find a matching native SDK event for the tombstone and merge them.
213+
*
214+
* @return The merged native event (with tombstone data applied) if a match was found and
215+
* merged, or null if no matching event was found or merge failed.
216+
*/
217+
private @Nullable SentryEvent mergeWithMatchingNativeEvents(
218+
long tombstoneTimestamp, SentryEvent tombstoneEvent, Hint hint) {
209219
// Try to find and remove matching native event from outbox
210220
final @Nullable NativeEventData matchingNativeEvent =
211221
nativeEventCollector.findAndRemoveMatchingNativeEvent(tombstoneTimestamp);
212222

213223
if (matchingNativeEvent == null) {
214224
options.getLogger().log(SentryLevel.DEBUG, "No matching native event found for tombstone.");
215-
return;
225+
return null;
216226
}
217227

218228
options
@@ -226,9 +236,12 @@ private void mergeWithMatchingNativeEvents(
226236
boolean deletionSuccess = nativeEventCollector.deleteNativeEventFile(matchingNativeEvent);
227237

228238
if (deletionSuccess) {
229-
mergeNativeCrashes(matchingNativeEvent.getEvent(), event);
239+
final SentryEvent nativeEvent = matchingNativeEvent.getEvent();
240+
mergeNativeCrashes(nativeEvent, tombstoneEvent);
230241
addNativeAttachmentsToTombstoneHint(matchingNativeEvent, hint);
242+
return nativeEvent;
231243
}
244+
return null;
232245
}
233246

234247
private void addNativeAttachmentsToTombstoneHint(

sentry-android-core/src/test/java/io/sentry/android/core/TombstoneIntegrationTest.kt

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import kotlin.test.assertTrue
1717
import org.junit.runner.RunWith
1818
import org.mockito.kotlin.any
1919
import org.mockito.kotlin.argThat
20+
import org.mockito.kotlin.check
2021
import org.mockito.kotlin.spy
2122
import org.mockito.kotlin.verify
2223
import org.mockito.kotlin.whenever
@@ -118,6 +119,64 @@ class TombstoneIntegrationTest : ApplicationExitIntegrationTestBase<TombstoneHin
118119
)
119120
}
120121

122+
@Test
123+
fun `when merging with native event, uses native event as base with tombstone stack traces`() {
124+
val integration =
125+
fixture.getSut(tmpDir, lastReportedTimestamp = oldTimestamp) { options ->
126+
val outboxDir = File(options.outboxPath!!)
127+
outboxDir.mkdirs()
128+
createNativeEnvelopeWithContext(outboxDir, newTimestamp)
129+
}
130+
131+
// Add tombstone with timestamp matching the native event
132+
fixture.addAppExitInfo(timestamp = newTimestamp)
133+
134+
integration.register(fixture.scopes, fixture.options)
135+
136+
verify(fixture.scopes)
137+
.captureEvent(
138+
check<SentryEvent> { event ->
139+
// Verify native SDK context is preserved
140+
assertEquals("native-sdk-user-id", event.user?.id)
141+
assertEquals("native-sdk-tag-value", event.getTag("native-sdk-tag"))
142+
143+
// Verify tombstone stack trace data is applied
144+
assertNotNull(event.exceptions)
145+
assertTrue(event.exceptions!!.isNotEmpty())
146+
assertEquals("TombstoneMerged", event.exceptions!![0].mechanism?.type)
147+
148+
// Verify tombstone debug meta is applied
149+
assertNotNull(event.debugMeta)
150+
assertTrue(event.debugMeta!!.images!!.isNotEmpty())
151+
152+
// Verify tombstone threads are applied (tombstone has 62 threads)
153+
assertEquals(62, event.threads?.size)
154+
},
155+
any<Hint>(),
156+
)
157+
}
158+
159+
private fun createNativeEnvelopeWithContext(outboxDir: File, timestamp: Long): File {
160+
val isoTimestamp = DateUtils.getTimestamp(DateUtils.getDateTime(timestamp))
161+
162+
// Native SDK event with user context and tags that should be preserved after merge
163+
val eventJson =
164+
"""{"event_id":"9ec79c33ec9942ab8353589fcb2e04dc","timestamp":"$isoTimestamp","platform":"native","level":"fatal","user":{"id":"native-sdk-user-id"},"tags":{"native-sdk-tag":"native-sdk-tag-value"}}"""
165+
val eventJsonSize = eventJson.toByteArray(Charsets.UTF_8).size
166+
167+
val envelopeContent =
168+
"""
169+
{"event_id":"9ec79c33ec9942ab8353589fcb2e04dc"}
170+
{"type":"event","length":$eventJsonSize,"content_type":"application/json"}
171+
$eventJson
172+
"""
173+
.trimIndent()
174+
175+
return File(outboxDir, "native-envelope-with-context.envelope").apply {
176+
writeText(envelopeContent)
177+
}
178+
}
179+
121180
private fun createNativeEnvelopeWithAttachment(outboxDir: File, timestamp: Long): File {
122181
val isoTimestamp = DateUtils.getTimestamp(DateUtils.getDateTime(timestamp))
123182

0 commit comments

Comments
 (0)