Skip to content

Commit 686ce95

Browse files
committed
Create DatadogCrashReportFilterTests.swift
1 parent 1f9fb08 commit 686ce95

File tree

1 file changed

+382
-0
lines changed

1 file changed

+382
-0
lines changed
Lines changed: 382 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,382 @@
1+
/*
2+
* Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0.
3+
* This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
* Copyright 2019-Present Datadog, Inc.
5+
*/
6+
7+
import XCTest
8+
import KSCrashRecording
9+
@testable import DatadogCrashReporting
10+
11+
class DatadogCrashReportFilterTests: XCTestCase {
12+
func testFilterReports_ConvertsValidCrashReportToDDCrashReport() throws {
13+
// Given
14+
let contextData = Data("test_context".utf8).base64EncodedString()
15+
let json = """
16+
{
17+
"report": {
18+
"timestamp": "2025-10-22T14:14:12.007336Z",
19+
"id": "incident-123"
20+
},
21+
"system": {
22+
"cpu_arch": "arm64",
23+
"process_id": 12345,
24+
"process_name": "MyApp",
25+
"parent_process_id": 1,
26+
"parent_process_name": "launchd",
27+
"executable_path": "/var/containers/Bundle/Application/MyApp"
28+
},
29+
"crash": {
30+
"error": {
31+
"signal": {
32+
"name": "SIGSEGV",
33+
"code_name": "SEGV_MAPERR"
34+
}
35+
},
36+
"diagnosis": "Application crash: SIGSEGV (Segmentation fault)",
37+
"threads": [
38+
{
39+
"index": 0,
40+
"crashed": true,
41+
"backtrace": {
42+
"contents": [
43+
{
44+
"instruction_addr": 4096,
45+
"symbol_addr": 4000,
46+
"object_name": "MyApp"
47+
}
48+
]
49+
}
50+
}
51+
]
52+
},
53+
"binary_images": [
54+
{
55+
"name": "/var/containers/Bundle/Application/MyApp/MyApp",
56+
"uuid": "12345678-1234-1234-1234-123456789012",
57+
"image_addr": 4096,
58+
"image_size": 8192
59+
}
60+
],
61+
"user": {
62+
"dd": "\(contextData)"
63+
}
64+
}
65+
"""
66+
67+
let dict = try XCTUnwrap(JSONSerialization.jsonObject(with: Data(json.utf8)) as? [String: Any])
68+
let report = AnyCrashReport(try CrashFieldDictionary(from: dict))
69+
let filter = DatadogCrashReportFilter()
70+
var capturedReports: [CrashReport]?
71+
72+
// When
73+
filter.filterReports([report]) { reports, error in
74+
XCTAssertNil(error)
75+
capturedReports = reports
76+
}
77+
78+
// Then
79+
XCTAssertEqual(capturedReports?.count, 1)
80+
let ddReport = try XCTUnwrap(capturedReports?.first?.untypedValue as? DDCrashReport)
81+
82+
XCTAssertEqual(ddReport.type, "SIGSEGV (SEGV_MAPERR)")
83+
XCTAssertEqual(ddReport.message, "Application crash: SIGSEGV (Segmentation fault)")
84+
XCTAssertEqual(ddReport.meta?.incidentIdentifier, "incident-123")
85+
XCTAssertEqual(ddReport.meta?.process, "MyApp [12345]")
86+
XCTAssertEqual(ddReport.meta?.parentProcess, "launchd [1]")
87+
XCTAssertEqual(ddReport.meta?.exceptionType, "SIGSEGV")
88+
XCTAssertEqual(ddReport.meta?.exceptionCodes, "SEGV_MAPERR")
89+
XCTAssertEqual(ddReport.threads.count, 1)
90+
XCTAssertTrue(ddReport.threads[0].crashed)
91+
XCTAssertEqual(ddReport.binaryImages.count, 1)
92+
XCTAssertEqual(ddReport.context, Data("test_context".utf8))
93+
}
94+
95+
func testFilterReports_HandlesMinimalCrashReport() throws {
96+
// Given
97+
let contextData = Data("test".utf8).base64EncodedString()
98+
let json = """
99+
{
100+
"report": {
101+
"timestamp": "2025-10-22T14:14:12.007Z",
102+
"id": "incident-456"
103+
},
104+
"system": {
105+
"cpu_arch": "x86_64"
106+
},
107+
"crash": {
108+
"error": {
109+
"signal": {}
110+
},
111+
"threads": []
112+
},
113+
"binary_images": [],
114+
"user": {
115+
"dd": "\(contextData)"
116+
}
117+
}
118+
"""
119+
120+
let dict = try XCTUnwrap(JSONSerialization.jsonObject(with: Data(json.utf8)) as? [String: Any])
121+
let report = AnyCrashReport(try CrashFieldDictionary(from: dict))
122+
let filter = DatadogCrashReportFilter()
123+
var capturedReports: [CrashReport]?
124+
125+
// When
126+
filter.filterReports([report]) { reports, error in
127+
XCTAssertNil(error)
128+
capturedReports = reports
129+
}
130+
131+
// Then
132+
let ddReport = try XCTUnwrap(capturedReports?.first?.untypedValue as? DDCrashReport)
133+
XCTAssertEqual(ddReport.type, "<unknown> (#0)")
134+
XCTAssertEqual(ddReport.message, "No crash reason provided")
135+
XCTAssertEqual(ddReport.stack, "???")
136+
}
137+
138+
func testFilterReports_ExtractsCrashedThreadStack() throws {
139+
// Given
140+
let contextData = Data("test".utf8).base64EncodedString()
141+
let json = """
142+
{
143+
"report": {
144+
"timestamp": "2025-10-22T14:14:12Z",
145+
"id": "incident-789"
146+
},
147+
"system": {
148+
"cpu_arch": "arm64"
149+
},
150+
"crash": {
151+
"error": {
152+
"signal": {
153+
"name": "SIGABRT"
154+
}
155+
},
156+
"threads": [
157+
{
158+
"index": 0,
159+
"crashed": false,
160+
"backtrace": {
161+
"contents": [
162+
{
163+
"instruction_addr": 1000,
164+
"symbol_addr": 900,
165+
"object_name": "libsystem"
166+
}
167+
]
168+
}
169+
},
170+
{
171+
"index": 1,
172+
"crashed": true,
173+
"backtrace": {
174+
"contents": [
175+
{
176+
"instruction_addr": 5000,
177+
"symbol_addr": 4900,
178+
"object_name": "MyFramework"
179+
}
180+
]
181+
}
182+
}
183+
]
184+
},
185+
"binary_images": [],
186+
"user": {
187+
"dd": "\(contextData)"
188+
}
189+
}
190+
"""
191+
192+
let dict = try XCTUnwrap(JSONSerialization.jsonObject(with: Data(json.utf8)) as? [String: Any])
193+
let report = AnyCrashReport(try CrashFieldDictionary(from: dict))
194+
let filter = DatadogCrashReportFilter()
195+
var capturedReports: [CrashReport]?
196+
197+
// When
198+
filter.filterReports([report]) { reports, error in
199+
capturedReports = reports
200+
}
201+
202+
// Then
203+
let ddReport = try XCTUnwrap(capturedReports?.first?.untypedValue as? DDCrashReport)
204+
XCTAssertTrue(ddReport.stack.contains("MyFramework"))
205+
XCTAssertFalse(ddReport.stack.contains("libsystem"))
206+
XCTAssertEqual(ddReport.threads.count, 2)
207+
XCTAssertTrue(ddReport.threads[1].crashed)
208+
}
209+
210+
func testFilterReports_DetectsSystemVsUserBinaryImages() throws {
211+
// Given
212+
let contextData = Data("test".utf8).base64EncodedString()
213+
let json = """
214+
{
215+
"report": {
216+
"timestamp": "2025-10-22T14:14:12Z",
217+
"id": "incident-999"
218+
},
219+
"system": {
220+
"cpu_arch": "arm64"
221+
},
222+
"crash": {
223+
"error": {
224+
"signal": {}
225+
},
226+
"threads": []
227+
},
228+
"binary_images": [
229+
{
230+
"name": "/System/Library/Frameworks/Foundation.framework/Foundation",
231+
"uuid": "sys-uuid",
232+
"image_addr": 1000,
233+
"image_size": 2000
234+
},
235+
{
236+
"name": "/var/containers/Bundle/Application/MyApp/MyApp",
237+
"uuid": "app-uuid",
238+
"image_addr": 3000,
239+
"image_size": 4000
240+
}
241+
],
242+
"user": {
243+
"dd": "\(contextData)"
244+
}
245+
}
246+
"""
247+
248+
let dict = try XCTUnwrap(JSONSerialization.jsonObject(with: Data(json.utf8)) as? [String: Any])
249+
let report = AnyCrashReport(try CrashFieldDictionary(from: dict))
250+
let filter = DatadogCrashReportFilter()
251+
var capturedReports: [CrashReport]?
252+
253+
// When
254+
filter.filterReports([report]) { reports, error in
255+
capturedReports = reports
256+
}
257+
258+
// Then
259+
let ddReport = try XCTUnwrap(capturedReports?.first?.untypedValue as? DDCrashReport)
260+
XCTAssertEqual(ddReport.binaryImages.count, 2)
261+
262+
let systemImage = ddReport.binaryImages.first { $0.libraryName == "Foundation" }
263+
XCTAssertEqual(systemImage?.isSystemLibrary, true)
264+
265+
let userImage = ddReport.binaryImages.first { $0.libraryName == "MyApp" }
266+
XCTAssertEqual(userImage?.isSystemLibrary, false)
267+
}
268+
269+
func testFilterReports_MarksTruncatedBacktraces() throws {
270+
// Given
271+
let contextData = Data("test".utf8).base64EncodedString()
272+
let json = """
273+
{
274+
"report": {
275+
"timestamp": "2025-10-22T14:14:12Z",
276+
"id": "incident-truncated"
277+
},
278+
"system": {
279+
"cpu_arch": "arm64"
280+
},
281+
"crash": {
282+
"error": {
283+
"signal": {}
284+
},
285+
"threads": [
286+
{
287+
"index": 0,
288+
"crashed": true,
289+
"backtrace": {
290+
"contents": [
291+
{
292+
"instruction_addr": 1000,
293+
"symbol_addr": 900,
294+
"object_name": "MyApp"
295+
}
296+
],
297+
"truncated": true
298+
}
299+
}
300+
]
301+
},
302+
"binary_images": [],
303+
"user": {
304+
"dd": "\(contextData)"
305+
}
306+
}
307+
"""
308+
309+
let dict = try XCTUnwrap(JSONSerialization.jsonObject(with: Data(json.utf8)) as? [String: Any])
310+
let report = AnyCrashReport(try CrashFieldDictionary(from: dict))
311+
let filter = DatadogCrashReportFilter()
312+
var capturedReports: [CrashReport]?
313+
314+
// When
315+
filter.filterReports([report]) { reports, error in
316+
capturedReports = reports
317+
}
318+
319+
// Then
320+
let ddReport = try XCTUnwrap(capturedReports?.first?.untypedValue as? DDCrashReport)
321+
XCTAssertTrue(ddReport.wasTruncated)
322+
}
323+
324+
func testFilterReports_ReturnsErrorForInvalidReportType() {
325+
// Given
326+
let report = AnyCrashReport(["invalid": "data"])
327+
let filter = DatadogCrashReportFilter()
328+
var capturedError: Error?
329+
330+
// When
331+
filter.filterReports([report]) { reports, error in
332+
capturedError = error
333+
}
334+
335+
// Then
336+
XCTAssertNotNil(capturedError)
337+
XCTAssertTrue(capturedError is CrashReportException)
338+
}
339+
340+
func testFilterReports_HandlesMultipleReports() throws {
341+
// Given
342+
let contextData = Data("test".utf8).base64EncodedString()
343+
let json1 = """
344+
{
345+
"report": {"timestamp": "2025-10-22T14:14:12Z", "id": "1"},
346+
"system": {"cpu_arch": "arm64"},
347+
"crash": {"error": {"signal": {"name": "SIGSEGV"}}, "threads": []},
348+
"binary_images": [],
349+
"user": {"dd": "\(contextData)"}
350+
}
351+
"""
352+
let json2 = """
353+
{
354+
"report": {"timestamp": "2025-10-22T14:15:12Z", "id": "2"},
355+
"system": {"cpu_arch": "arm64"},
356+
"crash": {"error": {"signal": {"name": "SIGABRT"}}, "threads": []},
357+
"binary_images": [],
358+
"user": {"dd": "\(contextData)"}
359+
}
360+
"""
361+
362+
let dict1 = try XCTUnwrap(JSONSerialization.jsonObject(with: Data(json1.utf8)) as? [String: Any])
363+
let dict2 = try XCTUnwrap(JSONSerialization.jsonObject(with: Data(json2.utf8)) as? [String: Any])
364+
let report1 = AnyCrashReport(try CrashFieldDictionary(from: dict1))
365+
let report2 = AnyCrashReport(try CrashFieldDictionary(from: dict2))
366+
let filter = DatadogCrashReportFilter()
367+
var capturedReports: [CrashReport]?
368+
369+
// When
370+
filter.filterReports([report1, report2]) { reports, error in
371+
XCTAssertNil(error)
372+
capturedReports = reports
373+
}
374+
375+
// Then
376+
XCTAssertEqual(capturedReports?.count, 2)
377+
let ddReport1 = try XCTUnwrap(capturedReports?[0].untypedValue as? DDCrashReport)
378+
let ddReport2 = try XCTUnwrap(capturedReports?[1].untypedValue as? DDCrashReport)
379+
XCTAssertEqual(ddReport1.meta?.incidentIdentifier, "1")
380+
XCTAssertEqual(ddReport2.meta?.incidentIdentifier, "2")
381+
}
382+
}

0 commit comments

Comments
 (0)