-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
802ca6b
commit 50bc435
Showing
10 changed files
with
306 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../shared/adapter.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
<html> | ||
<script type="module"> | ||
import { WASI, OpenFile, File, Fd, Directory, PreopenDirectory, wasi } from "/dist/index.js"; | ||
class ConsoleStdout extends Fd { | ||
constructor(write) { | ||
super(); | ||
this.write = write; | ||
} | ||
|
||
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.write(buffer); | ||
nwritten += iovec.buf_len; | ||
} | ||
return { ret: 0, nwritten }; | ||
} | ||
} | ||
|
||
async function derivePreopens(dirs) { | ||
const rawPreopens = await window.bindingDerivePreopens(dirs) | ||
function transform(entry) { | ||
if (entry.kind === "dir") { | ||
const contents = {}; | ||
for (const [name, child] of Object.entries(entry.contents)) { | ||
contents[name] = transform(child); | ||
} | ||
return new Directory(contents); | ||
} else if (entry.kind === "file") { | ||
return new File(Uint8Array.from(entry.buffer)) | ||
} else { | ||
throw new Error("Unknown kind: ", entry.kind, entry); | ||
} | ||
} | ||
const preopens = [] | ||
for (const preopen of rawPreopens) { | ||
const { dir, contents } = preopen; | ||
const newContents = {}; | ||
for (const [name, child] of Object.entries(contents)) { | ||
newContents[name] = transform(child); | ||
} | ||
preopens.push(new PreopenDirectory(dir, newContents)); | ||
} | ||
return preopens; | ||
} | ||
|
||
window.runWASI = async (options) => { | ||
const testFile = options["test-file"]; | ||
const args = [testFile].concat(options.arg); | ||
const fds = [ | ||
new OpenFile(new File([])), | ||
// Uint8Array is not [Serializable](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description) | ||
// so we need to convert it to an array before passing it to Playwright. | ||
new ConsoleStdout(bytes => window.bindingWriteIO(Array.from(bytes), "stdout")), | ||
new ConsoleStdout(bytes => window.bindingWriteIO(Array.from(bytes), "stderr")), | ||
]; | ||
const preopens = await derivePreopens(options.dir) | ||
fds.push(...preopens); | ||
|
||
const wasi = new WASI(args, options.env, fds, { debug: false }); | ||
|
||
const moduleBytes = await fetch(testFile).then(r => r.arrayBuffer()); | ||
const module = await WebAssembly.compile(moduleBytes); | ||
const instance = await WebAssembly.instantiate(module, { | ||
wasi_snapshot_preview1: wasi.wasiImport | ||
}); | ||
|
||
const exitCode = wasi.start(instance); | ||
return exitCode; | ||
} | ||
</script> | ||
|
||
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
#!/usr/bin/env node | ||
|
||
import fs from 'fs/promises'; | ||
import path from 'path'; | ||
import { chromium } from "playwright" | ||
import { parseArgs } from "../shared/parseArgs.mjs" | ||
import { walkFs } from "../shared/walkFs.mjs" | ||
|
||
async function derivePreopens(dirs) { | ||
const preopens = []; | ||
for (let dir of dirs) { | ||
const contents = await walkFs(dir, (name, entry, out) => { | ||
if (entry.kind === "file") { | ||
// Convert buffer to array to make it serializable. | ||
entry.buffer = Array.from(entry.buffer); | ||
} | ||
return { ...out, [name]: entry }; | ||
}, {}); | ||
preopens.push({ dir, contents }); | ||
} | ||
return preopens; | ||
} | ||
|
||
/** | ||
* Configure routes for the browser harness. | ||
* | ||
* @param {import('playwright').BrowserContext} context | ||
* @param {string} harnessURL | ||
*/ | ||
async function configureRoutes(context, harnessURL) { | ||
|
||
// Serve the main test page. | ||
context.route(`${harnessURL}/run-test.html`, async route => { | ||
const dirname = new URL(".", import.meta.url).pathname; | ||
const body = await fs.readFile(path.join(dirname, "run-test.html"), "utf8"); | ||
route.fulfill({ | ||
status: 200, | ||
contentType: 'text/html', | ||
// Browsers reduce the precision of performance.now() if the page is not | ||
// isolated. To keep the precision for `clock_get_time` we need to set the | ||
// following headers. | ||
// See: https://developer.mozilla.org/en-US/docs/Web/API/Performance/now#security_requirements | ||
headers: { | ||
"Cross-Origin-Opener-Policy": "same-origin", | ||
"Cross-Origin-Embedder-Policy": "require-corp", | ||
}, | ||
body, | ||
}); | ||
}) | ||
|
||
// Serve wasi-testsuite files. | ||
// e.g. http://browser-wasi-shim.localhost/home/me/browser_wasi_shim/test/wasi-testsuite/xxx | ||
let projectDir = path.join(new URL("../../wasi-testsuite", import.meta.url).pathname); | ||
projectDir = path.resolve(projectDir); | ||
context.route(`${harnessURL}${projectDir}/**/*`, async route => { | ||
const pathname = new URL(route.request().url()).pathname; | ||
const relativePath = pathname.slice(pathname.indexOf(projectDir) + projectDir.length); | ||
const content = await fs.readFile(path.join(projectDir, relativePath)); | ||
route.fulfill({ | ||
status: 200, | ||
contentType: 'application/javascript', | ||
body: content, | ||
}); | ||
}); | ||
|
||
// Serve transpiled browser_wasi_shim files under ./dist. | ||
context.route(`${harnessURL}/dist/*.js`, async route => { | ||
const pathname = new URL(route.request().url()).pathname; | ||
const distRelativePath = pathname.slice(pathname.indexOf("/dist/")); | ||
const distDir = new URL("../../..", import.meta.url); | ||
const distPath = path.join(distDir.pathname, distRelativePath); | ||
const content = await fs.readFile(distPath); | ||
route.fulfill({ | ||
status: 200, | ||
contentType: 'application/javascript', | ||
body: content, | ||
}); | ||
}); | ||
} | ||
|
||
async function runWASIOnBrowser(options) { | ||
const browser = await chromium.launch(); | ||
const context = await browser.newContext(); | ||
const harnessURL = 'http://browser-wasi-shim.localhost' | ||
|
||
await configureRoutes(context, harnessURL); | ||
|
||
const page = await context.newPage(); | ||
// Expose stdout/stderr bindings to allow test driver to capture output. | ||
page.exposeBinding("bindingWriteIO", (_, buffer, destination) => { | ||
buffer = Buffer.from(buffer); | ||
switch (destination) { | ||
case "stdout": | ||
process.stdout.write(buffer); | ||
break; | ||
case "stderr": | ||
process.stderr.write(buffer); | ||
break; | ||
default: | ||
throw new Error(`Unknown destination ${destination}`); | ||
} | ||
}); | ||
// Expose a way to serialize preopened directories to the browser. | ||
page.exposeBinding("bindingDerivePreopens", async (_, dirs) => { | ||
return await derivePreopens(dirs); | ||
}); | ||
|
||
page.on('console', msg => console.log(msg.text())); | ||
page.on('pageerror', ({ message }) => { | ||
console.log('PAGE ERROR:', message) | ||
process.exit(1); // Unexpected error. | ||
}); | ||
|
||
await page.goto(`${harnessURL}/run-test.html`, { waitUntil: "load" }) | ||
const status = await page.evaluate(async (o) => await window.runWASI(o), options) | ||
await page.close(); | ||
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; | ||
} | ||
|
||
await runWASIOnBrowser(options); | ||
} | ||
|
||
await main(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
../shared/adapter.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/// Parse command line arguments given by `adapter.py` through | ||
/// `wasi-testsuite`'s test runner. | ||
export 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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import fs from 'fs/promises'; | ||
import path from 'path'; | ||
|
||
/** | ||
* Walks a directory recursively and returns the result of combining the found entries | ||
* using the given reducer function. | ||
* | ||
* @typedef {{ kind: "dir", contents: any } | { kind: "file", buffer: Buffer }} Entry | ||
* @param {string} dir | ||
* @param {(name: string, entry: Entry, out: any) => any} nextPartialResult | ||
* @param {any} initial | ||
*/ | ||
export async function walkFs(dir, nextPartialResult, initial) { | ||
let result = { ...initial } | ||
const srcContents = await fs.readdir(dir, { withFileTypes: true }); | ||
for (let entry of srcContents) { | ||
const entryPath = path.join(dir, entry.name); | ||
if (entry.isDirectory()) { | ||
const contents = await walkFs(entryPath, nextPartialResult, initial); | ||
result = nextPartialResult(entry.name, { kind: "dir", contents }, result); | ||
} else { | ||
const buffer = await fs.readFile(entryPath); | ||
result = nextPartialResult(entry.name, { kind: "file", buffer }, result); | ||
} | ||
} | ||
return result; | ||
} |
Oops, something went wrong.