|
1 | 1 | import { afterEach, describe, expect, it, vi } from 'vitest'; |
2 | | -import { resolveTestProgressEnabled } from '../test-common.ts'; |
| 2 | +import type { ChildProcess } from 'node:child_process'; |
| 3 | +import { createTestExecutor, resolveTestProgressEnabled } from '../test-common.ts'; |
| 4 | +import type { CommandExecutor, CommandResponse } from '../command.ts'; |
| 5 | +import { DefaultStreamingExecutionContext } from '../execution/index.ts'; |
| 6 | +import type { AnyFragment } from '../../types/domain-fragments.ts'; |
| 7 | +import type { TestPreflightResult } from '../test-preflight.ts'; |
| 8 | +import { XcodePlatform } from '../xcode.ts'; |
| 9 | + |
| 10 | +function createSuccessfulCommandResponse(): CommandResponse { |
| 11 | + return { |
| 12 | + success: true, |
| 13 | + output: '', |
| 14 | + process: { pid: 12345 } as ChildProcess, |
| 15 | + exitCode: 0, |
| 16 | + }; |
| 17 | +} |
| 18 | + |
| 19 | +function createPreflight(): TestPreflightResult { |
| 20 | + return { |
| 21 | + scheme: 'Weather', |
| 22 | + configuration: 'Debug', |
| 23 | + projectPath: 'Weather.xcodeproj', |
| 24 | + destinationName: 'iPhone 17 Pro', |
| 25 | + selectors: { |
| 26 | + onlyTesting: [], |
| 27 | + skipTesting: [], |
| 28 | + }, |
| 29 | + targets: [ |
| 30 | + { |
| 31 | + name: 'WeatherTests', |
| 32 | + files: [ |
| 33 | + { |
| 34 | + path: 'WeatherTests/WeatherTests.swift', |
| 35 | + tests: [ |
| 36 | + { |
| 37 | + framework: 'swift-testing', |
| 38 | + targetName: 'WeatherTests', |
| 39 | + typeName: 'WeatherTests', |
| 40 | + methodName: 'emptySearchReturnsNoResults', |
| 41 | + displayName: 'WeatherTests/WeatherTests/emptySearchReturnsNoResults', |
| 42 | + line: 12, |
| 43 | + parameterized: false, |
| 44 | + }, |
| 45 | + ], |
| 46 | + }, |
| 47 | + ], |
| 48 | + warnings: [], |
| 49 | + }, |
| 50 | + ], |
| 51 | + warnings: [], |
| 52 | + totalTests: 1, |
| 53 | + completeness: 'complete', |
| 54 | + }; |
| 55 | +} |
3 | 56 |
|
4 | 57 | describe('resolveTestProgressEnabled', () => { |
5 | 58 | const originalRuntime = process.env.XCODEBUILDMCP_RUNTIME; |
@@ -39,3 +92,60 @@ describe('resolveTestProgressEnabled', () => { |
39 | 92 | expect(resolveTestProgressEnabled(false)).toBe(false); |
40 | 93 | }); |
41 | 94 | }); |
| 95 | + |
| 96 | +describe('createTestExecutor', () => { |
| 97 | + it('emits RUN_TESTS before test-without-building starts in two-phase simulator execution', async () => { |
| 98 | + const emitted: AnyFragment[] = []; |
| 99 | + const actions: string[] = []; |
| 100 | + const executor: CommandExecutor = async (command, _logPrefix, _useShell, opts) => { |
| 101 | + const action = command.at(-1); |
| 102 | + if (action) { |
| 103 | + actions.push(action); |
| 104 | + } |
| 105 | + |
| 106 | + if (action === 'build-for-testing') { |
| 107 | + opts?.onStdout?.('Ld /tmp/Weather.build/Weather normal arm64\n'); |
| 108 | + } |
| 109 | + |
| 110 | + return createSuccessfulCommandResponse(); |
| 111 | + }; |
| 112 | + |
| 113 | + const executeTest = createTestExecutor(executor, { |
| 114 | + preflight: createPreflight(), |
| 115 | + toolName: 'test_sim', |
| 116 | + target: 'simulator', |
| 117 | + request: { |
| 118 | + scheme: 'Weather', |
| 119 | + projectPath: 'Weather.xcodeproj', |
| 120 | + configuration: 'Debug', |
| 121 | + platform: XcodePlatform.iOSSimulator, |
| 122 | + }, |
| 123 | + }); |
| 124 | + |
| 125 | + await executeTest( |
| 126 | + { |
| 127 | + projectPath: 'Weather.xcodeproj', |
| 128 | + scheme: 'Weather', |
| 129 | + configuration: 'Debug', |
| 130 | + simulatorId: 'A2C64636-37E9-4B68-B872-E7F0A82A5670', |
| 131 | + platform: XcodePlatform.iOSSimulator, |
| 132 | + }, |
| 133 | + new DefaultStreamingExecutionContext({ |
| 134 | + onFragment: (fragment) => emitted.push(fragment), |
| 135 | + }), |
| 136 | + ); |
| 137 | + |
| 138 | + expect(actions).toEqual(['build-for-testing', 'test-without-building']); |
| 139 | + |
| 140 | + const stageEvents = emitted.filter((event) => event.fragment === 'build-stage'); |
| 141 | + expect(stageEvents.map((event) => event.stage)).toEqual(['LINKING', 'RUN_TESTS']); |
| 142 | + |
| 143 | + const runTestsIndex = emitted.findIndex( |
| 144 | + (event) => event.fragment === 'build-stage' && event.stage === 'RUN_TESTS', |
| 145 | + ); |
| 146 | + const finalSummaryIndex = emitted.findIndex((event) => event.fragment === 'build-summary'); |
| 147 | + |
| 148 | + expect(runTestsIndex).toBeGreaterThan(-1); |
| 149 | + expect(finalSummaryIndex).toBeGreaterThan(runTestsIndex); |
| 150 | + }); |
| 151 | +}); |
0 commit comments