Send stdout/stderr from WasmRunner to dev server without decoding as UTF-8 (#471)
* Enable WasmRunner to handle stdout as raw binary * catch other errors * move devDeps * use browser exception handling * comment out
This commit is contained in:
parent
59730f982f
commit
3cb3877d98
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,7 @@
|
||||||
|
.DS_Store
|
||||||
|
/.build
|
||||||
|
/Packages
|
||||||
|
xcuserdata/
|
||||||
|
DerivedData/
|
||||||
|
.swiftpm
|
||||||
|
.netrc
|
|
@ -0,0 +1,28 @@
|
||||||
|
// swift-tools-version: 5.9
|
||||||
|
|
||||||
|
import PackageDescription
|
||||||
|
|
||||||
|
let package = Package(
|
||||||
|
name: "DevServerTestApp",
|
||||||
|
products: [
|
||||||
|
.executable(name: "app", targets: ["app"])
|
||||||
|
],
|
||||||
|
dependencies: [
|
||||||
|
.package(path: "../../.."),
|
||||||
|
.package(url: "https://github.com/swiftwasm/JavaScriptKit", from: "0.19.2")
|
||||||
|
],
|
||||||
|
targets: [
|
||||||
|
.executableTarget(
|
||||||
|
name: "app",
|
||||||
|
dependencies: [
|
||||||
|
.product(name: "JavaScriptKit", package: "JavaScriptKit")
|
||||||
|
],
|
||||||
|
resources: [
|
||||||
|
.copy("style.css")
|
||||||
|
]
|
||||||
|
),
|
||||||
|
.testTarget(
|
||||||
|
name: "SimpleTests"
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
|
@ -0,0 +1,4 @@
|
||||||
|
This application serves as a working environment for experimenting with the behavior of "carton".
|
||||||
|
Since it is not used by automated tests, it can be easily modified.
|
||||||
|
If you want to include the behavior created here in automated tests,
|
||||||
|
please separate the target application for testing.
|
|
@ -0,0 +1,33 @@
|
||||||
|
#if os(WASI)
|
||||||
|
import WASILibc
|
||||||
|
typealias FILEPointer = OpaquePointer
|
||||||
|
#else
|
||||||
|
import Darwin
|
||||||
|
typealias FILEPointer = UnsafeMutablePointer<FILE>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
import JavaScriptKit
|
||||||
|
|
||||||
|
func fputs(_ string: String, file: FILEPointer) {
|
||||||
|
_ = string.withCString { (cstr) in
|
||||||
|
fputs(cstr, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fputs("hello stdout\n", file: stdout)
|
||||||
|
fputs("hello stderr\n", file: stderr)
|
||||||
|
|
||||||
|
//fatalError("hello fatalError")
|
||||||
|
|
||||||
|
let document = JSObject.global.document
|
||||||
|
|
||||||
|
let button = document.createElement("button")
|
||||||
|
_ = button.appendChild(
|
||||||
|
document.createTextNode("click to crash")
|
||||||
|
)
|
||||||
|
|
||||||
|
_ = button.addEventListener("click", JSClosure { (e) in
|
||||||
|
fatalError("crash")
|
||||||
|
})
|
||||||
|
|
||||||
|
_ = document.body.appendChild(button)
|
|
@ -0,0 +1,4 @@
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
final class BasicTests: XCTestCase {
|
||||||
|
func testAdd() {
|
||||||
|
XCTAssertEqual(1 + 1, 2)
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,22 +31,22 @@ const startWasiTask = async () => {
|
||||||
// JavaScriptKit module not available, running without JavaScriptKit runtime.
|
// JavaScriptKit module not available, running without JavaScriptKit runtime.
|
||||||
}
|
}
|
||||||
|
|
||||||
const wasmRunner = WasmRunner(false, runtimeConstructor);
|
const wasmRunner = WasmRunner({
|
||||||
|
onStdoutLine(line) {
|
||||||
|
console.log(line);
|
||||||
|
},
|
||||||
|
onStderrLine(line) {
|
||||||
|
console.error(line);
|
||||||
|
}
|
||||||
|
}, runtimeConstructor);
|
||||||
|
|
||||||
// Instantiate the WebAssembly file
|
// Instantiate the WebAssembly file
|
||||||
const wasmBytes = new Uint8Array(responseArrayBuffer).buffer;
|
const wasmBytes = new Uint8Array(responseArrayBuffer).buffer;
|
||||||
await wasmRunner.run(wasmBytes);
|
await wasmRunner.run(wasmBytes);
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleError(e: any) {
|
async function main(): Promise<void> {
|
||||||
console.error(e);
|
await startWasiTask();
|
||||||
if (e instanceof WebAssembly.RuntimeError) {
|
|
||||||
console.log(e.stack);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
main();
|
||||||
startWasiTask().catch(handleError);
|
|
||||||
} catch (e) {
|
|
||||||
handleError(e);
|
|
||||||
}
|
|
|
@ -15,17 +15,42 @@
|
||||||
import { WASI, File, OpenFile, ConsoleStdout, PreopenDirectory } from "@bjorn3/browser_wasi_shim";
|
import { WASI, File, OpenFile, ConsoleStdout, PreopenDirectory } from "@bjorn3/browser_wasi_shim";
|
||||||
import type { SwiftRuntime, SwiftRuntimeConstructor } from "./JavaScriptKit_JavaScriptKit.resources/Runtime";
|
import type { SwiftRuntime, SwiftRuntimeConstructor } from "./JavaScriptKit_JavaScriptKit.resources/Runtime";
|
||||||
|
|
||||||
|
export class LineDecoder {
|
||||||
|
constructor(onLine: (line: string) => void) {
|
||||||
|
this.decoder = new TextDecoder("utf-8", { fatal: false });
|
||||||
|
this.buffer = "";
|
||||||
|
this.onLine = onLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
private decoder: TextDecoder;
|
||||||
|
private buffer: string;
|
||||||
|
private onLine: (line: string) => void;
|
||||||
|
|
||||||
|
send(chunk: Uint8Array) {
|
||||||
|
this.buffer += this.decoder.decode(chunk, { stream: true });
|
||||||
|
|
||||||
|
const lines = this.buffer.split("\n");
|
||||||
|
for (let i = 0; i < lines.length - 1; i++) {
|
||||||
|
this.onLine(lines[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.buffer = lines[lines.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type Options = {
|
export type Options = {
|
||||||
args?: string[];
|
args?: string[];
|
||||||
onStdout?: (text: string) => void;
|
onStdout?: (chunk: Uint8Array) => void;
|
||||||
onStderr?: (text: string) => void;
|
onStdoutLine?: (line: string) => void;
|
||||||
|
onStderr?: (chunk: Uint8Array) => void;
|
||||||
|
onStderrLine?: (line: string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type WasmRunner = {
|
export type WasmRunner = {
|
||||||
run(wasmBytes: ArrayBufferLike, extraWasmImports?: WebAssembly.Imports): Promise<void>
|
run(wasmBytes: ArrayBufferLike, extraWasmImports?: WebAssembly.Imports): Promise<void>
|
||||||
};
|
};
|
||||||
|
|
||||||
export const WasmRunner = (rawOptions: Options | false, SwiftRuntime: SwiftRuntimeConstructor | undefined): WasmRunner => {
|
export const WasmRunner = (rawOptions: Options, SwiftRuntime: SwiftRuntimeConstructor | undefined): WasmRunner => {
|
||||||
const options: Options = defaultRunnerOptions(rawOptions);
|
const options: Options = defaultRunnerOptions(rawOptions);
|
||||||
|
|
||||||
let swift: SwiftRuntime;
|
let swift: SwiftRuntime;
|
||||||
|
@ -33,17 +58,29 @@ export const WasmRunner = (rawOptions: Options | false, SwiftRuntime: SwiftRunti
|
||||||
swift = new SwiftRuntime();
|
swift = new SwiftRuntime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let stdoutLine: LineDecoder | undefined = undefined;
|
||||||
|
if (options.onStdoutLine != null) {
|
||||||
|
stdoutLine = new LineDecoder(options.onStdoutLine);
|
||||||
|
}
|
||||||
|
const stdout = new ConsoleStdout((chunk) => {
|
||||||
|
options.onStdout?.call(undefined, chunk);
|
||||||
|
stdoutLine?.send(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
let stderrLine: LineDecoder | undefined = undefined;
|
||||||
|
if (options.onStderrLine != null) {
|
||||||
|
stderrLine = new LineDecoder(options.onStderrLine);
|
||||||
|
}
|
||||||
|
const stderr = new ConsoleStdout((chunk) => {
|
||||||
|
options.onStderr?.call(undefined, chunk);
|
||||||
|
stderrLine?.send(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
const args = options.args || [];
|
const args = options.args || [];
|
||||||
const fds = [
|
const fds = [
|
||||||
new OpenFile(new File([])), // stdin
|
new OpenFile(new File([])), // stdin
|
||||||
ConsoleStdout.lineBuffered((stdout) => {
|
stdout,
|
||||||
console.log(stdout);
|
stderr,
|
||||||
options.onStdout?.call(undefined, stdout);
|
|
||||||
}),
|
|
||||||
ConsoleStdout.lineBuffered((stderr) => {
|
|
||||||
console.error(stderr);
|
|
||||||
options.onStderr?.call(undefined, stderr);
|
|
||||||
}),
|
|
||||||
new PreopenDirectory("/", new Map()),
|
new PreopenDirectory("/", new Map()),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -129,15 +166,8 @@ export const WasmRunner = (rawOptions: Options | false, SwiftRuntime: SwiftRunti
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultRunnerOptions = (options: Options | false): Options => {
|
const defaultRunnerOptions = (options: Options): Options => {
|
||||||
if (!options) return defaultRunnerOptions({});
|
if (options.args != null) {
|
||||||
if (!options.onStdout) {
|
|
||||||
options.onStdout = () => { };
|
|
||||||
}
|
|
||||||
if (!options.onStderr) {
|
|
||||||
options.onStderr = () => { };
|
|
||||||
}
|
|
||||||
if (!options.args) {
|
|
||||||
options.args = ["main.wasm"];
|
options.args = ["main.wasm"];
|
||||||
}
|
}
|
||||||
return options;
|
return options;
|
||||||
|
|
|
@ -44,17 +44,12 @@ const startWasiTask = async () => {
|
||||||
|
|
||||||
const wasmRunner = WasmRunner(
|
const wasmRunner = WasmRunner(
|
||||||
{
|
{
|
||||||
onStderr() {
|
onStdoutLine(line) {
|
||||||
const prevLimit = Error.stackTraceLimit;
|
console.log(line);
|
||||||
Error.stackTraceLimit = 1000;
|
|
||||||
socket.send(
|
|
||||||
JSON.stringify({
|
|
||||||
kind: "stackTrace",
|
|
||||||
stackTrace: new Error().stack,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
Error.stackTraceLimit = prevLimit;
|
|
||||||
},
|
},
|
||||||
|
onStderrLine(line) {
|
||||||
|
console.error(line);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
runtimeConstructor
|
runtimeConstructor
|
||||||
);
|
);
|
||||||
|
@ -65,14 +60,32 @@ const startWasiTask = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
function handleError(e: any) {
|
function handleError(e: any) {
|
||||||
console.error(e);
|
if (e instanceof Error) {
|
||||||
if (e instanceof WebAssembly.RuntimeError) {
|
const stack = e.stack;
|
||||||
console.log(e.stack);
|
if (stack != null) {
|
||||||
|
socket.send(
|
||||||
|
JSON.stringify({
|
||||||
|
kind: "stackTrace",
|
||||||
|
stackTrace: stack,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
async function main(): Promise<void> {
|
||||||
startWasiTask().catch(handleError);
|
try {
|
||||||
} catch (e) {
|
window.addEventListener("error", (event) => {
|
||||||
handleError(e);
|
handleError(event.error);
|
||||||
|
});
|
||||||
|
window.addEventListener("unhandledrejection", (event) => {
|
||||||
|
handleError(event.reason);
|
||||||
|
});
|
||||||
|
await startWasiTask();
|
||||||
|
} catch (e) {
|
||||||
|
handleError(e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main();
|
|
@ -45,16 +45,12 @@ const startWasiTask = async () => {
|
||||||
let testRunOutput = "";
|
let testRunOutput = "";
|
||||||
const wasmRunner = WasmRunner(
|
const wasmRunner = WasmRunner(
|
||||||
{
|
{
|
||||||
onStdout: (text) => {
|
onStdoutLine: (line) => {
|
||||||
testRunOutput += text + "\n";
|
console.log(line);
|
||||||
|
testRunOutput += line + "\n";
|
||||||
},
|
},
|
||||||
onStderr: () => {
|
onStderrLine: (line) => {
|
||||||
socket.send(
|
console.error(line);
|
||||||
JSON.stringify({
|
|
||||||
kind: "stackTrace",
|
|
||||||
stackTrace: new Error().stack,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
runtimeConstructor
|
runtimeConstructor
|
||||||
|
@ -109,14 +105,33 @@ const startWasiTask = async () => {
|
||||||
|
|
||||||
function handleError(e: any) {
|
function handleError(e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
if (e instanceof WebAssembly.RuntimeError) {
|
|
||||||
console.log(e.stack);
|
if (e instanceof Error) {
|
||||||
|
const stack = e.stack;
|
||||||
|
if (stack != null) {
|
||||||
|
socket.send(
|
||||||
|
JSON.stringify({
|
||||||
|
kind: "stackTrace",
|
||||||
|
stackTrace: stack,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
socket.send(JSON.stringify({ kind: "errorReport", errorReport: e.toString() }));
|
|
||||||
|
socket.send(
|
||||||
|
JSON.stringify({
|
||||||
|
kind: "errorReport",
|
||||||
|
errorReport: e.toString()
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
async function main(): Promise<void> {
|
||||||
startWasiTask().catch(handleError);
|
try {
|
||||||
} catch (e) {
|
await startWasiTask();
|
||||||
handleError(e);
|
} catch (e) {
|
||||||
|
handleError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main();
|
|
@ -11,6 +11,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@bjorn3/browser_wasi_shim": "^0.3.0",
|
"@bjorn3/browser_wasi_shim": "^0.3.0",
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.7",
|
||||||
|
"@types/reconnectingwebsocket": "^1.0.10",
|
||||||
"esbuild": "^0.14.38",
|
"esbuild": "^0.14.38",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
|
@ -32,6 +33,12 @@
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/reconnectingwebsocket": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/reconnectingwebsocket/-/reconnectingwebsocket-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-30Pq4D3o8BKcdY53dzr0elGFyB/ChYpGrHiRH/GuaZKXXGWq/CsD1QBEu1b8IgdHReOKpo9tjk80UaxSbuXoTQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/ansi-styles": {
|
"node_modules/ansi-styles": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||||
|
@ -1318,6 +1325,12 @@
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/reconnectingwebsocket": {
|
||||||
|
"version": "1.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/reconnectingwebsocket/-/reconnectingwebsocket-1.0.10.tgz",
|
||||||
|
"integrity": "sha512-30Pq4D3o8BKcdY53dzr0elGFyB/ChYpGrHiRH/GuaZKXXGWq/CsD1QBEu1b8IgdHReOKpo9tjk80UaxSbuXoTQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"ansi-styles": {
|
"ansi-styles": {
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||||
|
|
|
@ -23,9 +23,10 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@bjorn3/browser_wasi_shim": "^0.3.0",
|
"@bjorn3/browser_wasi_shim": "^0.3.0",
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.7",
|
||||||
|
"@types/reconnectingwebsocket": "^1.0.10",
|
||||||
"esbuild": "^0.14.38",
|
"esbuild": "^0.14.38",
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"reconnecting-websocket": "^4.4.0",
|
"reconnecting-websocket": "^4.4.0",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue