Skip to content

Commit 6c9382c

Browse files
refactor(tests): use ephemeral local http server for fetch test
Signed-off-by: Victor Adossi <[email protected]>
1 parent 2973656 commit 6c9382c

File tree

4 files changed

+120
-40
lines changed

4 files changed

+120
-40
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/builtins/fetch.js

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,70 @@
1+
import { URL, fileURLToPath } from 'node:url';
2+
import { createServer } from 'node:http';
3+
14
import { strictEqual, ok } from 'node:assert';
25

3-
export const source = `
6+
const FETCH_URL = 'http://localhost';
7+
8+
export const state = async () => {
9+
const { getRandomPort } = await import(
10+
fileURLToPath(new URL('../util.js', import.meta.url))
11+
);
12+
const port = await getRandomPort();
13+
return { port };
14+
};
15+
16+
export const source = (testState) => {
17+
let port = testState?.port ? ':' + testState.port : '';
18+
const url = FETCH_URL + port;
19+
return `
420
export async function run () {
5-
const res = await fetch('https://httpbin.org/anything');
21+
const res = await fetch('${url}');
622
const source = await res.json();
723
console.log(source.url);
824
}
925
export function ready () {
1026
return true;
1127
}
1228
`;
29+
};
1330

1431
export const enableFeatures = ['http'];
1532

16-
export async function test(run) {
33+
export async function test(run, testState) {
34+
// Get the randomly generated port
35+
const port = testState.port;
36+
if (!port) {
37+
throw new Error('missing port on test state');
38+
}
39+
40+
const url = FETCH_URL + (port ? ':' + port : '');
41+
42+
// Run a local server on some port
43+
const server = createServer(async (req, res) => {
44+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
45+
res.write(
46+
JSON.stringify({
47+
status: 'ok',
48+
url,
49+
}),
50+
);
51+
res.end();
52+
}).listen(port);
53+
54+
// Wait until the server is ready
55+
let ready = false;
56+
while (!ready) {
57+
try {
58+
const res = await fetch(url);
59+
ready = true;
60+
} catch (err) {
61+
await new Promise((resolve) => setTimeout(resolve, 250));
62+
}
63+
}
64+
1765
const { stdout, stderr } = await run();
1866
strictEqual(stderr, '');
19-
strictEqual(stdout.trim(), 'https://httpbin.org/anything');
67+
strictEqual(stdout.trim(), url);
68+
69+
server.close();
2070
}

test/test.js

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ const LOG_DEBUGGING = false;
1111
const enableAot = process.env.WEVAL_TEST == '1';
1212
const debugBuild = process.env.DEBUG_TEST == '1';
1313

14+
const noOp = async () => {};
15+
1416
function maybeLogging(disableFeatures) {
1517
if (!LOG_DEBUGGING) return disableFeatures;
1618
if (disableFeatures && disableFeatures.includes('stdio')) {
@@ -23,13 +25,26 @@ const builtinsCases = await readdir(new URL('./builtins', import.meta.url));
2325
suite('Builtins', () => {
2426
for (const filename of builtinsCases) {
2527
const name = filename.slice(0, -3);
28+
if (!name.includes("fetch")) { continue; }
2629
test(name, async () => {
30+
const testModule = await import(`./builtins/${filename}`);
2731
const {
28-
source,
32+
state,
2933
test: runTest,
3034
disableFeatures,
3135
enableFeatures,
32-
} = await import(`./builtins/${filename}`);
36+
} = testModule;
37+
38+
// If an args object was provided, generate the arguments to feed to both
39+
// source generation (if necessary) and the test run itself
40+
let stateFn = state ?? noOp;
41+
const stateObj = await stateFn();
42+
43+
// If the source is a function then invoke it to generate the source string, possibly with arguments
44+
let source = testModule.source;
45+
if (typeof source === 'function') {
46+
source = source(stateObj);
47+
}
3348

3449
const { component } = await componentize(
3550
source,
@@ -46,7 +61,7 @@ suite('Builtins', () => {
4661
enableFeatures,
4762
disableFeatures: maybeLogging(disableFeatures),
4863
enableAot,
49-
}
64+
},
5065
);
5166

5267
const { files } = await transpile(component, {
@@ -61,13 +76,13 @@ suite('Builtins', () => {
6176

6277
await writeFile(
6378
new URL(`./output/${name}.component.wasm`, import.meta.url),
64-
component
79+
component,
6580
);
6681

6782
for (const file of Object.keys(files)) {
6883
await writeFile(
6984
new URL(`./output/${name}/${file}`, import.meta.url),
70-
files[file]
85+
files[file],
7186
);
7287
}
7388

@@ -76,11 +91,12 @@ suite('Builtins', () => {
7691
`
7792
import { run } from './${name}.js';
7893
run();
79-
`
94+
`,
8095
);
8196

8297
try {
83-
await runTest(async function run() {
98+
// Build a run function to pass to the test
99+
const runFn = async function run() {
84100
let stdout = '',
85101
stderr = '',
86102
timeout;
@@ -90,10 +106,10 @@ suite('Builtins', () => {
90106
process.argv[0],
91107
[
92108
fileURLToPath(
93-
new URL(`./output/${name}/run.js`, import.meta.url)
109+
new URL(`./output/${name}/run.js`, import.meta.url),
94110
),
95111
],
96-
{ stdio: 'pipe' }
112+
{ stdio: 'pipe' },
97113
);
98114
cp.stdout.on('data', (chunk) => {
99115
stdout += chunk;
@@ -103,16 +119,16 @@ suite('Builtins', () => {
103119
});
104120
cp.on('error', reject);
105121
cp.on('exit', (code) =>
106-
code === 0 ? resolve() : reject(new Error(stderr || stdout))
122+
code === 0 ? resolve() : reject(new Error(stderr || stdout)),
107123
);
108124
timeout = setTimeout(() => {
109125
reject(
110126
new Error(
111127
'test timed out with output:\n' +
112-
stdout +
113-
'\n\nstderr:\n' +
114-
stderr
115-
)
128+
stdout +
129+
'\n\nstderr:\n' +
130+
stderr,
131+
),
116132
);
117133
}, 10_000);
118134
});
@@ -123,7 +139,10 @@ suite('Builtins', () => {
123139
}
124140

125141
return { stdout, stderr };
126-
});
142+
};
143+
144+
// Run the actual test
145+
await runTest(runFn, stateObj);
127146
} catch (err) {
128147
if (err.stderr) console.error(err.stderr);
129148
throw err.err || err;
@@ -138,7 +157,7 @@ suite('Bindings', () => {
138157
test(name, async () => {
139158
const source = await readFile(
140159
new URL(`./cases/${name}/source.js`, import.meta.url),
141-
'utf8'
160+
'utf8',
142161
);
143162

144163
let witWorld,
@@ -148,14 +167,14 @@ suite('Bindings', () => {
148167
try {
149168
witWorld = await readFile(
150169
new URL(`./cases/${name}/world.wit`, import.meta.url),
151-
'utf8'
170+
'utf8',
152171
);
153172
} catch (e) {
154173
if (e?.code == 'ENOENT') {
155174
try {
156175
isWasiTarget = true;
157176
witPath = fileURLToPath(
158-
new URL(`./cases/${name}/wit`, import.meta.url)
177+
new URL(`./cases/${name}/wit`, import.meta.url),
159178
);
160179
await readdir(witPath);
161180
} catch (e) {
@@ -229,14 +248,14 @@ suite('Bindings', () => {
229248

230249
await writeFile(
231250
new URL(`./output/${name}.component.wasm`, import.meta.url),
232-
component
251+
component,
233252
);
234253

235254
for (const file of Object.keys(files)) {
236255
let source = files[file];
237256
await writeFile(
238257
new URL(`./output/${name}/${file}`, import.meta.url),
239-
source
258+
source,
240259
);
241260
}
242261

@@ -274,12 +293,12 @@ suite('WASI', () => {
274293
worldName: 'test1',
275294
enableAot,
276295
debugBuild,
277-
}
296+
},
278297
);
279298

280299
await writeFile(
281300
new URL(`./output/wasi.component.wasm`, import.meta.url),
282-
component
301+
component,
283302
);
284303

285304
const { files } = await transpile(component, { tracing: DEBUG_TRACING });
@@ -291,7 +310,7 @@ suite('WASI', () => {
291310
for (const file of Object.keys(files)) {
292311
await writeFile(
293312
new URL(`./output/wasi/${file}`, import.meta.url),
294-
files[file]
313+
files[file],
295314
);
296315
}
297316

@@ -303,19 +322,17 @@ suite('WASI', () => {
303322
});
304323

305324
test('basic app (OriginalSourceFile API)', async () => {
306-
const { component } = await componentize(
307-
{
308-
sourcePath: "./test/api/index.js",
309-
witPath: fileURLToPath(new URL('./wit', import.meta.url)),
310-
worldName: 'test1',
311-
enableAot,
312-
debugBuild,
313-
}
314-
);
325+
const { component } = await componentize({
326+
sourcePath: './test/api/index.js',
327+
witPath: fileURLToPath(new URL('./wit', import.meta.url)),
328+
worldName: 'test1',
329+
enableAot,
330+
debugBuild,
331+
});
315332

316333
await writeFile(
317334
new URL(`./output/wasi.component.wasm`, import.meta.url),
318-
component
335+
component,
319336
);
320337

321338
const { files } = await transpile(component, { tracing: DEBUG_TRACING });
@@ -327,7 +344,7 @@ suite('WASI', () => {
327344
for (const file of Object.keys(files)) {
328345
await writeFile(
329346
new URL(`./output/wasi/${file}`, import.meta.url),
330-
files[file]
347+
files[file],
331348
);
332349
}
333350

test/util.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { createServer } from 'node:net';
2+
3+
// Utility function for getting a random port
4+
export async function getRandomPort() {
5+
return await new Promise((resolve) => {
6+
const server = createServer();
7+
server.listen(0, function () {
8+
const port = this.address().port;
9+
server.on('close', () => resolve(port));
10+
server.close();
11+
});
12+
});
13+
}

0 commit comments

Comments
 (0)