Skip to content

Commit 9c152b4

Browse files
committed
fix(test-common): Finalize xcodebuild pipeline on post-startup exceptions
When the executor throws after startBuildPipeline has been called, the pipeline response was left in a pending state with no output. Hoist the started variable above the try block so the catch handler can call finalizeInlineXcodebuild and produce a proper error response with build log links.
1 parent 714177d commit 9c152b4

2 files changed

Lines changed: 55 additions & 2 deletions

File tree

src/utils/__tests__/test-common.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,45 @@ describe('handleTestLogic (pipeline)', () => {
246246
expect(renderedText).toContain('Running tests');
247247
});
248248

249+
it('finalizes the pipeline when the executor throws after startup', async () => {
250+
const executor = async () => {
251+
throw new Error('spawn blew up');
252+
};
253+
254+
const { result } = await runToolLogic(() =>
255+
handleTestLogic(
256+
{
257+
projectPath: '/tmp/App.xcodeproj',
258+
scheme: 'App',
259+
configuration: 'Debug',
260+
platform: XcodePlatform.macOS,
261+
progress: true,
262+
},
263+
executor,
264+
{
265+
preflight: {
266+
scheme: 'App',
267+
configuration: 'Debug',
268+
destinationName: 'iPhone 17 Pro',
269+
projectPath: '/tmp/App.xcodeproj',
270+
selectors: { onlyTesting: [], skipTesting: [] },
271+
targets: [],
272+
warnings: [],
273+
totalTests: 1,
274+
completeness: 'complete',
275+
},
276+
toolName: 'test_macos',
277+
},
278+
),
279+
);
280+
281+
expectPendingTestResponse(result, true);
282+
283+
const renderedText = finalizeAndGetText(result);
284+
expect(renderedText).toContain('spawn blew up');
285+
expect(renderedText).toContain('Build Logs:');
286+
});
287+
249288
it('returns a pending xcodebuild response when compilation fails before tests start', async () => {
250289
const executor = async (
251290
_command: string[],

src/utils/test-common.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { formatToolPreflight } from './build-preflight.ts';
2727
import { resolveDeviceName } from './device-name-resolver.ts';
2828
import { createSimulatorTwoPhaseExecutionPlan } from './simulator-test-execution.ts';
2929
import { startBuildPipeline } from './xcodebuild-pipeline.ts';
30-
import type { XcodebuildPipeline } from './xcodebuild-pipeline.ts';
30+
import type { StartedPipeline, XcodebuildPipeline } from './xcodebuild-pipeline.ts';
3131
import { finalizeInlineXcodebuild } from './xcodebuild-output.ts';
3232
import { getHandlerContext } from './typed-tool-factory.ts';
3333

@@ -77,6 +77,7 @@ export async function handleTestLogic(
7777
`Starting test run for scheme ${params.scheme} on platform ${params.platform} (internal)`,
7878
);
7979
const ctx = getHandlerContext();
80+
let started: StartedPipeline | null = null;
8081

8182
try {
8283
const execOpts: CommandExecOptions | undefined = params.testRunnerEnv
@@ -118,7 +119,7 @@ export async function handleTestLogic(
118119
: `${configText}\n${discoveryText}`;
119120
}
120121

121-
const started = startBuildPipeline({
122+
started = startBuildPipeline({
122123
operation: 'TEST',
123124
toolName: resolvedToolName,
124125
params: {
@@ -242,6 +243,19 @@ export async function handleTestLogic(
242243
} catch (error) {
243244
const errorMessage = error instanceof Error ? error.message : String(error);
244245
log('error', `Error during test run: ${errorMessage}`);
246+
247+
if (started) {
248+
finalizeInlineXcodebuild({
249+
started,
250+
emit: ctx.emit,
251+
succeeded: false,
252+
durationMs: Date.now() - started.startedAt,
253+
responseContent: [{ type: 'text', text: `Error during test run: ${errorMessage}` }],
254+
errorFallbackPolicy: 'always',
255+
});
256+
return;
257+
}
258+
245259
ctx.emit(
246260
header('Test Run', [
247261
{ label: 'Scheme', value: params.scheme },

0 commit comments

Comments
 (0)