Skip to content

Commit 650eb3e

Browse files
Merge pull request #441 from ruby/katei/component-test
Enable Component-based tests
2 parents 479a1d7 + e3d8ace commit 650eb3e

File tree

13 files changed

+128
-36
lines changed

13 files changed

+128
-36
lines changed

lib/ruby_wasm/packager.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,20 +29,22 @@ def package(executor, dest_dir, options)
2929

3030
fs = RubyWasm::Packager::FileSystem.new(dest_dir, self)
3131
fs.package_ruby_root(tarball, executor)
32-
wasm_bytes = ruby_core.build_and_link_exts(executor)
32+
33+
wasm_bytes = File.binread(File.join(fs.ruby_root, "bin", "ruby"))
3334

3435
fs.package_gems
3536
fs.remove_non_runtime_files(executor)
3637
fs.remove_stdlib(executor) unless options[:stdlib]
3738

38-
if full_build_options[:target] == "wasm32-unknown-wasip1" && !features.support_component_model?
39+
if full_build_options[:target] == "wasm32-unknown-wasip1"
3940
# wasi-vfs supports only WASI target
4041
wasi_vfs = RubyWasmExt::WasiVfs.new
4142
wasi_vfs.map_dir("/bundle", fs.bundle_dir)
4243
wasi_vfs.map_dir("/usr", File.dirname(fs.ruby_root))
4344

4445
wasm_bytes = wasi_vfs.pack(wasm_bytes)
4546
end
47+
wasm_bytes = ruby_core.build_and_link_exts(executor, wasm_bytes)
4648

4749
wasm_bytes = RubyWasmExt.preinitialize(wasm_bytes) if options[:optimize]
4850
wasm_bytes

lib/ruby_wasm/packager/core.rb

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def build(executor, options)
3737
raise NotImplementedError
3838
end
3939

40-
def build_and_link_exts(executor)
40+
def build_and_link_exts(executor, module_bytes)
4141
raise NotImplementedError
4242
end
4343

@@ -93,7 +93,7 @@ def build(executor, options)
9393
build.crossruby.artifact
9494
end
9595

96-
def build_and_link_exts(executor)
96+
def build_and_link_exts(executor, module_bytes)
9797
build = derive_build
9898
self.build_exts(executor, build)
9999
self.link_exts(executor, build)
@@ -276,7 +276,6 @@ def derive_build
276276
return @build if @build
277277
__skip__ = build ||= RubyWasm::Build.new(
278278
name, **@packager.full_build_options, target: target,
279-
wasi_vfs: @packager.features.support_component_model? ? nil : :default
280279
)
281280
build.crossruby.user_exts = user_exts(build)
282281
# Emscripten uses --global-base=1024 by default, but it conflicts with
@@ -302,10 +301,7 @@ def derive_build
302301
build
303302
end
304303

305-
def build_and_link_exts(executor)
306-
build = derive_build
307-
ruby_root = build.crossruby.dest_dir
308-
module_bytes = File.binread(File.join(ruby_root, "usr", "local", "bin", "ruby"))
304+
def build_and_link_exts(executor, module_bytes)
309305
return module_bytes unless @packager.features.support_component_model?
310306

311307
linker = RubyWasmExt::ComponentEncode.new

packages/gems/js/ext/js/witapi-core.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,11 @@ static inline void __wasm_call_ctors_if_needed(void) {
362362
if (!__wasm_call_ctors_done) {
363363
__wasm_call_ctors_done = true;
364364
__wasm_call_ctors();
365+
366+
__attribute__((weak)) extern void __wasi_vfs_rt_init(void);
367+
if (__wasi_vfs_rt_init) {
368+
__wasi_vfs_rt_init();
369+
}
365370
}
366371
}
367372

packages/npm-packages/ruby-head-wasm-wasi/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
"README.md"
3030
],
3131
"scripts": {
32-
"test": "RUBY_NPM_PACKAGE_ROOT=../ruby-head-wasm-wasi npm -C ../ruby-wasm-wasi run test:run",
32+
"test": "RUBY_NPM_PACKAGE_ROOT=../ruby-head-wasm-wasi npm -C ../ruby-wasm-wasi run test:run:all",
3333
"build:deps": "cd ../ruby-wasm-wasi && npm run build",
3434
"build:static:files": "../ruby-wasm-wasi/tools/pack-static-files.sh ./dist",
3535
"build:static:compat": "../ruby-wasm-wasi/tools/pack-compat-shim.mjs --dist=./dist --pkg=ruby-head-wasm-wasi",

packages/npm-packages/ruby-wasm-wasi/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@
3939
],
4040
"license": "MIT",
4141
"scripts": {
42-
"test:run": "npm run test:unit && npm run test:vitest && npm run test:e2e",
43-
"test:vitest": "NODE_OPTIONS=\"--experimental-wasi-unstable-preview1\" vitest run --pool=forks --testTimeout 300000 ./test/",
42+
"test:run:all": "npm run test:run && ENABLE_COMPONENT_TESTS=1 npm run test:run",
43+
"test:run": "npm run test:unit && npm run test:vitest -- --run && npm run test:e2e",
44+
"test:vitest": "vitest ./test/",
4445
"test:unit": "./tools/run-test-unit.mjs",
4546
"test:e2e": "playwright install && npm run test:e2e:examples && npm run test:e2e:integrations",
4647
"test:e2e:examples": "playwright test -c test-e2e/playwright.examples.config.ts",

packages/npm-packages/ruby-wasm-wasi/src/node.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ export const DefaultRubyVM = async (
55
rubyModule: WebAssembly.Module,
66
options: { env?: Record<string, string> | undefined } = {},
77
) => {
8-
const wasi = new WASI({ env: options.env, version: "preview1" });
8+
const wasi = new WASI({ env: options.env, version: "preview1", returnOnExit: true });
99
const vm = new RubyVM();
1010
const imports = {
1111
wasi_snapshot_preview1: wasi.wasiImport,

packages/npm-packages/ruby-wasm-wasi/test/gc.test.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@ describe("GC integration", () => {
2828

2929
test("protect exported Ruby objects", async () => {
3030
function dropRbValue(value) {
31-
value.inner.drop();
31+
if (value.inner.drop) {
32+
value.inner.drop();
33+
} else if (global.gc) {
34+
global.gc();
35+
} else {
36+
console.warn("--expose-gc is not enabled. Skip GC test.")
37+
}
3238
}
3339
const vm = await initRubyVM();
3440
const initialGCCount = Number(vm.eval("GC.count").toString());

packages/npm-packages/ruby-wasm-wasi/test/init.js

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
const fs = require("fs/promises");
2-
const path = require("path");
3-
const { WASI } = require("wasi");
4-
const { RubyVM } = require("../dist/cjs/index");
1+
import * as fs from "fs/promises";
2+
import * as path from "path";
3+
import { WASI } from "wasi";
4+
import { RubyVM } from "../src/index";
5+
import * as preview2Shim from "@bytecodealliance/preview2-shim"
56

67
const rubyModule = (async () => {
78
let binaryPath;
@@ -19,7 +20,7 @@ const rubyModule = (async () => {
1920
return await WebAssembly.compile(binary.buffer);
2021
})();
2122

22-
const initRubyVM = async ({ suppressStderr } = { suppressStderr: false }) => {
23+
const initModuleRubyVM = async ({ suppressStderr } = { suppressStderr: false }) => {
2324
let preopens = {};
2425
if (process.env.RUBY_ROOT) {
2526
preopens["/usr"] = path.join(process.env.RUBY_ROOT, "./usr");
@@ -30,6 +31,8 @@ const initRubyVM = async ({ suppressStderr } = { suppressStderr: false }) => {
3031
stderrFd = devNullFd.fd;
3132
}
3233
const wasi = new WASI({
34+
version: "preview1",
35+
returnOnExit: true,
3336
args: ["ruby.wasm"].concat(process.argv.slice(2)),
3437
stderr: stderrFd,
3538
preopens,
@@ -51,6 +54,63 @@ const initRubyVM = async ({ suppressStderr } = { suppressStderr: false }) => {
5154
return vm;
5255
};
5356

57+
const moduleCache = new Map();
58+
async function initComponentRubyVM({ suppressStderr } = { suppressStderr: false }) {
59+
const pkgPath = process.env.RUBY_NPM_PACKAGE_ROOT
60+
if (!pkgPath) {
61+
throw new Error("RUBY_NPM_PACKAGE_ROOT must be set");
62+
}
63+
const componentJsPath = path.resolve(pkgPath, "dist/component/ruby.component.js");
64+
const { instantiate } = await import(componentJsPath);
65+
const getCoreModule = async (relativePath) => {
66+
const coreModulePath = path.resolve(pkgPath, "dist/component", relativePath);
67+
if (moduleCache.has(coreModulePath)) {
68+
return moduleCache.get(coreModulePath);
69+
}
70+
const buffer = await fs.readFile(coreModulePath);
71+
const module = WebAssembly.compile(buffer);
72+
moduleCache.set(coreModulePath, module);
73+
return module;
74+
}
75+
const vm = await RubyVM._instantiate(async (jsRuntime) => {
76+
const { cli, clocks, filesystem, io, random, sockets } = preview2Shim;
77+
filesystem._setPreopens({})
78+
cli._setArgs(["ruby.wasm"].concat(process.argv.slice(2)));
79+
cli._setCwd("/")
80+
const root = await instantiate(getCoreModule, {
81+
"ruby:js/js-runtime": jsRuntime,
82+
"wasi:cli/environment": cli.environment,
83+
"wasi:cli/exit": cli.exit,
84+
"wasi:cli/stderr": cli.stderr,
85+
"wasi:cli/stdin": cli.stdin,
86+
"wasi:cli/stdout": cli.stdout,
87+
"wasi:cli/terminal-input": cli.terminalInput,
88+
"wasi:cli/terminal-output": cli.terminalOutput,
89+
"wasi:cli/terminal-stderr": cli.terminalStderr,
90+
"wasi:cli/terminal-stdin": cli.terminalStdin,
91+
"wasi:cli/terminal-stdout": cli.terminalStdout,
92+
"wasi:clocks/monotonic-clock": clocks.monotonicClock,
93+
"wasi:clocks/wall-clock": clocks.wallClock,
94+
"wasi:filesystem/preopens": filesystem.preopens,
95+
"wasi:filesystem/types": filesystem.types,
96+
"wasi:io/error": io.error,
97+
"wasi:io/poll": io.poll,
98+
"wasi:io/streams": io.streams,
99+
"wasi:random/random": random.random,
100+
"wasi:sockets/tcp": sockets.tcp,
101+
})
102+
return root.rubyRuntime;
103+
}, {})
104+
return vm;
105+
}
106+
107+
const initRubyVM = async ({ suppressStderr } = { suppressStderr: false }) => {
108+
if (process.env.ENABLE_COMPONENT_TESTS && process.env.ENABLE_COMPONENT_TESTS !== 'false') {
109+
return initComponentRubyVM({ suppressStderr });
110+
}
111+
return initModuleRubyVM({ suppressStderr });
112+
}
113+
54114
class RubyVersion {
55115
constructor(version) {
56116
this.version = version;

packages/npm-packages/ruby-wasm-wasi/test/package.test.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
import * as path from "path";
22
import * as fs from "fs/promises";
33
import { WASI } from "wasi";
4-
import { RubyVM } from "../dist/esm/index";
5-
import { DefaultRubyVM } from "../dist/esm/node";
4+
import { RubyVM } from "../src/index";
5+
import { DefaultRubyVM } from "../src/node";
66
import { describe, test, expect } from "vitest"
77

88
const initRubyVM = async (rubyModule, args) => {
9-
const wasi = new WASI();
9+
const wasi = new WASI({
10+
version: "preview1",
11+
returnOnExit: true,
12+
});
1013
const vm = new RubyVM();
1114
const imports = {
1215
wasi_snapshot_preview1: wasi.wasiImport,

packages/npm-packages/ruby-wasm-wasi/test/vm.test.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,11 @@ eval:11:in \`<main>'`
119119
expect(throwError).toThrowError(expectedBacktrace);
120120
});
121121

122-
test("exception while formatting exception backtrace", async () => {
123-
const vm = await initRubyVM();
122+
test.skipIf(
123+
// TODO(katei): Investigate further why this test "crashes" on Node.js 22.
124+
process.versions.node.startsWith("22.") && !process.env.ENABLE_COMPONENT_TESTS,
125+
)("exception while formatting exception backtrace", async () => {
126+
const vm = await initRubyVM({ suppressStderr: true });
124127
const throwError = () => {
125128
vm.eval(`
126129
class BrokenException < Exception
@@ -155,15 +158,18 @@ eval:11:in \`<main>'`
155158
`,
156159
`JS::RubyVM.eval("raise 'Exception from nested eval'")`,
157160
])("nested VM rewinding operation should throw fatal error", async (code) => {
158-
const vm = await initRubyVM();
161+
const vm = await initRubyVM({ suppressStderr: true });
159162
const setVM = vm.eval(`proc { |vm| JS::RubyVM = vm }`);
160163
setVM.call("call", vm.wrap(vm));
161164
expect(() => {
162165
vm.eval(code);
163166
}).toThrowError("Ruby APIs that may rewind the VM stack are prohibited");
164167
});
165168

166-
test.each([`JS::RubyVM.evalAsync("")`])(
169+
test.skipIf(
170+
// TODO(katei): Investigate further why this test "crashes" on Node.js 22.
171+
process.versions.node.startsWith("22.") && !process.env.ENABLE_COMPONENT_TESTS,
172+
).each([`JS::RubyVM.evalAsync("")`])(
167173
"nested VM rewinding operation should throw fatal error (async)",
168174
async (code) => {
169175
// Supress bugreport message triggered by the fatal error inside evalAsync
@@ -189,7 +195,7 @@ eval:11:in \`<main>'`
189195
);
190196

191197
test("caught raise in nested eval is ok", async () => {
192-
const vm = await initRubyVM();
198+
const vm = await initRubyVM({ suppressStderr: true });
193199
const setVM = vm.eval(`proc { |vm| JS::RubyVM = vm }`);
194200
setVM.call("call", vm.wrap(vm));
195201
expect(() => {

0 commit comments

Comments
 (0)