Skip to content

Commit 0c43f12

Browse files
refactor(tests): split up test suites, update docs
1 parent e5bbde9 commit 0c43f12

File tree

7 files changed

+461
-366
lines changed

7 files changed

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

test/builtins.js

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

0 commit comments

Comments
 (0)