Skip to content

refactor(tests): split up test suites, update docs #278

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ Establishing this initial prototype as a singular flexible engine foundation tha
### Weval AOT Compilation



## Platform APIs

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

## Contributing

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

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

### Building and testing
## Building

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

```console
npm install
npm run build
npm run test
```

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

## Testing

To run all tests:

```console
npm run test
```

### Running a specific test

To run a specific test suite, you can pass an argument to [`vitest`][vitest]:

```console
npm run test -- test/wasi.js
```

[vitest]: https://vitest.dev

# License

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

### Contribution
## Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this project by you, as defined in the Apache-2.0 license,
Expand Down
147 changes: 147 additions & 0 deletions test/bindings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { fileURLToPath, URL } from 'node:url';
import { readFile, readdir, mkdir, writeFile } from 'node:fs/promises';

import { componentize } from '@bytecodealliance/componentize-js';
import { transpile } from '@bytecodealliance/jco';

import { suite, test } from 'vitest';

import {
DEBUG_TRACING_ENABLED,
WEVAL_TEST_ENABLED,
DEBUG_TEST_ENABLED,
maybeLogging,
} from './util.js';

suite('Bindings', async () => {
const bindingsCases = await readdir(new URL('./cases', import.meta.url));

for (const name of bindingsCases) {
const testFn = WEVAL_TEST_ENABLED ? test : test.concurrent;
testFn(name, async () => {
const source = await readFile(
new URL(`./cases/${name}/source.js`, import.meta.url),
'utf8',
);

const test = await import(`./cases/${name}/test.js`);

// Determine the relevant WIT world to use
let witWorld,
witPath,
worldName,
isWasiTarget = false;
if (test.worldName) {
witPath = fileURLToPath(new URL('./wit', import.meta.url));
worldName = test.worldName;
isWasiTarget = true;
} else {
try {
witWorld = await readFile(
new URL(`./cases/${name}/world.wit`, import.meta.url),
'utf8',
);
} catch (e) {
if (e?.code == 'ENOENT') {
try {
isWasiTarget = true;
witPath = fileURLToPath(
new URL(`./cases/${name}/wit`, import.meta.url),
);
await readdir(witPath);
} catch (e) {
if (e?.code === 'ENOENT') {
witPath = fileURLToPath(new URL('./wit', import.meta.url));
worldName = 'test2';
} else {
throw e;
}
}
} else {
throw e;
}
}
}

const enableFeatures = test.enableFeatures || ['http'];
const disableFeatures =
test.disableFeatures ||
(isWasiTarget ? [] : ['random', 'clocks', 'http', 'stdio']);

let testArg;
try {
const { component, imports } = await componentize(source, {
sourceName: `${name}.js`,
witWorld,
witPath,
worldName,
enableFeatures,
disableFeatures: maybeLogging(disableFeatures),
enableAot: WEVAL_TEST_ENABLED,
debugBuild: DEBUG_TEST_ENABLED,
});
const map = {
'wasi:cli-base/*': '@bytecodealliance/preview2-shim/cli-base#*',
'wasi:clocks/*': '@bytecodealliance/preview2-shim/clocks#*',
'wasi:filesystem/*': '@bytecodealliance/preview2-shim/filesystem#*',
'wasi:http/*': '@bytecodealliance/preview2-shim/http#*',
'wasi:io/*': '@bytecodealliance/preview2-shim/io#*',
'wasi:logging/*': '@bytecodealliance/preview2-shim/logging#*',
'wasi:poll/*': '@bytecodealliance/preview2-shim/poll#*',
'wasi:random/*': '@bytecodealliance/preview2-shim/random#*',
'wasi:sockets/*': '@bytecodealliance/preview2-shim/sockets#*',
};
for (let [impt] of imports) {
if (impt.startsWith('wasi:')) continue;
if (impt.startsWith('[')) impt = impt.slice(impt.indexOf(']') + 1);
let importName = impt.split('/').pop();
if (importName === 'test') importName = 'imports';
map[impt] = `../../cases/${name}/${importName}.js`;
}

const {
files,
imports: componentImports,
exports: componentExports,
} = await transpile(component, {
name,
map,
wasiShim: true,
validLiftingOptimization: false,
tracing: DEBUG_TRACING_ENABLED,
});

testArg = { imports, componentImports, componentExports };

await mkdir(new URL(`./output/${name}/interfaces`, import.meta.url), {
recursive: true,
});

await writeFile(
new URL(`./output/${name}.component.wasm`, import.meta.url),
component,
);

for (const file of Object.keys(files)) {
let source = files[file];
await writeFile(
new URL(`./output/${name}/${file}`, import.meta.url),
source,
);
}

const outputPath = fileURLToPath(
new URL(`./output/${name}/${name}.js`, import.meta.url),
);
var instance = await import(outputPath);
} catch (e) {
if (test.err) {
test.err(e);
return;
}
throw e;
}
await test.test(instance, testArg);
});
}
});
130 changes: 130 additions & 0 deletions test/builtins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { readFile, readdir, mkdir, writeFile } from 'node:fs/promises';
import { spawn } from 'node:child_process';
import { fileURLToPath, URL } from 'node:url';

import { componentize } from '@bytecodealliance/componentize-js';
import { transpile } from '@bytecodealliance/jco';

import { suite, test, assert } from 'vitest';

import {
DEBUG_TRACING_ENABLED,
WEVAL_TEST_ENABLED,
DEBUG_TEST_ENABLED,
maybeLogging,
} from './util.js';

suite('Builtins', async () => {
const builtins = await readdir(new URL('./builtins', import.meta.url));

for (const filename of builtins) {
const name = filename.slice(0, -3);
const testFn = WEVAL_TEST_ENABLED ? test : test.concurrent;
testFn(name, async () => {
const {
source,
test: runTest,
disableFeatures,
enableFeatures,
} = await import(`./builtins/${name}.js`);

const { component } = await componentize(
source,
`
package local:runworld;
world runworld {
export run: func();
}
`,
{
sourceName: `${name}.js`,
// also test the debug build while we are about it (unless testing Weval)
debugBuild: DEBUG_TEST_ENABLED,
enableFeatures,
disableFeatures: maybeLogging(disableFeatures),
enableAot: WEVAL_TEST_ENABLED,
},
);

const { files } = await transpile(component, {
name,
wasiShim: true,
tracing: DEBUG_TRACING_ENABLED,
});

await mkdir(new URL(`./output/${name}/interfaces`, import.meta.url), {
recursive: true,
});

await writeFile(
new URL(`./output/${name}.component.wasm`, import.meta.url),
component,
);

for (const file of Object.keys(files)) {
await writeFile(
new URL(`./output/${name}/${file}`, import.meta.url),
files[file],
);
}

await writeFile(
new URL(`./output/${name}/run.js`, import.meta.url),
`
import { run } from './${name}.js';
run();
`,
);

try {
await runTest(async function run() {
let stdout = '',
stderr = '',
timeout;
try {
await new Promise((resolve, reject) => {
const cp = spawn(
process.argv[0],
[
fileURLToPath(
new URL(`./output/${name}/run.js`, import.meta.url),
),
],
{ stdio: 'pipe' },
);
cp.stdout.on('data', (chunk) => {
stdout += chunk;
});
cp.stderr.on('data', (chunk) => {
stderr += chunk;
});
cp.on('error', reject);
cp.on('exit', (code) =>
code === 0 ? resolve() : reject(new Error(stderr || stdout)),
);
timeout = setTimeout(() => {
reject(
new Error(
'test timed out with output:\n' +
stdout +
'\n\nstderr:\n' +
stderr,
),
);
}, 10_000);
});
} catch (err) {
throw { err, stdout, stderr };
} finally {
clearTimeout(timeout);
}

return { stdout, stderr };
});
} catch (err) {
if (err.stderr) console.error(err.stderr);
throw err.err || err;
}
});
}
});
12 changes: 7 additions & 5 deletions test/cases/fetch-event-server/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ export const enableFeatures = ['http', 'fetch-event'];
export const worldName = 'test3';

export async function test(instance) {
const server = new HTTPServer(instance.incomingHandler);
let port = await getRandomPort();
server.listen(port);

let server;
try {
server = new HTTPServer(instance.incomingHandler);
let port = await getRandomPort();
server.listen(port);
const resp = await fetch(`http://localhost:${port}`);
const text = await resp.text();
strictEqual(text, 'Hello World');
} finally {
server.stop();
if (server) {
server.stop();
}
}
}
12 changes: 7 additions & 5 deletions test/cases/http-server/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ export const enableFeatures = ['http'];
export const worldName = 'test3';

export async function test(instance) {
const server = new HTTPServer(instance.incomingHandler);
let port = await getRandomPort();
server.listen(port);

let server;
try {
server = new HTTPServer(instance.incomingHandler);
let port = await getRandomPort();
server.listen(port);
const resp = await fetch(`http://localhost:${port}`);
const text = await resp.text();
strictEqual(text, 'Hello world!');
} finally {
server.stop();
if (server) {
server.stop();
}
}
}
Loading
Loading