11import { describe , it , expect , vi , afterEach } from 'vitest' ;
2- import * as fs from 'node:fs' ;
32import type { ChildProcess , SpawnOptions } from 'node:child_process' ;
43import { EventEmitter } from 'node:events' ;
54import { launchSimulatorAppWithLogging } from '../simulator-steps.ts' ;
5+ import type { CommandExecutor } from '../CommandExecutor.ts' ;
66
77function createMockChild ( exitCode : number | null = null ) : ChildProcess {
88 const emitter = new EventEmitter ( ) ;
@@ -13,38 +13,34 @@ function createMockChild(exitCode: number | null = null): ChildProcess {
1313 return child ;
1414}
1515
16- function createFileWritingSpawner ( content : string , delayMs : number = 0 ) {
17- return ( command : string , args : string [ ] , options : SpawnOptions ) : ChildProcess => {
18- const child = createMockChild ( null ) ;
19- const stdio = options . stdio as [ unknown , number , number ] ;
20- const fd = stdio [ 1 ] ;
21- if ( typeof fd === 'number' ) {
22- if ( delayMs > 0 ) {
23- setTimeout ( ( ) => {
24- try {
25- fs . writeSync ( fd , content ) ;
26- } catch {
27- // fd may already be closed by the caller
28- }
29- } , delayMs ) ;
30- } else {
31- fs . writeSync ( fd , content ) ;
32- }
33- }
34- return child ;
16+ function createMockSpawner ( ) {
17+ return ( _command : string , _args : string [ ] , _options : SpawnOptions ) : ChildProcess => {
18+ return createMockChild ( null ) ;
3519 } ;
3620}
3721
38- describe ( 'launchSimulatorAppWithLogging PID parsing' , ( ) => {
22+ function createMockExecutor ( pid ?: number ) : CommandExecutor {
23+ return async ( ) => ( {
24+ success : true ,
25+ output : pid !== undefined ? `com.example.app: ${ pid } ` : '' ,
26+ process : { pid : 1 } as never ,
27+ exitCode : 0 ,
28+ } ) ;
29+ }
30+
31+ describe ( 'launchSimulatorAppWithLogging PID resolution' , ( ) => {
3932 afterEach ( ( ) => {
4033 vi . restoreAllMocks ( ) ;
4134 } ) ;
4235
43- it ( 'extracts PID from standard simctl colon format (bundleId: PID)' , async ( ) => {
44- const spawner = createFileWritingSpawner ( 'com.example.app: 42567\n' ) ;
36+ it ( 'resolves PID via idempotent simctl launch' , async ( ) => {
37+ const spawner = createMockSpawner ( ) ;
38+ const executor = createMockExecutor ( 42567 ) ;
39+
4540 const result = await launchSimulatorAppWithLogging (
4641 'test-sim-uuid' ,
4742 'com.example.app' ,
43+ executor ,
4844 undefined ,
4945 { spawner } ,
5046 ) ;
@@ -53,48 +49,58 @@ describe('launchSimulatorAppWithLogging PID parsing', () => {
5349 expect ( result . processId ) . toBe ( 42567 ) ;
5450 } ) ;
5551
56- it ( 'extracts PID from first line even when app output has bracketed numbers ' , async ( ) => {
57- const spawner = createFileWritingSpawner (
58- 'com.example.app: 42567\n[404] Not Found\nHTTP [200] OK\n' ,
59- ) ;
52+ it ( 'returns undefined processId when executor returns no PID ' , async ( ) => {
53+ const spawner = createMockSpawner ( ) ;
54+ const executor = createMockExecutor ( ) ;
55+
6056 const result = await launchSimulatorAppWithLogging (
6157 'test-sim-uuid' ,
6258 'com.example.app' ,
59+ executor ,
6360 undefined ,
6461 { spawner } ,
6562 ) ;
6663
6764 expect ( result . success ) . toBe ( true ) ;
68- expect ( result . processId ) . toBe ( 42567 ) ;
65+ expect ( result . processId ) . toBeUndefined ( ) ;
6966 } ) ;
7067
71- it ( 'ignores non-PID first lines and returns undefined' , async ( ) => {
72- const spawner = createFileWritingSpawner ( 'Loading resources...\n[404] Not Found\n' ) ;
68+ it ( 'returns undefined processId when executor fails' , async ( ) => {
69+ const spawner = createMockSpawner ( ) ;
70+ const executor : CommandExecutor = async ( ) => ( {
71+ success : false ,
72+ output : 'Unable to launch' ,
73+ error : 'App not installed' ,
74+ process : { pid : 1 } as never ,
75+ exitCode : 1 ,
76+ } ) ;
77+
7378 const result = await launchSimulatorAppWithLogging (
7479 'test-sim-uuid' ,
7580 'com.example.app' ,
81+ executor ,
7682 undefined ,
7783 { spawner } ,
7884 ) ;
7985
8086 expect ( result . success ) . toBe ( true ) ;
81- // First line has no colon PID pattern, bracketed numbers are not matched
8287 expect ( result . processId ) . toBeUndefined ( ) ;
8388 } ) ;
8489
85- it ( 'returns undefined when no PID is found within timeout' , async ( ) => {
86- // Write content with no PID pattern at all
87- const spawner = createFileWritingSpawner ( 'Starting application...\nLoading resources...\n' ) ;
90+ it ( 'reports failure when spawn exits immediately with error' , async ( ) => {
91+ const spawner = ( _command : string , _args : string [ ] , _options : SpawnOptions ) : ChildProcess => {
92+ return createMockChild ( 1 ) ;
93+ } ;
94+ const executor = createMockExecutor ( 42567 ) ;
8895
89- // Use a short timeout to not slow down tests
9096 const result = await launchSimulatorAppWithLogging (
9197 'test-sim-uuid' ,
9298 'com.example.app' ,
99+ executor ,
93100 undefined ,
94101 { spawner } ,
95102 ) ;
96103
97- expect ( result . success ) . toBe ( true ) ;
98- expect ( result . processId ) . toBeUndefined ( ) ;
104+ expect ( result . success ) . toBe ( false ) ;
99105 } ) ;
100106} ) ;
0 commit comments