1- import { beforeEach , describe , expect , it , vi } from 'vitest' ;
1+ import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest' ;
22import fs from 'node:fs' ;
33import { join } from 'node:path' ;
44import { tmpdir } from 'node:os' ;
55import { createCrashArtifactWriter } from '@react-native-harness/tools' ;
6- import { collectCrashArtifacts } from '../crash-diagnostics.js' ;
7- import * as simctl from '../xcrun/simctl.js' ;
6+ import {
7+ collectCrashArtifacts ,
8+ waitForCrashArtifact ,
9+ } from '../crash-diagnostics.js' ;
810import * as devicectl from '../xcrun/devicectl.js' ;
911
12+ const writeIosIpsCrashReport = ( path : string ) => {
13+ fs . writeFileSync (
14+ path ,
15+ [
16+ JSON . stringify ( {
17+ app_name : 'HarnessPlayground' ,
18+ bundleID : 'com.harnessplayground' ,
19+ timestamp : '2026-03-12 11:35:08 +0000' ,
20+ } ) ,
21+ JSON . stringify ( {
22+ pid : 1234 ,
23+ procName : 'HarnessPlayground' ,
24+ procPath :
25+ '/Users/me/Library/Developer/CoreSimulator/Devices/sim-udid/data/Containers/Bundle/Application/ABC/HarnessPlayground.app/HarnessPlayground' ,
26+ exception : {
27+ type : 'EXC_BREAKPOINT' ,
28+ signal : 'SIGTRAP' ,
29+ } ,
30+ } ) ,
31+ ] . join ( '\n' ) ,
32+ 'utf8' ,
33+ ) ;
34+ } ;
35+
1036describe ( 'collectCrashArtifacts' , ( ) => {
37+ const originalDiagnosticReportsDir =
38+ process . env . RN_HARNESS_IOS_DIAGNOSTIC_REPORTS_DIR ;
39+ let diagnosticReportsDir : string ;
40+
1141 beforeEach ( ( ) => {
1242 vi . restoreAllMocks ( ) ;
43+ diagnosticReportsDir = fs . mkdtempSync (
44+ join ( tmpdir ( ) , 'rn-harness-diagnostic-reports-' ) ,
45+ ) ;
46+ process . env . RN_HARNESS_IOS_DIAGNOSTIC_REPORTS_DIR = diagnosticReportsDir ;
1347 } ) ;
1448
15- it ( 'collects simulator crash artifacts from simctl diagnose output' , async ( ) => {
16- const outputRoot = fs . mkdtempSync (
17- join ( tmpdir ( ) , 'rn-harness-simctl-diagnose-' ) ,
18- ) ;
19- const crashPath = join ( outputRoot , 'HarnessPlayground.ips' ) ;
20- fs . writeFileSync (
21- crashPath ,
22- [
23- JSON . stringify ( {
24- app_name : 'HarnessPlayground' ,
25- bundleID : 'com.harnessplayground' ,
26- timestamp : '2026-03-12 11:35:08 +0000' ,
27- } ) ,
28- JSON . stringify ( {
29- pid : 1234 ,
30- procName : 'HarnessPlayground' ,
31- procPath :
32- '/Users/me/Library/Developer/CoreSimulator/Devices/sim-udid/data/Containers/Bundle/Application/ABC/HarnessPlayground.app/HarnessPlayground' ,
33- exception : {
34- type : 'EXC_BREAKPOINT' ,
35- signal : 'SIGTRAP' ,
36- } ,
37- } ) ,
38- ] . join ( '\n' ) ,
39- 'utf8' ,
40- ) ;
49+ afterEach ( ( ) => {
50+ if ( originalDiagnosticReportsDir === undefined ) {
51+ delete process . env . RN_HARNESS_IOS_DIAGNOSTIC_REPORTS_DIR ;
52+ } else {
53+ process . env . RN_HARNESS_IOS_DIAGNOSTIC_REPORTS_DIR =
54+ originalDiagnosticReportsDir ;
55+ }
56+ } ) ;
4157
42- vi . spyOn ( simctl , 'diagnose' ) . mockImplementation (
43- async ( _udid , outputDir ) => {
44- fs . mkdirSync ( outputDir , { recursive : true } ) ;
45- fs . copyFileSync ( crashPath , join ( outputDir , 'HarnessPlayground.ips' ) ) ;
46- } ,
58+ it ( 'collects simulator crash artifacts from host DiagnosticReports' , async ( ) => {
59+ writeIosIpsCrashReport (
60+ join ( diagnosticReportsDir , 'HarnessPlayground-2026-03-12-113508.ips' ) ,
4761 ) ;
4862
4963 const artifacts = await collectCrashArtifacts ( {
@@ -107,43 +121,14 @@ describe('collectCrashArtifacts', () => {
107121 } ) ;
108122
109123 it ( 'persists matched crash artifacts with the provided writer' , async ( ) => {
110- const sourceRoot = fs . mkdtempSync (
111- join ( tmpdir ( ) , 'rn-harness-crash-diagnostics-' ) ,
112- ) ;
113- const sourcePath = join ( sourceRoot , 'HarnessPlayground.ips' ) ;
114- fs . writeFileSync (
115- sourcePath ,
116- [
117- JSON . stringify ( {
118- app_name : 'HarnessPlayground' ,
119- bundleID : 'com.harnessplayground' ,
120- timestamp : '2026-03-12 11:35:08 +0000' ,
121- } ) ,
122- JSON . stringify ( {
123- pid : 1234 ,
124- procName : 'HarnessPlayground' ,
125- procPath :
126- '/Users/me/Library/Developer/CoreSimulator/Devices/sim-udid/data/Containers/Bundle/Application/ABC/HarnessPlayground.app/HarnessPlayground' ,
127- exception : {
128- type : 'EXC_BREAKPOINT' ,
129- signal : 'SIGTRAP' ,
130- } ,
131- } ) ,
132- ] . join ( '\n' ) ,
133- 'utf8' ,
134- ) ;
135-
136- vi . spyOn ( simctl , 'diagnose' ) . mockImplementation (
137- async ( _udid , outputDir ) => {
138- fs . mkdirSync ( outputDir , { recursive : true } ) ;
139- fs . copyFileSync ( sourcePath , join ( outputDir , 'HarnessPlayground.ips' ) ) ;
140- } ,
124+ writeIosIpsCrashReport (
125+ join ( diagnosticReportsDir , 'HarnessPlayground-2026-03-12-113508.ips' ) ,
141126 ) ;
142127
143128 const writer = createCrashArtifactWriter ( {
144129 runnerName : 'ios-sim' ,
145130 platformId : 'ios' ,
146- rootDir : join ( sourceRoot , '.harness' , 'crash-reports' ) ,
131+ rootDir : join ( diagnosticReportsDir , '.harness' , 'crash-reports' ) ,
147132 runTimestamp : '2026-03-12T11-35-08-000Z' ,
148133 } ) ;
149134
@@ -158,4 +143,40 @@ describe('collectCrashArtifacts', () => {
158143 expect ( artifacts [ 0 ] ?. artifactPath ) . toContain ( '/.harness/crash-reports/' ) ;
159144 expect ( fs . existsSync ( artifacts [ 0 ] ?. artifactPath ?? '' ) ) . toBe ( true ) ;
160145 } ) ;
146+
147+ it ( 'returns a host crash report without waiting for device crash log lookup to finish' , async ( ) => {
148+ writeIosIpsCrashReport (
149+ join ( diagnosticReportsDir , 'HarnessPlayground-2026-03-12-113508.ips' ) ,
150+ ) ;
151+
152+ vi . spyOn ( devicectl , 'listFiles' ) . mockImplementation (
153+ ( ) =>
154+ new Promise ( ( ) => {
155+ // Keep the device-side collector pending so the host lookup must win.
156+ } ) ,
157+ ) ;
158+
159+ const artifact = await waitForCrashArtifact ( {
160+ lookup : {
161+ processName : 'HarnessPlayground' ,
162+ pid : 1234 ,
163+ occurredAt : Date . parse ( '2026-03-12T11:35:08.000Z' ) ,
164+ } ,
165+ options : {
166+ targetId : 'device-udid' ,
167+ targetType : 'device' ,
168+ processNames : [ 'HarnessPlayground' ] ,
169+ bundleId : 'com.harnessplayground' ,
170+ minOccurredAt : Date . parse ( '2026-03-12T11:35:07.000Z' ) ,
171+ } ,
172+ getFallbackArtifact : ( ) => null ,
173+ recordArtifact : vi . fn ( ) ,
174+ } ) ;
175+
176+ expect ( artifact ) . toMatchObject ( {
177+ processName : 'HarnessPlayground' ,
178+ pid : 1234 ,
179+ signal : 'SIGTRAP' ,
180+ } ) ;
181+ } ) ;
161182} ) ;
0 commit comments