Skip to content

Commit 7aa0182

Browse files
refactor(tests): use ephemeral local http server for fetch test
Signed-off-by: Victor Adossi <vadossi@cosmonic.com>
1 parent 2973656 commit 7aa0182

File tree

4 files changed

+119
-40
lines changed

4 files changed

+119
-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: 50 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')) {
@@ -24,12 +26,24 @@ suite('Builtins', () => {
2426
for (const filename of builtinsCases) {
2527
const name = filename.slice(0, -3);
2628
test(name, async () => {
29+
const testModule = await import(`./builtins/${filename}`);
2730
const {
28-
source,
31+
state,
2932
test: runTest,
3033
disableFeatures,
3134
enableFeatures,
32-
} = await import(`./builtins/${filename}`);
35+
} = testModule;
36+
37+
// If an args object was provided, generate the arguments to feed to both
38+
// source generation (if necessary) and the test run itself
39+
let stateFn = state ?? noOp;
40+
const stateObj = await stateFn();
41+
42+
// If the source is a function then invoke it to generate the source string, possibly with arguments
43+
let source = testModule.source;
44+
if (typeof source === 'function') {
45+
source = source(stateObj);
46+
}
3347

3448
const { component } = await componentize(
3549
source,
@@ -46,7 +60,7 @@ suite('Builtins', () => {
4660
enableFeatures,
4761
disableFeatures: maybeLogging(disableFeatures),
4862
enableAot,
49-
}
63+
},
5064
);
5165

5266
const { files } = await transpile(component, {
@@ -61,13 +75,13 @@ suite('Builtins', () => {
6175

6276
await writeFile(
6377
new URL(`./output/${name}.component.wasm`, import.meta.url),
64-
component
78+
component,
6579
);
6680

6781
for (const file of Object.keys(files)) {
6882
await writeFile(
6983
new URL(`./output/${name}/${file}`, import.meta.url),
70-
files[file]
84+
files[file],
7185
);
7286
}
7387

@@ -76,11 +90,12 @@ suite('Builtins', () => {
7690
`
7791
import { run } from './${name}.js';
7892
run();
79-
`
93+
`,
8094
);
8195

8296
try {
83-
await runTest(async function run() {
97+
// Build a run function to pass to the test
98+
const runFn = async function run() {
8499
let stdout = '',
85100
stderr = '',
86101
timeout;
@@ -90,10 +105,10 @@ suite('Builtins', () => {
90105
process.argv[0],
91106
[
92107
fileURLToPath(
93-
new URL(`./output/${name}/run.js`, import.meta.url)
108+
new URL(`./output/${name}/run.js`, import.meta.url),
94109
),
95110
],
96-
{ stdio: 'pipe' }
111+
{ stdio: 'pipe' },
97112
);
98113
cp.stdout.on('data', (chunk) => {
99114
stdout += chunk;
@@ -103,16 +118,16 @@ suite('Builtins', () => {
103118
});
104119
cp.on('error', reject);
105120
cp.on('exit', (code) =>
106-
code === 0 ? resolve() : reject(new Error(stderr || stdout))
121+
code === 0 ? resolve() : reject(new Error(stderr || stdout)),
107122
);
108123
timeout = setTimeout(() => {
109124
reject(
110125
new Error(
111126
'test timed out with output:\n' +
112-
stdout +
113-
'\n\nstderr:\n' +
114-
stderr
115-
)
127+
stdout +
128+
'\n\nstderr:\n' +
129+
stderr,
130+
),
116131
);
117132
}, 10_000);
118133
});
@@ -123,7 +138,10 @@ suite('Builtins', () => {
123138
}
124139

125140
return { stdout, stderr };
126-
});
141+
};
142+
143+
// Run the actual test
144+
await runTest(runFn, stateObj);
127145
} catch (err) {
128146
if (err.stderr) console.error(err.stderr);
129147
throw err.err || err;
@@ -138,7 +156,7 @@ suite('Bindings', () => {
138156
test(name, async () => {
139157
const source = await readFile(
140158
new URL(`./cases/${name}/source.js`, import.meta.url),
141-
'utf8'
159+
'utf8',
142160
);
143161

144162
let witWorld,
@@ -148,14 +166,14 @@ suite('Bindings', () => {
148166
try {
149167
witWorld = await readFile(
150168
new URL(`./cases/${name}/world.wit`, import.meta.url),
151-
'utf8'
169+
'utf8',
152170
);
153171
} catch (e) {
154172
if (e?.code == 'ENOENT') {
155173
try {
156174
isWasiTarget = true;
157175
witPath = fileURLToPath(
158-
new URL(`./cases/${name}/wit`, import.meta.url)
176+
new URL(`./cases/${name}/wit`, import.meta.url),
159177
);
160178
await readdir(witPath);
161179
} catch (e) {
@@ -229,14 +247,14 @@ suite('Bindings', () => {
229247

230248
await writeFile(
231249
new URL(`./output/${name}.component.wasm`, import.meta.url),
232-
component
250+
component,
233251
);
234252

235253
for (const file of Object.keys(files)) {
236254
let source = files[file];
237255
await writeFile(
238256
new URL(`./output/${name}/${file}`, import.meta.url),
239-
source
257+
source,
240258
);
241259
}
242260

@@ -274,12 +292,12 @@ suite('WASI', () => {
274292
worldName: 'test1',
275293
enableAot,
276294
debugBuild,
277-
}
295+
},
278296
);
279297

280298
await writeFile(
281299
new URL(`./output/wasi.component.wasm`, import.meta.url),
282-
component
300+
component,
283301
);
284302

285303
const { files } = await transpile(component, { tracing: DEBUG_TRACING });
@@ -291,7 +309,7 @@ suite('WASI', () => {
291309
for (const file of Object.keys(files)) {
292310
await writeFile(
293311
new URL(`./output/wasi/${file}`, import.meta.url),
294-
files[file]
312+
files[file],
295313
);
296314
}
297315

@@ -303,19 +321,17 @@ suite('WASI', () => {
303321
});
304322

305323
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-
);
324+
const { component } = await componentize({
325+
sourcePath: './test/api/index.js',
326+
witPath: fileURLToPath(new URL('./wit', import.meta.url)),
327+
worldName: 'test1',
328+
enableAot,
329+
debugBuild,
330+
});
315331

316332
await writeFile(
317333
new URL(`./output/wasi.component.wasm`, import.meta.url),
318-
component
334+
component,
319335
);
320336

321337
const { files } = await transpile(component, { tracing: DEBUG_TRACING });
@@ -327,7 +343,7 @@ suite('WASI', () => {
327343
for (const file of Object.keys(files)) {
328344
await writeFile(
329345
new URL(`./output/wasi/${file}`, import.meta.url),
330-
files[file]
346+
files[file],
331347
);
332348
}
333349

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)