Skip to content

Commit 072e6e1

Browse files
committed
Improve test runner
1 parent 581d0a4 commit 072e6e1

File tree

2 files changed

+73
-85
lines changed

2 files changed

+73
-85
lines changed

dev-packages/cloudflare-integration-tests/runner.ts

Lines changed: 72 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,41 @@ import { join } from 'path';
77
import { inspect } from 'util';
88
import { expect } from 'vitest';
99

10-
/** Promise only resolves when fn returns true */
11-
async function waitFor(fn: () => boolean, timeout = 10_000, message = 'Timed out waiting'): Promise<void> {
12-
let remaining = timeout;
13-
while (fn() === false) {
14-
await new Promise<void>(resolve => setTimeout(resolve, 100));
15-
remaining -= 100;
16-
if (remaining < 0) {
17-
throw new Error(message);
18-
}
10+
const CLEANUP_STEPS = new Set<() => void>();
11+
12+
export function cleanupChildProcesses(): void {
13+
for (const step of CLEANUP_STEPS) {
14+
step();
1915
}
16+
CLEANUP_STEPS.clear();
2017
}
2118

22-
type VoidFunction = () => void;
19+
process.on('exit', cleanupChildProcesses);
20+
21+
function deferredPromise<T = void>(
22+
done?: () => void,
23+
): { resolve: (val: T) => void; reject: (reason?: unknown) => void; promise: Promise<T> } {
24+
let resolve;
25+
let reject;
26+
const promise = new Promise<T>((res, rej) => {
27+
resolve = (val: T) => {
28+
done?.();
29+
res(val);
30+
};
31+
reject = (reason: Error) => {
32+
done?.();
33+
rej(reason);
34+
};
35+
});
36+
if (!resolve || !reject) {
37+
throw new Error('Failed to create deferred promise');
38+
}
39+
return {
40+
resolve,
41+
reject,
42+
promise,
43+
};
44+
}
2345

2446
type Expected = Envelope | ((envelope: Envelope) => void);
2547

@@ -41,7 +63,6 @@ export function createRunner(...paths: string[]) {
4163
throw new Error(`Test scenario not found: ${testPath}`);
4264
}
4365

44-
const cleanupSteps = new Set<VoidFunction>();
4566
const expectedEnvelopes: Expected[] = [];
4667
// By default, we ignore session & sessions
4768
const ignored: Set<EnvelopeItemType> = new Set(['session', 'sessions', 'client_report']);
@@ -68,32 +89,18 @@ export function createRunner(...paths: string[]) {
6889
return this;
6990
},
7091
start: function (): StartResult {
71-
let isComplete = false;
72-
let completeError: Error | undefined;
73-
92+
const { resolve, reject, promise: isComplete } = deferredPromise(cleanupChildProcesses);
7493
const expectedEnvelopeCount = expectedEnvelopes.length;
7594

7695
let envelopeCount = 0;
77-
let scenarioServerPort: number | undefined;
96+
const { resolve: setWorkerPort, promise: workerPortPromise } = deferredPromise<number>();
7897
let child: ReturnType<typeof spawn> | undefined;
7998

80-
function complete(error?: Error): void {
81-
if (isComplete) {
82-
return;
83-
}
84-
85-
isComplete = true;
86-
completeError = error || undefined;
87-
for (const step of cleanupSteps) {
88-
step();
89-
}
90-
}
91-
9299
/** Called after each expect callback to check if we're complete */
93100
function expectCallbackCalled(): void {
94101
envelopeCount++;
95102
if (envelopeCount === expectedEnvelopeCount) {
96-
complete();
103+
resolve();
97104
}
98105
}
99106

@@ -121,87 +128,68 @@ export function createRunner(...paths: string[]) {
121128
}
122129
expectCallbackCalled();
123130
} catch (e) {
124-
complete(e as Error);
131+
reject(e);
125132
}
126133
}
127134

128135
createBasicSentryServer(newEnvelope)
129136
.then(([mockServerPort, mockServerClose]) => {
130137
if (mockServerClose) {
131-
cleanupSteps.add(() => {
138+
CLEANUP_STEPS.add(() => {
132139
mockServerClose();
133140
});
134141
}
135142

136-
// const env = { ...process.env, ...withEnv, SENTRY_DSN: `http://public@localhost:${mockServerPort}/1337` };
137-
138-
const SENTRY_DSN = `http://public@localhost:${mockServerPort}/1337`;
139-
140-
if (process.env.DEBUG) log('starting scenario', { testPath, SENTRY_DSN });
141-
142-
const wranglerConfigPath = join(testPath, 'wrangler.jsonc');
143-
144-
child = spawn('wrangler', ['dev', '--config', wranglerConfigPath, '--var', `SENTRY_DSN:${SENTRY_DSN}`]);
143+
if (process.env.DEBUG) log('Starting scenario', testPath);
144+
145+
const stdio: ('inherit' | 'ipc' | 'ignore')[] = process.env.DEBUG
146+
? ['inherit', 'inherit', 'inherit', 'ipc']
147+
: ['ignore', 'ignore', 'ignore', 'ipc'];
148+
149+
child = spawn(
150+
'wrangler',
151+
[
152+
'dev',
153+
'--config',
154+
join(testPath, 'wrangler.jsonc'),
155+
'--show-interactive-dev-session',
156+
'false',
157+
'--var',
158+
`SENTRY_DSN:http://public@localhost:${mockServerPort}/1337`,
159+
],
160+
{ stdio },
161+
);
162+
163+
CLEANUP_STEPS.add(() => {
164+
child?.kill();
165+
});
145166

146167
child.on('error', e => {
147168
// eslint-disable-next-line no-console
148169
console.error('Error starting child process:', e);
149-
complete(e);
170+
reject(e);
150171
});
151172

152-
cleanupSteps.add(() => {
153-
child?.kill();
154-
});
155-
156-
if (process.env.DEBUG) {
157-
child.stderr?.on('data', (data: Buffer) => {
158-
log('stderr line', data.toString());
159-
});
160-
}
161-
162-
child.stdout?.on('data', (data: Buffer) => {
163-
if (scenarioServerPort === undefined) {
164-
const line = data.toString();
165-
const result = line.match(/Ready on http:\/\/localhost:(\d+)/);
166-
if (result?.[1]) {
167-
scenarioServerPort = parseInt(result[1], 10);
168-
}
169-
}
170-
171-
if (process.env.DEBUG) {
172-
log('stdout line', data.toString());
173+
child.on('message', (message: string) => {
174+
const msg = JSON.parse(message) as { event: string; port?: number };
175+
if (msg.event === 'DEV_SERVER_READY' && typeof msg.port === 'number') {
176+
setWorkerPort(msg.port);
177+
if (process.env.DEBUG) log('worker ready on port', msg.port);
173178
}
174179
});
175-
176-
// Pass error to done to end the test quickly
177-
child.on('error', e => {
178-
if (process.env.DEBUG) log('scenario error', e);
179-
complete(e);
180-
});
181180
})
182-
.catch(e => complete(e));
181+
.catch(e => reject(e));
183182

184183
return {
185184
completed: async function (): Promise<void> {
186-
await waitFor(() => isComplete, 120_000, 'Timed out waiting for test to complete');
187-
188-
if (completeError) {
189-
throw completeError;
190-
}
185+
return isComplete;
191186
},
192187
makeRequest: async function <T>(
193188
method: 'get' | 'post',
194189
path: string,
195190
options: { headers?: Record<string, string>; data?: BodyInit; expectError?: boolean } = {},
196191
): Promise<T | undefined> {
197-
try {
198-
await waitFor(() => scenarioServerPort !== undefined, 10_000, 'Timed out waiting for server port');
199-
} catch (e) {
200-
complete(e as Error);
201-
return;
202-
}
203-
204-
const url = `http://localhost:${scenarioServerPort}${path}`;
192+
const url = `http://localhost:${await workerPortPromise}${path}`;
205193
const body = options.data;
206194
const headers = options.headers || {};
207195
const expectError = options.expectError || false;
@@ -213,14 +201,14 @@ export function createRunner(...paths: string[]) {
213201

214202
if (!res.ok) {
215203
if (!expectError) {
216-
complete(new Error(`Expected request to "${path}" to succeed, but got a ${res.status} response`));
204+
reject(new Error(`Expected request to "${path}" to succeed, but got a ${res.status} response`));
217205
}
218206

219207
return;
220208
}
221209

222210
if (expectError) {
223-
complete(new Error(`Expected request to "${path}" to fail, but got a ${res.status} response`));
211+
reject(new Error(`Expected request to "${path}" to fail, but got a ${res.status} response`));
224212
return;
225213
}
226214

@@ -234,7 +222,7 @@ export function createRunner(...paths: string[]) {
234222
return;
235223
}
236224

237-
complete(e as Error);
225+
reject(e);
238226
return;
239227
}
240228
},

dev-packages/cloudflare-integration-tests/vite.config.mts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default defineConfig({
1010
},
1111
isolate: false,
1212
include: ['./suites/**/test.ts'],
13-
testTimeout: 15000,
13+
testTimeout: 20_000,
1414
// Ensure we can see debug output when DEBUG=true
1515
...(process.env.DEBUG
1616
? {

0 commit comments

Comments
 (0)