From b719a54b94ab4a85114fdbc087b5bcd058f3fef9 Mon Sep 17 00:00:00 2001 From: Jarrod Overson Date: Tue, 10 Oct 2023 14:28:50 -0400 Subject: [PATCH] added browser tests --- .eslintrc.cjs | 4 + .gitignore | 5 + README.md | 16 ++ e2e/harness.html | 8 + e2e/harness.js | 71 ++++++ e2e/wasmtime.spec.ts | 24 ++ package-lock.json | 537 ++++++++++++++++++++++++++++++++++++++++- package.json | 11 +- playwright.config.ts | 56 +++++ scripts/build-tests.sh | 20 ++ src/fd.ts | 28 ++- src/fs_core.ts | 10 +- src/fs_fd.ts | 52 +++- src/wasi.ts | 32 ++- src/wasi_defs.ts | 166 ++++++++++++- tsconfig.json | 5 +- 16 files changed, 998 insertions(+), 47 deletions(-) create mode 100644 e2e/harness.html create mode 100644 e2e/harness.js create mode 100644 e2e/wasmtime.spec.ts create mode 100644 playwright.config.ts create mode 100755 scripts/build-tests.sh diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 3d332f5..05990c8 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -2,6 +2,10 @@ module.exports = { extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"], parser: "@typescript-eslint/parser", plugins: ["@typescript-eslint"], + env: { + browser: true, + node: true, + }, rules: { "@typescript-eslint/no-this-alias": "off", }, diff --git a/.gitignore b/.gitignore index 149320b..c493683 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,8 @@ node_modules/ dist/ typings/ +/test-results/ +/playwright-report/ +/playwright/.cache/ + +wasmtime/ \ No newline at end of file diff --git a/README.md b/README.md index ea591f6..f04ba5b 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,22 @@ $ npm install $ npm run build ``` +## Running tests + +This project uses playwright to run tests in a browser using WebAssembly built from the Wasmtime project. + +To clone wasmtime and build the test WebAssembly, run: + +``` +$ npm test:build +``` + +Once the WebAssembly is built, you can run the tests with: + +``` +$ npm test +``` + ## Running the demo The demo requires the wasm rustc artifacts and the xterm js package. To get them run: diff --git a/e2e/harness.html b/e2e/harness.html new file mode 100644 index 0000000..0c8cc15 --- /dev/null +++ b/e2e/harness.html @@ -0,0 +1,8 @@ + + + Test Harness + +

Status: Not started

+

Check console for details

+ + diff --git a/e2e/harness.js b/e2e/harness.js new file mode 100644 index 0000000..c400bc2 --- /dev/null +++ b/e2e/harness.js @@ -0,0 +1,71 @@ +import { WASI, File, PreopenDirectory } from "../dist/index.js"; + +const stdio = { + stdin: undefined, + stdout: new File(), + stderr: new File(), +}; + +const WASI_ENVS = { + DEFAULT: [ + stdio.stdin, + stdio.stdout, + stdio.stderr, + new PreopenDirectory("/", {}), + ], +}; + +async function main(options) { + const wasmReq = await fetch(options.file); + + const args = [options.file]; + args.push(...(options.args || [])); + + const wasi = new WASI( + args, + (options.env || "").split(","), + WASI_ENVS[options.wasi_env] || WASI_ENVS.DEFAULT, + { + debug: true, + }, + ); + const module = await WebAssembly.compileStreaming(wasmReq); + + const instance = await WebAssembly.instantiate(module, { + wasi_snapshot_preview1: wasi.wasiImport, + }); + + const status = document.getElementById("status"); + try { + if (options.reactor) { + wasi.initialize(instance); + } + if (options.command) { + wasi.start(instance); + } + printBuffer(stdio, "stdout", "log"); + printBuffer(stdio, "stderr", "log"); + status.innerText = "success"; + } catch (e) { + printBuffer(stdio, "stdout", "log"); + printBuffer(stdio, "stderr", "error"); + status.innerText = "failure"; + throw e; + } +} + +function printBuffer(stdio, type, logger) { + const output = new TextDecoder().decode(stdio[type].data); + if (output.trim().length > 0) { + console[logger](`${type}`, output); + } +} + +main(JSON.parse(decodeURI(location.search.slice(1)))) + .then(() => { + console.log("done"); + }) + .catch((e) => { + console.error(e); + throw e; + }); diff --git a/e2e/wasmtime.spec.ts b/e2e/wasmtime.spec.ts new file mode 100644 index 0000000..4a9291f --- /dev/null +++ b/e2e/wasmtime.spec.ts @@ -0,0 +1,24 @@ +import { test, expect } from "@playwright/test"; + +const URL = "http://127.0.0.1:3000/e2e/harness.html"; +const WASMDIR = "../wasmtime/artifacts"; + +const tests = [ + { + file: `${WASMDIR}/preview1_path_open_read_write.wasm`, + command: true, + args: ["/"], + }, +]; + +for (const testDef of tests) { + test(`first ${testDef.file}`, async ({ page }) => { + await page.goto(`${URL}?${JSON.stringify(testDef)}`); + await page.waitForFunction( + () => document.getElementById("status")?.textContent !== "Not started", + ); + expect(await await page.getByTestId("status").textContent()).toBe( + "success", + ); + }); +} diff --git a/package-lock.json b/package-lock.json index 251b201..72be966 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,16 @@ "version": "0.2.15", "license": "MIT OR Apache-2.0", "devDependencies": { + "@playwright/test": "^1.38.1", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.37", + "@types/node": "^20.8.4", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", + "chokidar": "^3.5.3", "eslint": "^8.50.0", + "http-server": "^14.1.1", + "playwright": "^1.38.1", "prettier": "^3.0.3", "typescript": "^4.9.5" } @@ -170,6 +175,21 @@ "node": ">= 8" } }, + "node_modules/@playwright/test": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.38.1.tgz", + "integrity": "sha512-NqRp8XMwj3AK+zKLbZShl0r/9wKgzqI/527bkptKXomtuo+dOjU9NdMASQ8DNC9z9zLOMbG53T4eihYr3XR+BQ==", + "dev": true, + "dependencies": { + "playwright": "1.38.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -451,10 +471,13 @@ } }, "node_modules/@types/node": { - "version": "18.14.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.6.tgz", - "integrity": "sha512-93+VvleD3mXwlLI/xASjw0FzKcwzl3OdTCzm1LaRfqgS21gfFtK3zDXM5Op9TeeMsJVOaJ2VRDpT9q4Y3d0AvA==", - "dev": true + "version": "20.8.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.4.tgz", + "integrity": "sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A==", + "dev": true, + "dependencies": { + "undici-types": "~5.25.1" + } }, "node_modules/@types/responselike": { "version": "1.0.0", @@ -721,6 +744,19 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", @@ -756,12 +792,39 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "dev": true, + "dependencies": { + "lodash": "^4.17.14" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, "node_modules/bin-check": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bin-check/-/bin-check-4.1.0.tgz", @@ -926,6 +989,15 @@ "node": ">= 8" } }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -990,6 +1062,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1015,6 +1100,33 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/clone-response": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", @@ -1072,6 +1184,15 @@ "node": ">= 0.6" } }, + "node_modules/corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha512-utCYNzRSQIZNPIcGZdQc92UVJYAhtGAteCFg0yRaFm8f0P+CPtyGyHXJcGXnffjCybUCEx3FQ2G7U3/o9eIkVQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -1411,6 +1532,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, "node_modules/execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", @@ -1630,12 +1757,67 @@ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", "dev": true }, + "node_modules/follow-redirects": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", @@ -1743,6 +1925,15 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/has": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1752,12 +1943,98 @@ "node": ">=8" } }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", "dev": true }, + "node_modules/http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "dependencies": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/http-server": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-14.1.1.tgz", + "integrity": "sha512-+cbxadF40UXd9T01zUHgA+rlo2Bg1Srer4+B4NwIHdaGxAGGv59nYRnGGDJ9LBk7alpS0US+J+bLLdQOOkJq4A==", + "dev": true, + "dependencies": { + "basic-auth": "^2.0.1", + "chalk": "^4.1.2", + "corser": "^2.0.1", + "he": "^1.2.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy": "^1.18.1", + "mime": "^1.6.0", + "minimist": "^1.2.6", + "opener": "^1.5.1", + "portfinder": "^1.0.28", + "secure-compare": "3.0.1", + "union": "~0.5.0", + "url-join": "^4.0.1" + }, + "bin": { + "http-server": "bin/http-server" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/http2-wrapper": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", @@ -1780,6 +2057,18 @@ "node": ">=10.17.0" } }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -1850,6 +2139,18 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -1980,6 +2281,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2033,6 +2340,18 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -2072,6 +2391,27 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2084,6 +2424,15 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", @@ -2108,6 +2457,15 @@ "node": ">=4" } }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2132,6 +2490,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -2291,6 +2658,73 @@ "node": ">=0.10.0" } }, + "node_modules/playwright": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.38.1.tgz", + "integrity": "sha512-oRMSJmZrOu1FP5iu3UrCx8JEFRIMxLDM0c/3o4bpzU5Tz97BypefWf7TuTNPWeCe279TPal5RtPPZ+9lW/Qkow==", + "dev": true, + "dependencies": { + "playwright-core": "1.38.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz", + "integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/portfinder": { + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", + "integrity": "sha512-on2ZJVVDXRADWE6jnQaX0ioEylzgBpQk8r55NE4wjXW1ZxO+BgDlY6DXwj20i0V8eB4SenDQ00WEaxfiIQPcxg==", + "dev": true, + "dependencies": { + "async": "^2.6.4", + "debug": "^3.2.7", + "mkdirp": "^0.5.6" + }, + "engines": { + "node": ">= 0.12.0" + } + }, + "node_modules/portfinder/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -2340,6 +2774,21 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", + "dev": true, + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -2402,6 +2851,24 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true + }, "node_modules/resolve-alpn": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", @@ -2497,6 +2964,18 @@ } ] }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "node_modules/secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha512-AckIIV90rPDcBcglUwXPF3kg0P0qmPsPXAj6BBEENQE1p5yA1xfmDJzfi1Tappj37Pv2mVbKpL3Z1T+Nn7k1Qw==", + "dev": true + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -2584,6 +3063,20 @@ "node": ">=0.10.0" } }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -2820,6 +3313,24 @@ "node": ">=4.2.0" } }, + "node_modules/undici-types": { + "version": "5.25.3", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", + "dev": true + }, + "node_modules/union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "dependencies": { + "qs": "^6.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2829,12 +3340,30 @@ "punycode": "^2.1.0" } }, + "node_modules/url-join": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", + "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==", + "dev": true + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 3bbeaeb..f0c748a 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,12 @@ "type": "module", "scripts": { "build": "swc src -d dist && tsc --emitDeclarationOnly", + "watch": "swc src -w -d dist", "prepare": "swc src -d dist && tsc --emitDeclarationOnly", - "check": "tsc --noEmit && prettier src -c && eslint src/" + "check": "tsc --noEmit && prettier src -c && eslint src/", + "test": "playwright test", + "test:build": "./scripts/build-tests.sh", + "test:server": "http-server -p 3000" }, "repository": { "type": "git", @@ -26,11 +30,16 @@ "typings": "./typings/index.d.ts", "homepage": "https://github.com/bjorn3/browser_wasi_shim#readme", "devDependencies": { + "@playwright/test": "^1.38.1", "@swc/cli": "^0.1.62", "@swc/core": "^1.3.37", + "@types/node": "^20.8.4", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", + "chokidar": "^3.5.3", "eslint": "^8.50.0", + "http-server": "^14.1.1", + "playwright": "^1.38.1", "prettier": "^3.0.3", "typescript": "^4.9.5" } diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..868925b --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,56 @@ +import { defineConfig, devices } from "@playwright/test"; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: "./e2e", + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: "html", + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: "on-first-retry", + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, + ], + + webServer: { + command: "npm run test:server", + url: "http://127.0.0.1:3000", + // reuseExistingServer: !process.env.CI, + }, +}); diff --git a/scripts/build-tests.sh b/scripts/build-tests.sh new file mode 100755 index 0000000..d4f903f --- /dev/null +++ b/scripts/build-tests.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +WASMTIME_DIR=$SCRIPT_DIR/../wasmtime + +if ! [[ -d wasmtime ]]; then + git clone --recurse-submodules https://github.com/bytecodealliance/wasmtime.git --depth=1 $WASMTIME_DIR; +fi + +cargo build --manifest-path $WASMTIME_DIR/crates/test-programs/artifacts/Cargo.toml + +TARGET_DIR=$(ls -dt $WASMTIME_DIR/target/debug/build/test-programs-artifacts-* | head -n 1) + +if [[ -f $WASMTIME_DIR/artifacts ]]; then + $WASMTIME_DIR/artifacts +fi + +ln -s $TARGET_DIR/out/wasm32-wasi/debug $WASMTIME_DIR/artifacts + diff --git a/src/fd.ts b/src/fd.ts index 4a49b64..c297d8b 100644 --- a/src/fd.ts +++ b/src/fd.ts @@ -38,10 +38,13 @@ export class Fd { fd_pread(view8: Uint8Array, iovs: Array, offset: bigint) { return { ret: -1, nread: 0 }; } - fd_prestat_get() { + fd_prestat_get(): { + ret: number; + prestat: wasi.Prestat | null; + } { return { ret: -1, prestat: null }; } - fd_prestat_dir_name() { + fd_prestat_dir_name(): { ret: number; prestat_dir_name: Uint8Array | null } { return { ret: -1, prestat_dir_name: null }; } fd_pwrite(view8: Uint8Array, iovs: Array, offset: bigint) { @@ -74,7 +77,10 @@ export class Fd { path_create_directory(path): number { return -1; } - path_filestat_get(flags, path) { + path_filestat_get( + flags, + path, + ): { ret: number; filestat: wasi.Filestat | null } { return { ret: -1, filestat: null }; } path_filestat_set_times(flags, path, atim, mtim, fst_flags) { @@ -84,16 +90,16 @@ export class Fd { return -1; } path_open( - dirflags, - path, - oflags, - fs_rights_base, - fs_rights_inheriting, - fdflags, - ) { + dirflags: number, + path: string, + oflags: number, + fs_rights_base: bigint, + fs_rights_inheriting: bigint, + fd_flags: number, + ): { ret: number; fd_obj: Fd | null } { return { ret: -1, fd_obj: null }; } - path_readlink(path) { + path_readlink(path): { ret: -1; data: Uint8Array | null } { return { ret: -1, data: null }; } path_remove_directory(path): number { diff --git a/src/fs_core.ts b/src/fs_core.ts index 4532ee3..d71fded 100644 --- a/src/fs_core.ts +++ b/src/fs_core.ts @@ -19,8 +19,8 @@ export class File { this.readonly = !!options?.readonly; } - open(fd_flags: number) { - const file = new OpenFile(this); + open(fd_flags: number, fs_rights_base: bigint) { + const file = new OpenFile(this, fs_rights_base); if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END); return file; } @@ -66,8 +66,8 @@ export class SyncOPFSFile { this.readonly = !!options?.readonly; } - open(fd_flags: number) { - const file = new OpenSyncOPFSFile(this); + open(fd_flags: number, fs_rights_base: bigint) { + const file = new OpenSyncOPFSFile(this, fs_rights_base); if (fd_flags & wasi.FDFLAGS_APPEND) file.fd_seek(0n, wasi.WHENCE_END); return file; } @@ -115,7 +115,7 @@ export class Directory { if (entry.contents[component] != undefined) { entry = entry.contents[component]; } else { - debug.log(component); + debug.log(`component ${component} undefined`); return null; } } diff --git a/src/fs_fd.ts b/src/fs_fd.ts index 104958f..f13b6cc 100644 --- a/src/fs_fd.ts +++ b/src/fs_fd.ts @@ -6,20 +6,35 @@ import { debug } from "./debug.js"; export class OpenFile extends Fd { file: File; file_pos: bigint = 0n; + fs_rights_base: bigint = 0n; - constructor(file: File) { + constructor(file: File, fs_rights_base: bigint = 0n) { super(); this.file = file; + this.fs_rights_base = fs_rights_base; } fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat | null } { - return { ret: 0, fdstat: new wasi.Fdstat(wasi.FILETYPE_REGULAR_FILE, 0) }; + return { + ret: 0, + fdstat: new wasi.Fdstat( + wasi.FILETYPE_REGULAR_FILE, + 0, + this.fs_rights_base, + ), + }; } fd_read( view8: Uint8Array, iovs: Array, ): { ret: number; nread: number } { + if ( + (Number(this.fs_rights_base) & wasi.RIGHTS_FD_READ) !== + wasi.RIGHTS_FD_READ + ) { + return { ret: wasi.ERRNO_BADF, nread: 0 }; + } let nread = 0; for (const iovec of iovs) { if (this.file_pos < this.file.data.byteLength) { @@ -66,7 +81,11 @@ export class OpenFile extends Fd { iovs: Array, ): { ret: number; nwritten: number } { let nwritten = 0; - if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten }; + if ( + this.file.readonly || + (Number(this.fs_rights_base) & wasi.RIGHTS_FD_WRITE) === 0 + ) + return { ret: wasi.ERRNO_BADF, nwritten }; for (const iovec of iovs) { const buffer = view8.slice(iovec.buf, iovec.buf + iovec.buf_len); if (this.file_pos + BigInt(buffer.byteLength) > this.file.size) { @@ -94,10 +113,12 @@ export class OpenFile extends Fd { export class OpenSyncOPFSFile extends Fd { file: SyncOPFSFile; position: bigint = 0n; + fs_rights_base: bigint = 0n; - constructor(file: SyncOPFSFile) { + constructor(file: SyncOPFSFile, fs_rights_base: bigint = 0n) { super(); this.file = file; + this.fs_rights_base = fs_rights_base; } fd_fdstat_get(): { ret: number; fdstat: wasi.Fdstat | null } { @@ -118,6 +139,12 @@ export class OpenSyncOPFSFile extends Fd { view8: Uint8Array, iovs: Array, ): { ret: number; nread: number } { + if ( + (Number(this.fs_rights_base) & wasi.RIGHTS_FD_READ) !== + wasi.RIGHTS_FD_READ + ) { + return { ret: wasi.ERRNO_BADF, nread: 0 }; + } let nread = 0; for (const iovec of iovs) { if (this.position < this.file.handle.getSize()) { @@ -162,7 +189,11 @@ export class OpenSyncOPFSFile extends Fd { iovs: Array, ): { ret: number; nwritten: number } { let nwritten = 0; - if (this.file.readonly) return { ret: wasi.ERRNO_BADF, nwritten }; + if ( + this.file.readonly || + (Number(this.fs_rights_base) & wasi.RIGHTS_FD_WRITE) === 0 + ) + return { ret: wasi.ERRNO_BADF, nwritten }; for (const iovec of iovs) { const buf = new Uint8Array(view8.buffer, iovec.buf, iovec.buf_len); // don't need to extend file manually, just write @@ -279,7 +310,11 @@ export class OpenDirectory extends Fd { const ret = entry.truncate(); if (ret != wasi.ERRNO_SUCCESS) return { ret, fd_obj: null }; } - return { ret: wasi.ERRNO_SUCCESS, fd_obj: entry.open(fd_flags) }; + + return { + ret: wasi.ERRNO_SUCCESS, + fd_obj: entry.open(fd_flags, fs_rights_base), + }; } path_create_directory(path: string): number { @@ -292,6 +327,11 @@ export class OpenDirectory extends Fd { 0, ).ret; } + + path_unlink_file(path: string): number { + delete this.dir.contents[path]; + return wasi.ERRNO_SUCCESS; + } } export class PreopenDirectory extends OpenDirectory { diff --git a/src/wasi.ts b/src/wasi.ts index 12392bd..ab16a89 100644 --- a/src/wasi.ts +++ b/src/wasi.ts @@ -7,11 +7,11 @@ export interface Options { } export default class WASI { - args: Array = []; - env: Array = []; - fds: Array = []; + args: string[] = []; + env: string[] = []; + fds: (Fd | undefined)[] = []; inst: { exports: { memory: WebAssembly.Memory } }; - wasiImport: { [key: string]: (...args: Array) => unknown }; + wasiImport: wasi.WasiPreview1; /// Start a WASI command start(instance: { @@ -19,6 +19,7 @@ export default class WASI { exports: { memory: WebAssembly.Memory; _start: () => unknown }; }) { this.inst = instance; + debug.log("calling _start"); instance.exports._start(); } @@ -27,16 +28,22 @@ export default class WASI { exports: { memory: WebAssembly.Memory; _initialize: () => unknown }; }) { this.inst = instance; - instance.exports._initialize(); + if (instance.exports._initialize) { + debug.log("calling _initialize"); + instance.exports._initialize(); + } else { + debug.log("no _initialize found"); + } } constructor( - args: Array, - env: Array, - fds: Array, + args: string[] = [], + env: string[] = [], + fds: Fd[] = [], options: Options = {}, ) { debug.enable(options.debug); + debug.log("initializing wasi", { args, env, fds }); this.args = args; this.env = env; @@ -485,6 +492,8 @@ export default class WASI { buffer8.slice(path_ptr, path_ptr + path_len), ); return self.fds[fd].path_create_directory(path); + } else { + return wasi.ERRNO_BADF; } }, path_filestat_get( @@ -578,7 +587,7 @@ export default class WASI { const path = new TextDecoder("utf-8").decode( buffer8.slice(path_ptr, path_ptr + path_len), ); - debug.log(path); + debug.log({ path }); const { ret, fd_obj } = self.fds[fd].path_open( dirflags, path, @@ -591,8 +600,9 @@ export default class WASI { return ret; } // FIXME use first free fd - self.fds.push(fd_obj); + self.fds.push(fd_obj!); const opened_fd = self.fds.length - 1; + buffer.setUint32(opened_fd_ptr, opened_fd, true); return 0; } else { @@ -613,7 +623,7 @@ export default class WASI { const path = new TextDecoder("utf-8").decode( buffer8.slice(path_ptr, path_ptr + path_len), ); - debug.log(path); + debug.log({ path }); const { ret, data } = self.fds[fd].path_readlink(path); if (data != null) { if (data.length > buf_len) { diff --git a/src/wasi_defs.ts b/src/wasi_defs.ts index 91fa7f6..f2a6053 100644 --- a/src/wasi_defs.ts +++ b/src/wasi_defs.ts @@ -128,12 +128,8 @@ export class Iovec { return iovec; } - static read_bytes_array( - view: DataView, - ptr: number, - len: number, - ): Array { - const iovecs = []; + static read_bytes_array(view: DataView, ptr: number, len: number): Iovec[] { + const iovecs: Iovec[] = []; for (let i = 0; i < len; i++) { iovecs.push(Iovec.read_bytes(view, ptr + 8 * i)); } @@ -157,7 +153,7 @@ export class Ciovec { ptr: number, len: number, ): Array { - const iovecs = []; + const iovecs: Ciovec[] = []; for (let i = 0; i < len; i++) { iovecs.push(Ciovec.read_bytes(view, ptr + 8 * i)); } @@ -236,9 +232,10 @@ export class Fdstat { fs_rights_base: bigint = 0n; fs_rights_inherited: bigint = 0n; - constructor(filetype: number, flags: number) { + constructor(filetype: number, flags: number, fs_rights_base = 0n) { this.fs_filetype = filetype; this.fs_flags = flags; + this.fs_rights_base = fs_rights_base; } write_bytes(view: DataView, ptr: number) { @@ -364,3 +361,156 @@ export class Prestat { this.inner.write_bytes(view, ptr + 4); } } + +export interface WasiPreview1 { + args_sizes_get(argc: number, argv_buf_size: number): number; + args_get(argv: number, argv_buf: number): number; + + environ_sizes_get(environ_count: number, environ_size: number): number; + environ_get(environ: number, environ_buf: number): number; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + clock_res_get(id: number, res_ptr: number): number; + clock_time_get(id: number, precision: bigint, time: number): number; + + fd_advise(fd: number, offset: bigint, len: bigint, advice: number): number; + fd_allocate(fd: number, offset: bigint, len: bigint): number; + fd_close(fd: number): number; + fd_datasync(fd: number): number; + fd_fdstat_get(fd: number, fdstat_ptr: number): number; + fd_fdstat_set_flags(fd: number, flags: number): number; + fd_fdstat_set_rights( + fd: number, + fs_rights_base: bigint, + fs_rights_inheriting: bigint, + ): number; + fd_filestat_get(fd: number, filestat_ptr: number): number; + fd_filestat_set_size(fd: number, size: bigint): number; + fd_filestat_set_times( + fd: number, + atim: bigint, + mtim: bigint, + fst_flags: number, + ): number; + fd_pread( + fd: number, + iovs_ptr: number, + iovs_len: number, + offset: bigint, + nread_ptr: number, + ): number; + fd_prestat_get(fd: number, buf_ptr: number): number; + + fd_prestat_dir_name( + fd: number, + path_ptr: number, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + path_len: number, + ): number; + fd_pwrite( + fd: number, + iovs_ptr: number, + iovs_len: number, + offset: bigint, + nwritten_ptr: number, + ): number; + fd_read( + fd: number, + iovs_ptr: number, + iovs_len: number, + nread_ptr: number, + ): number; + fd_readdir( + fd: number, + buf: number, + buf_len: number, + cookie: bigint, + bufused_ptr: number, + ): number; + fd_renumber(fd: number, to: number); + fd_seek( + fd: number, + offset: bigint, + whence: number, + offset_out_ptr: number, + ): number; + fd_sync(fd: number): number; + fd_tell(fd: number, offset_ptr: number): number; + fd_write( + fd: number, + iovs_ptr: number, + iovs_len: number, + nwritten_ptr: number, + ): number; + path_create_directory(fd: number, path_ptr: number, path_len: number): number; + path_filestat_get( + fd: number, + flags: number, + path_ptr: number, + path_len: number, + filestat_ptr: number, + ): number; + path_filestat_set_times( + fd: number, + flags: number, + path_ptr: number, + path_len: number, + atim, + mtim, + fst_flags, + ); + path_link( + old_fd: number, + old_flags, + old_path_ptr: number, + old_path_len: number, + new_fd: number, + new_path_ptr: number, + new_path_len: number, + ): number; + path_open( + fd: number, + dirflags, + path_ptr: number, + path_len: number, + oflags, + fs_rights_base, + fs_rights_inheriting, + fd_flags, + opened_fd_ptr: number, + ): number; + path_readlink( + fd: number, + path_ptr: number, + path_len: number, + buf_ptr: number, + buf_len: number, + nread_ptr: number, + ): number; + path_remove_directory(fd: number, path_ptr: number, path_len: number): number; + path_rename( + fd: number, + old_path_ptr: number, + old_path_len: number, + new_fd: number, + new_path_ptr: number, + new_path_len: number, + ): number; + path_symlink( + old_path_ptr: number, + old_path_len: number, + fd: number, + new_path_ptr: number, + new_path_len: number, + ): number; + path_unlink_file(fd: number, path_ptr: number, path_len: number): number; + poll_oneoff(in_, out, nsubscriptions); + proc_exit(exit_code: number); + proc_raise(sig: number); + sched_yield(); + random_get(buf: number, buf_len: number); + sock_recv(fd: number, ri_data, ri_flags); + sock_send(fd: number, si_data, si_flags); + sock_shutdown(fd: number, how); + sock_accept(fd: number, flags); +} diff --git a/tsconfig.json b/tsconfig.json index 17ebcea..36488c6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,10 @@ /* Language and Environment */ "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + "lib": [ + "dom", + "ESNext" + ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */