Skip to content

Commit a88c40b

Browse files
committed
Add fatal step e2e test
1 parent c1333ed commit a88c40b

File tree

2 files changed

+78
-0
lines changed

2 files changed

+78
-0
lines changed

packages/core/e2e/e2e.test.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -719,4 +719,49 @@ describe('e2e', () => {
719719
expect(stepCompletedEvents).toHaveLength(1);
720720
}
721721
);
722+
723+
test(
724+
'processExitResilienceWorkflow - step self-terminates on first attempt',
725+
{ timeout: 60_000 },
726+
async () => {
727+
// This test verifies that the workflow system is resilient to fatal process crashes
728+
// The step will call process.exit() on the first attempt, simulating an unhandled
729+
// fatal error, but the system should recover and retry on a new process
730+
const run = await triggerWorkflow('processExitResilienceWorkflow', []);
731+
const returnValue = await getWorkflowReturnValue(run.runId);
732+
733+
// The workflow should complete successfully after the process crash
734+
expect(returnValue).toMatchObject({
735+
attempt: 2,
736+
status: 'recovered',
737+
});
738+
739+
// Verify the run data shows successful completion
740+
const { json: runData } = await cliInspectJson(
741+
`runs ${run.runId} --withData`
742+
);
743+
expect(runData).toMatchObject({
744+
runId: run.runId,
745+
status: 'completed',
746+
output: { attempt: 2, status: 'recovered' },
747+
});
748+
749+
// Query steps to verify the step was retried
750+
const { json: stepsData } = await cliInspectJson(
751+
`steps --runId ${run.runId} --withData`
752+
);
753+
expect(stepsData).toBeDefined();
754+
expect(Array.isArray(stepsData)).toBe(true);
755+
expect(stepsData.length).toBeGreaterThan(0);
756+
757+
// Find the stepThatExitsOnFirstAttempt step
758+
const exitStep = stepsData.find((s: any) =>
759+
s.stepName.includes('stepThatExitsOnFirstAttempt')
760+
);
761+
expect(exitStep).toBeDefined();
762+
expect(exitStep.status).toBe('completed');
763+
expect(exitStep.attempt).toBe(2);
764+
expect(exitStep.output).toEqual([{ attempt: 2, status: 'recovered' }]);
765+
}
766+
);
722767
});

workbench/example/workflows/99_e2e.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,3 +512,36 @@ async function doubleNumber(x: number) {
512512
'use step';
513513
return x * 2;
514514
}
515+
516+
//////////////////////////////////////////////////////////
517+
518+
async function stepThatExitsOnFirstAttempt() {
519+
'use step';
520+
const { attempt } = getStepMetadata();
521+
console.log(`stepThatExitsOnFirstAttempt - attempt: ${attempt}`);
522+
523+
// Kill the process on the first attempt to simulate a fatal crash
524+
if (attempt === 1) {
525+
console.log(
526+
`Attempt ${attempt} - calling process.exit() to simulate fatal crash`
527+
);
528+
process.exit(1);
529+
}
530+
531+
console.log(`Attempt ${attempt} - succeeding after process recovered`);
532+
return { attempt, status: 'recovered' };
533+
}
534+
535+
export async function processExitResilienceWorkflow() {
536+
'use workflow';
537+
console.log('Starting process exit resilience workflow');
538+
539+
// This step should crash the process on the first attempt,
540+
// but the workflow should recover and retry on a new process
541+
const result = await stepThatExitsOnFirstAttempt();
542+
543+
console.log(
544+
`Workflow completed successfully after process crash: ${JSON.stringify(result)}`
545+
);
546+
return result;
547+
}

0 commit comments

Comments
 (0)