Skip to content

Commit 5a75801

Browse files
refactor(tests): split up test suites, update docs (#278)
* refactor(tests): split up test suites, update docs * fix(tests): more careful server instantiation * refactor(tests): disable concurrent tests for weval runs * fix(ci): max concurrent suites during weval test
1 parent 5f2f951 commit 5a75801

File tree

9 files changed

+500
-376
lines changed

9 files changed

+500
-376
lines changed

README.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ Establishing this initial prototype as a singular flexible engine foundation tha
5252
### Weval AOT Compilation
5353

5454

55-
5655
## Platform APIs
5756

5857
The following APIs are available:
@@ -330,21 +329,20 @@ imports. Direct component analysis should be used to correctly infer the real im
330329

331330
## Contributing
332331

333-
### Pre-requisites
332+
To contribute, you'll need to set up the following:
334333

335334
* `git submodule update --init --recursive` to update the submodules.
336335
* Stable Rust with the `wasm32-unknown-unknown` and `wasm32-wasi` targets
337336
installed.
338337
* `wasi-sdk-20.0` installed at `/opt/wasi-sdk/`
339338

340-
### Building and testing
339+
## Building
341340

342341
Building and testing the project can be performed via NPM scripts (see [`package.json`](./package.json)):
343342

344343
```console
345344
npm install
346345
npm run build
347-
npm run test
348346
```
349347

350348
Before being able to use `componetize-js` (ex. via `npm link`, from `jco`), you'll need to run:
@@ -361,12 +359,30 @@ To clean up a local installation (i.e. remove the installation of StarlingMonkey
361359
npm run clean
362360
```
363361

362+
## Testing
363+
364+
To run all tests:
365+
366+
```console
367+
npm run test
368+
```
369+
370+
### Running a specific test
371+
372+
To run a specific test suite, you can pass an argument to [`vitest`][vitest]:
373+
374+
```console
375+
npm run test -- test/wasi.js
376+
```
377+
378+
[vitest]: https://vitest.dev
379+
364380
# License
365381

366382
This project is licensed under the Apache 2.0 license with the LLVM exception.
367383
See [LICENSE](LICENSE) for more details.
368384

369-
### Contribution
385+
## Contribution
370386

371387
Unless you explicitly state otherwise, any contribution intentionally submitted
372388
for inclusion in this project by you, as defined in the Apache-2.0 license,

test/bindings.js

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { fileURLToPath, URL } from 'node:url';
2+
import { readFile, readdir, mkdir, writeFile } from 'node:fs/promises';
3+
4+
import { componentize } from '@bytecodealliance/componentize-js';
5+
import { transpile } from '@bytecodealliance/jco';
6+
7+
import { suite, test } from 'vitest';
8+
9+
import {
10+
DEBUG_TRACING_ENABLED,
11+
WEVAL_TEST_ENABLED,
12+
DEBUG_TEST_ENABLED,
13+
maybeLogging,
14+
} from './util.js';
15+
16+
suite('Bindings', async () => {
17+
const bindingsCases = await readdir(new URL('./cases', import.meta.url));
18+
19+
for (const name of bindingsCases) {
20+
const testFn = WEVAL_TEST_ENABLED ? test : test.concurrent;
21+
testFn(name, async () => {
22+
const source = await readFile(
23+
new URL(`./cases/${name}/source.js`, import.meta.url),
24+
'utf8',
25+
);
26+
27+
const test = await import(`./cases/${name}/test.js`);
28+
29+
// Determine the relevant WIT world to use
30+
let witWorld,
31+
witPath,
32+
worldName,
33+
isWasiTarget = false;
34+
if (test.worldName) {
35+
witPath = fileURLToPath(new URL('./wit', import.meta.url));
36+
worldName = test.worldName;
37+
isWasiTarget = true;
38+
} else {
39+
try {
40+
witWorld = await readFile(
41+
new URL(`./cases/${name}/world.wit`, import.meta.url),
42+
'utf8',
43+
);
44+
} catch (e) {
45+
if (e?.code == 'ENOENT') {
46+
try {
47+
isWasiTarget = true;
48+
witPath = fileURLToPath(
49+
new URL(`./cases/${name}/wit`, import.meta.url),
50+
);
51+
await readdir(witPath);
52+
} catch (e) {
53+
if (e?.code === 'ENOENT') {
54+
witPath = fileURLToPath(new URL('./wit', import.meta.url));
55+
worldName = 'test2';
56+
} else {
57+
throw e;
58+
}
59+
}
60+
} else {
61+
throw e;
62+
}
63+
}
64+
}
65+
66+
const enableFeatures = test.enableFeatures || ['http'];
67+
const disableFeatures =
68+
test.disableFeatures ||
69+
(isWasiTarget ? [] : ['random', 'clocks', 'http', 'stdio']);
70+
71+
let testArg;
72+
try {
73+
const { component, imports } = await componentize(source, {
74+
sourceName: `${name}.js`,
75+
witWorld,
76+
witPath,
77+
worldName,
78+
enableFeatures,
79+
disableFeatures: maybeLogging(disableFeatures),
80+
enableAot: WEVAL_TEST_ENABLED,
81+
debugBuild: DEBUG_TEST_ENABLED,
82+
});
83+
const map = {
84+
'wasi:cli-base/*': '@bytecodealliance/preview2-shim/cli-base#*',
85+
'wasi:clocks/*': '@bytecodealliance/preview2-shim/clocks#*',
86+
'wasi:filesystem/*': '@bytecodealliance/preview2-shim/filesystem#*',
87+
'wasi:http/*': '@bytecodealliance/preview2-shim/http#*',
88+
'wasi:io/*': '@bytecodealliance/preview2-shim/io#*',
89+
'wasi:logging/*': '@bytecodealliance/preview2-shim/logging#*',
90+
'wasi:poll/*': '@bytecodealliance/preview2-shim/poll#*',
91+
'wasi:random/*': '@bytecodealliance/preview2-shim/random#*',
92+
'wasi:sockets/*': '@bytecodealliance/preview2-shim/sockets#*',
93+
};
94+
for (let [impt] of imports) {
95+
if (impt.startsWith('wasi:')) continue;
96+
if (impt.startsWith('[')) impt = impt.slice(impt.indexOf(']') + 1);
97+
let importName = impt.split('/').pop();
98+
if (importName === 'test') importName = 'imports';
99+
map[impt] = `../../cases/${name}/${importName}.js`;
100+
}
101+
102+
const {
103+
files,
104+
imports: componentImports,
105+
exports: componentExports,
106+
} = await transpile(component, {
107+
name,
108+
map,
109+
wasiShim: true,
110+
validLiftingOptimization: false,
111+
tracing: DEBUG_TRACING_ENABLED,
112+
});
113+
114+
testArg = { imports, componentImports, componentExports };
115+
116+
await mkdir(new URL(`./output/${name}/interfaces`, import.meta.url), {
117+
recursive: true,
118+
});
119+
120+
await writeFile(
121+
new URL(`./output/${name}.component.wasm`, import.meta.url),
122+
component,
123+
);
124+
125+
for (const file of Object.keys(files)) {
126+
let source = files[file];
127+
await writeFile(
128+
new URL(`./output/${name}/${file}`, import.meta.url),
129+
source,
130+
);
131+
}
132+
133+
const outputPath = fileURLToPath(
134+
new URL(`./output/${name}/${name}.js`, import.meta.url),
135+
);
136+
var instance = await import(outputPath);
137+
} catch (e) {
138+
if (test.err) {
139+
test.err(e);
140+
return;
141+
}
142+
throw e;
143+
}
144+
await test.test(instance, testArg);
145+
});
146+
}
147+
});

test/builtins.js

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { readFile, readdir, mkdir, writeFile } from 'node:fs/promises';
2+
import { spawn } from 'node:child_process';
3+
import { fileURLToPath, URL } from 'node:url';
4+
5+
import { componentize } from '@bytecodealliance/componentize-js';
6+
import { transpile } from '@bytecodealliance/jco';
7+
8+
import { suite, test, assert } from 'vitest';
9+
10+
import {
11+
DEBUG_TRACING_ENABLED,
12+
WEVAL_TEST_ENABLED,
13+
DEBUG_TEST_ENABLED,
14+
maybeLogging,
15+
} from './util.js';
16+
17+
suite('Builtins', async () => {
18+
const builtins = await readdir(new URL('./builtins', import.meta.url));
19+
20+
for (const filename of builtins) {
21+
const name = filename.slice(0, -3);
22+
const testFn = WEVAL_TEST_ENABLED ? test : test.concurrent;
23+
testFn(name, async () => {
24+
const {
25+
source,
26+
test: runTest,
27+
disableFeatures,
28+
enableFeatures,
29+
} = await import(`./builtins/${name}.js`);
30+
31+
const { component } = await componentize(
32+
source,
33+
`
34+
package local:runworld;
35+
world runworld {
36+
export run: func();
37+
}
38+
`,
39+
{
40+
sourceName: `${name}.js`,
41+
// also test the debug build while we are about it (unless testing Weval)
42+
debugBuild: DEBUG_TEST_ENABLED,
43+
enableFeatures,
44+
disableFeatures: maybeLogging(disableFeatures),
45+
enableAot: WEVAL_TEST_ENABLED,
46+
},
47+
);
48+
49+
const { files } = await transpile(component, {
50+
name,
51+
wasiShim: true,
52+
tracing: DEBUG_TRACING_ENABLED,
53+
});
54+
55+
await mkdir(new URL(`./output/${name}/interfaces`, import.meta.url), {
56+
recursive: true,
57+
});
58+
59+
await writeFile(
60+
new URL(`./output/${name}.component.wasm`, import.meta.url),
61+
component,
62+
);
63+
64+
for (const file of Object.keys(files)) {
65+
await writeFile(
66+
new URL(`./output/${name}/${file}`, import.meta.url),
67+
files[file],
68+
);
69+
}
70+
71+
await writeFile(
72+
new URL(`./output/${name}/run.js`, import.meta.url),
73+
`
74+
import { run } from './${name}.js';
75+
run();
76+
`,
77+
);
78+
79+
try {
80+
await runTest(async function run() {
81+
let stdout = '',
82+
stderr = '',
83+
timeout;
84+
try {
85+
await new Promise((resolve, reject) => {
86+
const cp = spawn(
87+
process.argv[0],
88+
[
89+
fileURLToPath(
90+
new URL(`./output/${name}/run.js`, import.meta.url),
91+
),
92+
],
93+
{ stdio: 'pipe' },
94+
);
95+
cp.stdout.on('data', (chunk) => {
96+
stdout += chunk;
97+
});
98+
cp.stderr.on('data', (chunk) => {
99+
stderr += chunk;
100+
});
101+
cp.on('error', reject);
102+
cp.on('exit', (code) =>
103+
code === 0 ? resolve() : reject(new Error(stderr || stdout)),
104+
);
105+
timeout = setTimeout(() => {
106+
reject(
107+
new Error(
108+
'test timed out with output:\n' +
109+
stdout +
110+
'\n\nstderr:\n' +
111+
stderr,
112+
),
113+
);
114+
}, 10_000);
115+
});
116+
} catch (err) {
117+
throw { err, stdout, stderr };
118+
} finally {
119+
clearTimeout(timeout);
120+
}
121+
122+
return { stdout, stderr };
123+
});
124+
} catch (err) {
125+
if (err.stderr) console.error(err.stderr);
126+
throw err.err || err;
127+
}
128+
});
129+
}
130+
});

test/cases/fetch-event-server/test.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ export const enableFeatures = ['http', 'fetch-event'];
88
export const worldName = 'test3';
99

1010
export async function test(instance) {
11-
const server = new HTTPServer(instance.incomingHandler);
12-
let port = await getRandomPort();
13-
server.listen(port);
14-
11+
let server;
1512
try {
13+
server = new HTTPServer(instance.incomingHandler);
14+
let port = await getRandomPort();
15+
server.listen(port);
1616
const resp = await fetch(`http://localhost:${port}`);
1717
const text = await resp.text();
1818
strictEqual(text, 'Hello World');
1919
} finally {
20-
server.stop();
20+
if (server) {
21+
server.stop();
22+
}
2123
}
2224
}

test/cases/http-server/test.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ export const enableFeatures = ['http'];
88
export const worldName = 'test3';
99

1010
export async function test(instance) {
11-
const server = new HTTPServer(instance.incomingHandler);
12-
let port = await getRandomPort();
13-
server.listen(port);
14-
11+
let server;
1512
try {
13+
server = new HTTPServer(instance.incomingHandler);
14+
let port = await getRandomPort();
15+
server.listen(port);
1616
const resp = await fetch(`http://localhost:${port}`);
1717
const text = await resp.text();
1818
strictEqual(text, 'Hello world!');
1919
} finally {
20-
server.stop();
20+
if (server) {
21+
server.stop();
22+
}
2123
}
2224
}

0 commit comments

Comments
 (0)