mirror of
https://github.com/vercel/workflow.git
synced 2026-09-14 19:59:43 +08:00
234d3dd7b8
## Summary & Motivation The e2e `start()` wrappers now poll the new run until it leaves `pending`. A run still pending after `WORKFLOW_E2E_PICKUP_BUDGET_MS` (default 15s) has executed no workflow code, so it is abandoned and replaced in place and the test continues — one replacement, with the CI-level retry still the backstop if that one stalls too. Each replacement is recorded to an `e2e-infra-*.json` sidecar that every e2e job uploads, and the aggregation script renders it as an "Infra Events" section in the step summary and PR comment, so clustered timestamps read as a backend blip rather than as unrelated flaky tests. ## Test Plan Unit tests cover the pickup watchdog; a local nextjs-turbopack run exercised both the clean path and, with a forced 1ms budget, the replacement path end to end, and the aggregation script was smoke-tested against synthetic sidecars in both modes. Signed-off-by: Alex Langenfeld <alex.langenfeld@vercel.com>
133 lines
3.9 KiB
TypeScript
133 lines
3.9 KiB
TypeScript
import { afterEach, describe, expect, test } from 'vitest';
|
|
import { hasStepSourceMaps, waitForRunPickup } from './utils';
|
|
|
|
const ORIGINAL_ENV = { ...process.env };
|
|
|
|
function setStepSourceMapEnv({
|
|
appName,
|
|
dev,
|
|
}: {
|
|
appName: string;
|
|
dev: boolean;
|
|
}) {
|
|
process.env.APP_NAME = appName;
|
|
process.env.DEPLOYMENT_URL = 'http://localhost:3000';
|
|
|
|
if (dev) {
|
|
process.env.DEV_TEST_CONFIG = '{}';
|
|
} else {
|
|
delete process.env.DEV_TEST_CONFIG;
|
|
}
|
|
}
|
|
|
|
afterEach(() => {
|
|
process.env = { ...ORIGINAL_ENV };
|
|
});
|
|
|
|
describe('hasStepSourceMaps', () => {
|
|
test('expects source filenames for webpack local dev', () => {
|
|
setStepSourceMapEnv({
|
|
appName: 'nextjs-webpack',
|
|
dev: true,
|
|
});
|
|
|
|
expect(hasStepSourceMaps()).toBe(true);
|
|
});
|
|
|
|
test('does not expect source filenames for turbopack local dev', () => {
|
|
setStepSourceMapEnv({
|
|
appName: 'nextjs-turbopack',
|
|
dev: true,
|
|
});
|
|
|
|
expect(hasStepSourceMaps()).toBe(false);
|
|
});
|
|
|
|
test('does not expect source filenames for webpack local production builds', () => {
|
|
setStepSourceMapEnv({
|
|
appName: 'nextjs-webpack',
|
|
dev: false,
|
|
});
|
|
|
|
expect(hasStepSourceMaps()).toBe(false);
|
|
});
|
|
|
|
test('expects source filenames for a framework in local dev', () => {
|
|
setStepSourceMapEnv({ appName: 'express', dev: true });
|
|
|
|
expect(hasStepSourceMaps()).toBe(true);
|
|
});
|
|
|
|
test('does not expect source filenames for a framework in local production', () => {
|
|
setStepSourceMapEnv({ appName: 'express', dev: false });
|
|
|
|
expect(hasStepSourceMaps()).toBe(false);
|
|
});
|
|
|
|
test('does not expect source filenames for nest, even in local dev', () => {
|
|
// The Nest integration does not signal a dev build, so source maps default
|
|
// to off (dev-on/prod-off) in both dev and prod.
|
|
setStepSourceMapEnv({ appName: 'nest', dev: true });
|
|
expect(hasStepSourceMaps()).toBe(false);
|
|
|
|
setStepSourceMapEnv({ appName: 'nest', dev: false });
|
|
expect(hasStepSourceMaps()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('waitForRunPickup', () => {
|
|
const runWithStatuses = (statuses: string[]) => {
|
|
let reads = 0;
|
|
return {
|
|
get status() {
|
|
const status = statuses[Math.min(reads, statuses.length - 1)];
|
|
reads++;
|
|
return Promise.resolve(status);
|
|
},
|
|
get reads() {
|
|
return reads;
|
|
},
|
|
};
|
|
};
|
|
|
|
test('resolves true on the first read for a picked-up run', async () => {
|
|
const run = runWithStatuses(['running']);
|
|
// biome-ignore lint/suspicious/noExplicitAny: minimal Run stand-in
|
|
await expect(waitForRunPickup(run as any, 5_000)).resolves.toBe(true);
|
|
expect(run.reads).toBe(1);
|
|
});
|
|
|
|
test('any non-pending status counts as picked up, including terminal ones', async () => {
|
|
const run = runWithStatuses(['completed']);
|
|
// biome-ignore lint/suspicious/noExplicitAny: minimal Run stand-in
|
|
await expect(waitForRunPickup(run as any, 5_000)).resolves.toBe(true);
|
|
});
|
|
|
|
test('polls through pending until the run is picked up', async () => {
|
|
const run = runWithStatuses(['pending', 'pending', 'running']);
|
|
// biome-ignore lint/suspicious/noExplicitAny: minimal Run stand-in
|
|
await expect(waitForRunPickup(run as any, 10_000)).resolves.toBe(true);
|
|
expect(run.reads).toBe(3);
|
|
});
|
|
|
|
test('resolves false when the run never leaves pending within the budget', async () => {
|
|
const run = runWithStatuses(['pending']);
|
|
// biome-ignore lint/suspicious/noExplicitAny: minimal Run stand-in
|
|
await expect(waitForRunPickup(run as any, 1_200)).resolves.toBe(false);
|
|
});
|
|
|
|
test('keeps polling through transient status-read failures', async () => {
|
|
let reads = 0;
|
|
const run = {
|
|
get status() {
|
|
reads++;
|
|
return reads < 2
|
|
? Promise.reject(new Error('transient'))
|
|
: Promise.resolve('running');
|
|
},
|
|
};
|
|
// biome-ignore lint/suspicious/noExplicitAny: minimal Run stand-in
|
|
await expect(waitForRunPickup(run as any, 5_000)).resolves.toBe(true);
|
|
});
|
|
});
|