@@ -10,72 +10,57 @@ describe('simctl collectCrashReports', () => {
1010 vi . restoreAllMocks ( ) ;
1111 } ) ;
1212
13- it ( 'extracts matching simulator .ips crash reports' , async ( ) => {
13+ it ( 'extracts matching simulator .ips crash reports by filename prefix ' , async ( ) => {
1414 const diagnosticReportsDir = join (
1515 homedir ( ) ,
1616 'Library' ,
1717 'Logs' ,
1818 'DiagnosticReports'
1919 ) ;
2020 vi . spyOn ( fs , 'existsSync' ) . mockReturnValue ( true ) ;
21+ // OtherApp file is present but must be ignored purely based on filename prefix
2122 vi . spyOn ( fs , 'readdirSync' ) . mockReturnValue ( [
2223 'HarnessPlayground-2026-03-12-122756.ips' ,
2324 'OtherApp-2026-03-12-122756.ips' ,
2425 ] as unknown as ReturnType < typeof fs . readdirSync > ) ;
25- vi . spyOn ( fs , 'readFileSync' ) . mockImplementation ( ( ( path : fs . PathOrFileDescriptor ) => {
26- const filePath = String ( path ) ;
27-
28- if ( filePath . includes ( 'HarnessPlayground' ) ) {
29- return [
30- JSON . stringify ( {
31- app_name : 'HarnessPlayground' ,
32- bundleID : 'com.harnessplayground' ,
33- name : 'HarnessPlayground' ,
34- } ) ,
35- JSON . stringify ( {
36- pid : 1234 ,
37- procName : 'HarnessPlayground' ,
38- faultingThread : 0 ,
39- threads : [
40- {
41- frames : [
42- {
43- symbol : '_assertionFailure(_:_:file:line:flags:)' ,
44- symbolLocation : 156 ,
45- imageIndex : 1 ,
46- } ,
47- {
48- symbol : 'AppDelegate.crashIfRequested()' ,
49- sourceFile : 'AppDelegate.swift' ,
50- sourceLine : 31 ,
51- imageIndex : 1 ,
52- } ,
53- ] ,
54- } ,
55- ] ,
56- usedImages : [ { name : 'dyld' } , { name : 'HarnessPlayground' } ] ,
57- procPath :
58- `${ homedir ( ) } /Library/Developer/CoreSimulator/Devices/sim-udid/data/Containers/Bundle/Application/ABC/HarnessPlayground.app/HarnessPlayground` ,
59- exception : {
60- type : 'EXC_BREAKPOINT' ,
61- signal : 'SIGTRAP' ,
62- } ,
63- } ) ,
64- ] . join ( '\n' ) ;
65- }
66-
67- return [
26+ vi . spyOn ( fs , 'readFileSync' ) . mockReturnValue (
27+ [
6828 JSON . stringify ( {
69- app_name : 'OtherApp' ,
70- bundleID : 'com.other.app' ,
29+ app_name : 'HarnessPlayground' ,
30+ bundleID : 'com.harnessplayground' ,
31+ name : 'HarnessPlayground' ,
7132 } ) ,
7233 JSON . stringify ( {
73- procName : 'OtherApp' ,
34+ pid : 1234 ,
35+ procName : 'HarnessPlayground' ,
7436 procPath :
75- `${ homedir ( ) } /Library/Developer/CoreSimulator/Devices/other-udid/data/Containers/Bundle/Application/DEF/OtherApp.app/OtherApp` ,
37+ `${ homedir ( ) } /Library/Developer/CoreSimulator/Devices/sim-udid/data/Containers/Bundle/Application/ABC/HarnessPlayground.app/HarnessPlayground` ,
38+ faultingThread : 0 ,
39+ threads : [
40+ {
41+ frames : [
42+ {
43+ symbol : '_assertionFailure(_:_:file:line:flags:)' ,
44+ symbolLocation : 156 ,
45+ imageIndex : 1 ,
46+ } ,
47+ {
48+ symbol : 'AppDelegate.crashIfRequested()' ,
49+ sourceFile : 'AppDelegate.swift' ,
50+ sourceLine : 31 ,
51+ imageIndex : 1 ,
52+ } ,
53+ ] ,
54+ } ,
55+ ] ,
56+ usedImages : [ { name : 'dyld' } , { name : 'HarnessPlayground' } ] ,
57+ exception : {
58+ type : 'EXC_BREAKPOINT' ,
59+ signal : 'SIGTRAP' ,
60+ } ,
7661 } ) ,
77- ] . join ( '\n' ) ;
78- } ) as typeof fs . readFileSync ) ;
62+ ] . join ( '\n' ) as ReturnType < typeof fs . readFileSync >
63+ ) ;
7964 vi . spyOn ( fs , 'statSync' ) . mockReturnValue ( {
8065 mtimeMs : 123456 ,
8166 } as fs . Stats ) ;
@@ -170,8 +155,8 @@ describe('simctl collectCrashReports', () => {
170155 it ( 'ignores simulator reports older than the current run window' , async ( ) => {
171156 vi . spyOn ( fs , 'existsSync' ) . mockReturnValue ( true ) ;
172157 vi . spyOn ( fs , 'readdirSync' ) . mockReturnValue ( [
173- 'old .ips' ,
174- 'new .ips' ,
158+ 'HarnessPlayground-2026-03-12-113008 .ips' ,
159+ 'HarnessPlayground-2026-03-12-114008 .ips' ,
175160 ] as unknown as ReturnType < typeof fs . readdirSync > ) ;
176161 vi . spyOn ( fs , 'readFileSync' ) . mockImplementation ( ( ( input : fs . PathOrFileDescriptor ) => {
177162 const filePath = String ( input ) ;
@@ -183,7 +168,7 @@ describe('simctl collectCrashReports', () => {
183168 name : 'HarnessPlayground' ,
184169 } ) ,
185170 JSON . stringify ( {
186- pid : filePath . includes ( 'old ' ) ? 1234 : 1235 ,
171+ pid : filePath . includes ( '113008 ' ) ? 1234 : 1235 ,
187172 procName : 'HarnessPlayground' ,
188173 procPath :
189174 `${ homedir ( ) } /Library/Developer/CoreSimulator/Devices/sim-udid/data/Containers/Bundle/Application/ABC/HarnessPlayground.app/HarnessPlayground` ,
@@ -195,7 +180,7 @@ describe('simctl collectCrashReports', () => {
195180 ] . join ( '\n' ) ;
196181 } ) as typeof fs . readFileSync ) ;
197182 vi . spyOn ( fs , 'statSync' ) . mockImplementation ( ( ( input : fs . PathLike ) => ( {
198- mtimeMs : String ( input ) . includes ( 'old ' )
183+ mtimeMs : String ( input ) . includes ( '113008 ' )
199184 ? Date . parse ( '2026-03-12T11:30:08.000Z' )
200185 : Date . parse ( '2026-03-12T11:40:08.000Z' ) ,
201186 } ) ) as typeof fs . statSync ) ;
@@ -210,4 +195,50 @@ describe('simctl collectCrashReports', () => {
210195 expect ( reports ) . toHaveLength ( 1 ) ;
211196 expect ( reports [ 0 ] ?. pid ) . toBe ( 1235 ) ;
212197 } ) ;
198+
199+ it ( 'returns the latest crash report that matches the simulator udid, skipping newer ones from other simulators' , async ( ) => {
200+ vi . spyOn ( fs , 'existsSync' ) . mockReturnValue ( true ) ;
201+ vi . spyOn ( fs , 'readdirSync' ) . mockReturnValue ( [
202+ 'HarnessPlayground-2026-03-12-110000.ips' ,
203+ 'HarnessPlayground-2026-03-12-120000.ips' ,
204+ 'HarnessPlayground-2026-03-12-130000.ips' ,
205+ ] as unknown as ReturnType < typeof fs . readdirSync > ) ;
206+ vi . spyOn ( fs , 'readFileSync' ) . mockImplementation ( ( ( input : fs . PathOrFileDescriptor ) => {
207+ const filePath = String ( input ) ;
208+ // The newest file (130000) belongs to a different simulator; the second-newest (120000) is ours
209+ const udid = filePath . includes ( '130000' ) ? 'other-sim-udid' : 'sim-udid' ;
210+ const pid = filePath . includes ( '110000' ) ? 1001 : filePath . includes ( '120000' ) ? 1002 : 1003 ;
211+
212+ return [
213+ JSON . stringify ( { app_name : 'HarnessPlayground' , bundleID : 'com.harnessplayground' } ) ,
214+ JSON . stringify ( {
215+ pid,
216+ procName : 'HarnessPlayground' ,
217+ procPath :
218+ `${ homedir ( ) } /Library/Developer/CoreSimulator/Devices/${ udid } /data/Containers/Bundle/Application/ABC/HarnessPlayground.app/HarnessPlayground` ,
219+ exception : { type : 'EXC_BREAKPOINT' , signal : 'SIGTRAP' } ,
220+ } ) ,
221+ ] . join ( '\n' ) ;
222+ } ) as typeof fs . readFileSync ) ;
223+ vi . spyOn ( fs , 'statSync' ) . mockImplementation ( ( ( input : fs . PathLike ) => {
224+ const filePath = String ( input ) ;
225+ const mtimeMs = filePath . includes ( '110000' )
226+ ? Date . parse ( '2026-03-12T11:00:00.000Z' )
227+ : filePath . includes ( '120000' )
228+ ? Date . parse ( '2026-03-12T12:00:00.000Z' )
229+ : Date . parse ( '2026-03-12T13:00:00.000Z' ) ;
230+
231+ return { mtimeMs } as fs . Stats ;
232+ } ) as typeof fs . statSync ) ;
233+
234+ const reports = await collectCrashReports ( {
235+ udid : 'sim-udid' ,
236+ bundleId : 'com.harnessplayground' ,
237+ processNames : [ 'HarnessPlayground' ] ,
238+ } ) ;
239+
240+ expect ( reports ) . toHaveLength ( 1 ) ;
241+ // Skips the newest (pid 1003, other simulator) and returns the second-newest that matches
242+ expect ( reports [ 0 ] ?. pid ) . toBe ( 1002 ) ;
243+ } ) ;
213244} ) ;
0 commit comments