From 6dbd96e481ffcb4fcb39448987c8a1d33653e35b Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 5 Dec 2023 13:49:38 +0000 Subject: [PATCH 1/4] test: Setup wasi-testsuite with Node.js adapter --- .github/workflows/check.yml | 2 + .gitmodules | 4 ++ test/adapter.py | 13 ++++ test/run-testsuite.sh | 14 ++++ test/run-wasi.mjs | 133 ++++++++++++++++++++++++++++++++++++ test/skip.json | 40 +++++++++++ test/wasi-testsuite | 1 + 7 files changed, 207 insertions(+) create mode 100644 test/adapter.py create mode 100755 test/run-testsuite.sh create mode 100644 test/run-wasi.mjs create mode 100644 test/skip.json create mode 160000 test/wasi-testsuite diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 3b1bd71..1ec796e 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -13,3 +13,5 @@ jobs: registry-url: "https://registry.npmjs.org" - run: npm ci - run: npm run check + - run: python3 -m pip install -r ./test/wasi-testsuite/test-runner/requirements.txt + - run: ./test/run-testsuite.sh diff --git a/.gitmodules b/.gitmodules index faf6aa1..c9f05a1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,7 @@ [submodule "examples/wasm-rustc"] path = examples/wasm-rustc url = git@github.com:bjorn3/wasm-rustc.git +[submodule "test/wasi-testsuite"] + path = test/wasi-testsuite + url = https://github.com/WebAssembly/wasi-testsuite + branch = prod/testsuite-base diff --git a/test/adapter.py b/test/adapter.py new file mode 100644 index 0000000..b89ec9b --- /dev/null +++ b/test/adapter.py @@ -0,0 +1,13 @@ +import subprocess +import pathlib +import sys +import os + +run_wasi_mjs = pathlib.Path(__file__).parent / "run-wasi.mjs" +args = sys.argv[1:] +cmd = ["node", str(run_wasi_mjs)] + args +if os.environ.get("VERBOSE_ADAPTER") is not None: + print(" ".join(map(lambda x: f"'{x}'", cmd))) + +result = subprocess.run(cmd, check=False) +sys.exit(result.returncode) diff --git a/test/run-testsuite.sh b/test/run-testsuite.sh new file mode 100755 index 0000000..253ad11 --- /dev/null +++ b/test/run-testsuite.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +set -euo pipefail + +TEST_DIR="$(cd "$(dirname $0)" && pwd)" +TESTSUITE_ROOT="$TEST_DIR/wasi-testsuite" + +python3 "$TESTSUITE_ROOT/test-runner/wasi_test_runner.py" \ + --test-suite "$TESTSUITE_ROOT/tests/assemblyscript/testsuite/" \ + "$TESTSUITE_ROOT/tests/c/testsuite/" \ + "$TESTSUITE_ROOT/tests/rust/testsuite/" \ + --runtime-adapter "$TEST_DIR/adapter.py" \ + --exclude-filter "$TEST_DIR/skip.json" \ + $@ diff --git a/test/run-wasi.mjs b/test/run-wasi.mjs new file mode 100644 index 0000000..f1f7c6e --- /dev/null +++ b/test/run-wasi.mjs @@ -0,0 +1,133 @@ +#!/usr/bin/env node + +import fs from 'fs/promises'; +import path from 'path'; +import { WASI, wasi, strace, OpenFile, File, Directory, PreopenDirectory, Fd } from "../dist/index.js" + +function parseArgs() { + const args = process.argv.slice(2); + const options = { + "version": false, + "test-file": null, + "arg": [], + "env": [], + "dir": [], + }; + while (args.length > 0) { + const arg = args.shift(); + if (arg.startsWith("--")) { + let [name, value] = arg.split("="); + name = name.slice(2); + if (Object.prototype.hasOwnProperty.call(options, name)) { + if (value === undefined) { + value = args.shift() || true; + } + if (Array.isArray(options[name])) { + options[name].push(value); + } else { + options[name] = value; + } + } + } + } + + return options; +} + +class NodeStdout extends Fd { + constructor(out) { + super(); + this.out = out; + } + + fd_filestat_get() { + const filestat = new wasi.Filestat( + wasi.FILETYPE_CHARACTER_DEVICE, + BigInt(0), + ); + return { ret: 0, filestat }; + } + + fd_fdstat_get() { + const fdstat = new wasi.Fdstat(wasi.FILETYPE_CHARACTER_DEVICE, 0); + fdstat.fs_rights_base = BigInt(wasi.RIGHTS_FD_WRITE); + return { ret: 0, fdstat }; + } + + fd_write(view8, iovs) { + let nwritten = 0; + for (let iovec of iovs) { + let buffer = view8.slice(iovec.buf, iovec.buf + iovec.buf_len); + this.out.write(buffer); + nwritten += iovec.buf_len; + } + return { ret: 0, nwritten }; + } +} + +async function cloneToMemfs(dir) { + const destContents = {}; + const srcContents = await fs.readdir(dir, { withFileTypes: true }); + for (let entry of srcContents) { + const entryPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + destContents[entry.name] = new Directory(await cloneToMemfs(entryPath)); + } else { + const buffer = await fs.readFile(entryPath); + const file = new File(buffer); + destContents[entry.name] = file; + } + } + return destContents; +} + +async function derivePreopens(dirs) { + const preopens = []; + for (let dir of dirs) { + const contents = await cloneToMemfs(dir); + const preopen = new PreopenDirectory(dir, contents); + preopens.push(preopen); + } + return preopens; +} + +async function runWASI(options) { + const testFile = options["test-file"] + if (!testFile) { + throw new Error("Missing --test-file"); + } + + // arg0 is the given test file + const args = [testFile].concat(options.arg) + const fds = [ + new OpenFile(new File([])), + new NodeStdout(process.stdout), + new NodeStdout(process.stderr), + ]; + const preopens = await derivePreopens(options.dir); + fds.push(...preopens); + const wasi = new WASI(args, options.env, fds, { debug: false }) + + let wasiImport = wasi.wasiImport; + if (process.env["STRACE"]) { + wasiImport = strace(wasiImport, []); + } + const importObject = { wasi_snapshot_preview1: wasiImport } + + const wasm = await WebAssembly.compile(await fs.readFile(testFile)); + const instance = await WebAssembly.instantiate(wasm, importObject); + const status = wasi.start(instance); + process.exit(status); +} + +async function main() { + const options = parseArgs(); + if (options.version) { + const pkg = JSON.parse(await fs.readFile(new URL("../package.json", import.meta.url))); + console.log(`${pkg.name} v${pkg.version}`); + return; + } + runWASI(options); +} + +await main(); diff --git a/test/skip.json b/test/skip.json new file mode 100644 index 0000000..3264d07 --- /dev/null +++ b/test/skip.json @@ -0,0 +1,40 @@ +{ + "WASI Assemblyscript tests": { + }, + "WASI C tests": { + "sock_shutdown-invalid_fd": "not implemented yet", + "stat-dev-ino": "fail", + "sock_shutdown-not_sock": "fail", + "fdopendir-with-access": "fail" + }, + "WASI Rust tests": { + "sched_yield": "not implemented yet", + "path_rename": "fail", + "fd_advise": "fail", + "path_exists": "fail", + "path_open_dirfd_not_dir": "fail", + "fd_filestat_set": "fail", + "symlink_create": "fail", + "overwrite_preopen": "fail", + "path_open_read_write": "fail", + "path_rename_dir_trailing_slashes": "fail", + "fd_flags_set": "fail", + "path_filestat": "fail", + "path_link": "fail", + "fd_fdstat_set_rights": "fail", + "readlink": "fail", + "unlink_file_trailing_slashes": "fail", + "path_symlink_trailing_slashes": "fail", + "poll_oneoff_stdio": "fail", + "dangling_symlink": "fail", + "dir_fd_op_failures": "fail", + "file_allocate": "fail", + "nofollow_errors": "fail", + "path_open_preopen": "fail", + "fd_readdir": "fail", + "directory_seek": "fail", + "symlink_filestat": "fail", + "symlink_loop": "fail", + "interesting_paths": "fail" + } +} diff --git a/test/wasi-testsuite b/test/wasi-testsuite new file mode 160000 index 0000000..616f50d --- /dev/null +++ b/test/wasi-testsuite @@ -0,0 +1 @@ +Subproject commit 616f50d785984057215f60df32c8d29465bb6866 From db75ca44aca76bf39b4b4dc85a6c45a37ff2a991 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 5 Dec 2023 16:39:19 +0000 Subject: [PATCH 2/4] test: Add `npm test` to run testsuite --- .github/workflows/check.yml | 2 +- README.md | 7 +++++++ package.json | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 1ec796e..f9a595a 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -14,4 +14,4 @@ jobs: - run: npm ci - run: npm run check - run: python3 -m pip install -r ./test/wasi-testsuite/test-runner/requirements.txt - - run: ./test/run-testsuite.sh + - run: npm test diff --git a/README.md b/README.md index ea591f6..95ad586 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,13 @@ $ npx http-server And visit [http://127.0.0.1:8080/examples/rustc.html]() in your browser. +## Testing + +``` +$ python3 -m pip install -r ./test/wasi-testsuite/test-runner/requirements.txt +$ npm test +``` + ## License Licensed under either of diff --git a/package.json b/package.json index fde6d3f..b75410e 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "scripts": { "build": "swc src -d dist && tsc --emitDeclarationOnly", "prepare": "swc src -d dist && tsc --emitDeclarationOnly", + "test": "./test/run-testsuite.sh", "check": "tsc --noEmit && prettier src -c && eslint src/" }, "repository": { From 9aad050979a8acdad6b694df2c506121a24af538 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Tue, 5 Dec 2023 17:04:03 +0000 Subject: [PATCH 3/4] ci: fetch submodules to clone wasi-testsuite --- .github/workflows/check.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index f9a595a..d7742ec 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -7,6 +7,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + submodules: true - uses: actions/setup-node@v3 with: node-version: "16.x" From 53582ae5067cc2d6ab7c8f4fd39b78872aa7d33c Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 14 Dec 2023 05:51:40 +0000 Subject: [PATCH 4/4] ci: checkout only wasi-testsuite submodule `examples/wasm-rustc` is not used while running the tests. --- .github/workflows/check.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index d7742ec..e3c1f39 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -7,8 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - with: - submodules: true + - run: git submodule update --init test/wasi-testsuite - uses: actions/setup-node@v3 with: node-version: "16.x"