Skip to content

Commit 2f6a3dd

Browse files
Move Ruby JS lib unit tests to test-unit
1 parent 04c19cf commit 2f6a3dd

File tree

8 files changed

+224
-112
lines changed

8 files changed

+224
-112
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
dist
22
src/bindgen
33
coverage
4+
# To avoid breaking shebang trick
5+
/tools/run-test-unit.mjs

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@
2323
],
2424
"license": "MIT",
2525
"scripts": {
26-
"test": "NODE_OPTIONS=\"--experimental-wasi-unstable-preview1\" jest --coverage",
26+
"test": "npm run test:unit && npm run test:jest",
27+
"test:jest": "NODE_OPTIONS=\"--experimental-wasi-unstable-preview1\" jest --coverage",
28+
"test:unit": "./tools/run-test-unit.mjs",
2729
"format": "prettier --write ."
2830
},
2931
"devDependencies": {

packages/npm-packages/ruby-wasm-wasi/test/js_from_rb.test.ts

Lines changed: 0 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -11,83 +11,6 @@ describe("Manipulation of JS from Ruby", () => {
1111
expect((result as any).inner._wasm_val).toBe(Qtrue);
1212
});
1313

14-
test.each([
15-
// A ruby object always returns false
16-
{ object: "1", klass: "Integer", result: false },
17-
{ object: "'x'", klass: "String", result: false },
18-
// A js object is not an instance of itself
19-
{ object: "JS.global", klass: "JS.global", result: false },
20-
// globalThis is an instance of Object
21-
{ object: "JS.global", klass: "JS.global[:Object]", result: true },
22-
])("JS.is_a? %s", async (props) => {
23-
const vm = await initRubyVM();
24-
const code = `require "js"; JS.is_a?(${props.object}, ${props.klass})`;
25-
expect(vm.eval(code).toString()).toBe(String(props.result));
26-
});
27-
28-
test.each([
29-
{ object: `JS.eval('return 1')`, result: "number" },
30-
{ object: `JS.eval('return "x"')`, result: "string" },
31-
{ object: `JS.eval('return null')`, result: "object" },
32-
{ object: `JS.eval('return undefined')`, result: "undefined" },
33-
{ object: `JS.global`, result: "object" },
34-
])("JS::Object#typeof (%s)", async (props) => {
35-
const vm = await initRubyVM();
36-
const code = `require "js"; (${props.object}).typeof`;
37-
expect(vm.eval(code).toString()).toBe(String(props.result));
38-
});
39-
40-
test.each([
41-
{ lhs: `24`, rhs: `24`, result: true },
42-
{ lhs: `null`, rhs: `null`, result: true },
43-
{ lhs: `undefined`, rhs: `undefined`, result: true },
44-
{ lhs: `"str"`, rhs: `"str"`, result: true },
45-
{ lhs: `48`, rhs: `24`, result: false },
46-
{ lhs: `NaN`, rhs: `NaN`, result: false },
47-
])("JS::Object#== (%s)", async (props) => {
48-
const vm = await initRubyVM();
49-
const methodResult = `require "js"; JS.eval('return ${props.lhs}').eql?(JS.eval('return ${props.rhs}'))`;
50-
expect(vm.eval(methodResult).toString()).toBe(String(props.result));
51-
52-
const operatorResult = `require "js"; JS.eval('return ${props.lhs}') == JS.eval('return ${props.rhs}')`;
53-
expect(vm.eval(operatorResult).toString()).toBe(String(props.result));
54-
});
55-
56-
test.each([
57-
{ lhs: `24`, rhs: `24`, result: true },
58-
{ lhs: `null`, rhs: `null`, result: true },
59-
{ lhs: `undefined`, rhs: `undefined`, result: true },
60-
{ lhs: `new String("str")`, rhs: `"str"`, result: false },
61-
])("JS::Object#strictly_eql? (%s)", async (props) => {
62-
const vm = await initRubyVM();
63-
const result = `require "js"; JS.eval('return ${props.lhs}').strictly_eql?(JS.eval('return ${props.rhs}'))`;
64-
expect(vm.eval(result).toString()).toBe(String(props.result));
65-
});
66-
67-
test.each([`24`, `"hello"`, `null`, `undefined`])(
68-
"JS::Object#to_s (%s)",
69-
async (value) => {
70-
const vm = await initRubyVM();
71-
const to_s_result = `require "js"; JS.eval('return ${value}').to_s`;
72-
const inspect_result = `require "js"; JS.eval('return ${value}').inspect`;
73-
expect(vm.eval(to_s_result).toString()).toBe(String(eval(value)));
74-
expect(vm.eval(inspect_result).toString()).toBe(String(eval(value)));
75-
}
76-
);
77-
78-
test.each([
79-
{ self: `24`, calee: "toString", args: [], result: "24" },
80-
{ self: `"hello"`, calee: "charAt", args: [4], result: "o" },
81-
])("JS::Object#method_missing (%s)", async (props) => {
82-
const vm = await initRubyVM();
83-
const result = `
84-
require "js"
85-
obj = JS.eval('return ${props.self}')
86-
obj.${props.calee}(${props.args.join(", ")})
87-
`;
88-
expect(vm.eval(result).toString()).toBe(props.result);
89-
});
90-
9114
test("JS::Object#method_missing with block", async () => {
9215
const vm = await initRubyVM();
9316
const proc = vm.eval(`
@@ -306,21 +229,4 @@ describe("Manipulation of JS from Ruby", () => {
306229
object.call("itself");
307230
}
308231
});
309-
310-
test("Guard null", async () => {
311-
const vm = await initRubyVM();
312-
const result = vm.eval(`
313-
require "js"
314-
intrinsics = JS.eval(<<-JS)
315-
return {
316-
returnNull(v) { return null },
317-
returnUndef(v) { return undefined },
318-
}
319-
JS
320-
js_null = JS.eval("return null")
321-
o1 = intrinsics.call(:returnNull)
322-
o1 == js_null
323-
`);
324-
expect(result.toString()).toEqual("true");
325-
});
326232
});
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require_relative "./unit/test_js"
2+
require_relative "./unit/test_object"

packages/npm-packages/ruby-wasm-wasi/test/unit/object.test.ts

Lines changed: 0 additions & 17 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
require "test-unit"
2+
require "js"
3+
4+
class JS::TestJS < Test::Unit::TestCase
5+
def test_is_a?
6+
# A ruby object always returns false
7+
assert_false JS.is_a?(1, Integer)
8+
assert_false JS.is_a?('x', String)
9+
# A js object is not an instance of itself
10+
assert_false JS.is_a?(JS.global, JS.global)
11+
# globalThis is an instance of Object
12+
assert_true JS.is_a?(JS.global, JS.global[:Object])
13+
end
14+
15+
def test_eval
16+
JS.eval("var x = 42;")
17+
# Variable scope is isolated in each JS.eval
18+
assert_equal "not defined", JS.eval(<<~JS).to_s
19+
try {
20+
return x;
21+
} catch {
22+
return "not defined";
23+
}
24+
JS
25+
end
26+
end
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
require "test-unit"
2+
require "js"
3+
4+
class JS::TestObject < Test::Unit::TestCase
5+
def test_typeof
6+
assert_equal "number", JS.eval("return 1;").typeof
7+
assert_equal "boolean", JS.eval("return true;").typeof
8+
assert_equal "string", JS.eval("return 'x';").typeof
9+
assert_equal "object", JS.eval("return null;").typeof
10+
assert_equal "undefined", JS.eval("return undefined;").typeof
11+
assert_equal "object", JS.global.typeof
12+
end
13+
14+
def assert_object_eql?(result, a, b)
15+
assert_equal result, a == b
16+
assert_equal !result, a != b
17+
assert_equal result, a.eql?(b)
18+
assert_equal result, b.eql?(a)
19+
end
20+
21+
def test_eql?
22+
assert_object_eql? true, JS.eval("return 24;"), JS.eval("return 24;")
23+
assert_object_eql? true, JS.eval("return null;"), JS.eval("return null;")
24+
assert_object_eql? true, JS.eval("return undefined;"), JS.eval("return undefined;")
25+
assert_object_eql? true, JS.eval("return 'x';"), JS.eval("return 'x';")
26+
assert_object_eql? true, JS.eval("return null;"), JS.eval("return undefined;")
27+
28+
assert_object_eql? false, JS.eval("return 24;"), JS.eval("return 42;")
29+
assert_object_eql? false, JS.eval("return NaN;"), JS.eval("return NaN;")
30+
end
31+
32+
def assert_object_strictly_eql?(result, a, b)
33+
assert_equal result, a.strictly_eql?(b)
34+
assert_equal result, b.strictly_eql?(a)
35+
end
36+
37+
def test_strictly_eql?
38+
assert_object_strictly_eql? true, JS.eval("return 24;"), JS.eval("return 24;")
39+
assert_object_strictly_eql? true, JS.eval("return null;"), JS.eval("return null;")
40+
assert_object_strictly_eql? true, JS.eval("return undefined;"), JS.eval("return undefined;")
41+
assert_object_strictly_eql? false, JS.eval("return new String('str');"), JS.eval("return 'str';")
42+
assert_object_strictly_eql? false, JS.eval("return null;"), JS.eval("return undefined;")
43+
end
44+
45+
def test_to_s
46+
assert_equal "24", JS.eval("return 24;").to_s
47+
assert_equal "true", JS.eval("return true;").to_s
48+
assert_equal "null", JS.eval("return null;").to_s
49+
assert_equal "undefined", JS.eval("return undefined;").to_s
50+
end
51+
52+
def test_inspect
53+
assert_equal "24", JS.eval("return 24;").inspect
54+
assert_equal "true", JS.eval("return true;").inspect
55+
assert_equal "null", JS.eval("return null;").inspect
56+
assert_equal "undefined", JS.eval("return undefined;").inspect
57+
end
58+
59+
def test_call
60+
assert_nothing_raised { JS.global.call(:Array) }
61+
assert_equal "1,2,3", JS.global.call(:Array, 1, 2, 3).to_s
62+
end
63+
64+
def test_method_missing
65+
assert_equal "42", JS.eval("return 42;").toString.to_s
66+
assert_equal "o", JS.eval("return 'hello';").charAt(4).to_s
67+
end
68+
69+
def test_method_missing_with_block
70+
obj = JS.eval(<<~JS)
71+
return {
72+
takeBlock(block) {
73+
return block(1, 2, 3);
74+
}
75+
}
76+
JS
77+
block_called = false
78+
# TODO: Support return value in block
79+
result = obj.takeBlock do |a, b, c|
80+
block_called = true
81+
# TODO: Compare them as integers after introducing `JS::Object#to_i`
82+
assert_equal 1.to_s, a.to_s
83+
assert_equal 2.to_s, b.to_s
84+
assert_equal 3.to_s, c.to_s
85+
end
86+
assert_true block_called
87+
end
88+
89+
def test_method_missing_with_undefined_method
90+
object = JS.eval(<<~JS)
91+
return { foo() { return true; } };
92+
JS
93+
assert_raise(NoMethodError) { object.bar }
94+
end
95+
96+
def test_respond_to_missing?
97+
object = JS.eval(<<~JS)
98+
return { foo() { return true; } };
99+
JS
100+
assert_true object.respond_to?(:foo)
101+
assert_false object.respond_to?(:bar)
102+
end
103+
104+
def test_member_get
105+
object = JS.eval(<<~JS)
106+
return { foo: 42 };
107+
JS
108+
assert_equal 42.to_s, object[:foo].to_s
109+
assert_equal 42.to_s, object["foo"].to_s
110+
end
111+
112+
def test_member_set
113+
object = JS.eval(<<~JS)
114+
return { foo: 42 };
115+
JS
116+
object[:foo] = 24
117+
assert_equal 24.to_s, object[:foo].to_s
118+
object["foo"] = 42
119+
assert_equal 42.to_s, object["foo"].to_s
120+
end
121+
end
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
#!/bin/sh
2+
":" //# ; exec /usr/bin/env node --experimental-wasi-unstable-preview1 "$0" "$@"
3+
4+
import fs from "fs/promises";
5+
import path from "path";
6+
import { WASI } from "wasi";
7+
import { RubyVM } from "../dist/index.cjs.js";
8+
9+
const instantiate = async (rootTestFile) => {
10+
const dirname = path.dirname(new URL(import.meta.url).pathname);
11+
let binaryPath;
12+
let preopens = {
13+
__root__: path.join(dirname, ".."),
14+
};
15+
if (process.env.RUBY_ROOT) {
16+
binaryPath = path.join(process.env.RUBY_ROOT, "./usr/local/bin/ruby");
17+
preopens["/usr"] = path.join(process.env.RUBY_ROOT, "./usr");
18+
} else {
19+
binaryPath = path.join(dirname, "../dist/ruby+stdlib.wasm");
20+
}
21+
const binary = await fs.readFile(binaryPath);
22+
const rubyModule = await WebAssembly.compile(binary);
23+
const wasi = new WASI({
24+
stdio: "inherit",
25+
args: ["ruby.wasm"].concat(process.argv.slice(2)),
26+
env: process.env,
27+
preopens: preopens,
28+
});
29+
30+
const vm = new RubyVM();
31+
const imports = {
32+
wasi_snapshot_preview1: wasi.wasiImport,
33+
};
34+
35+
vm.addToImports(imports);
36+
37+
const instance = await WebAssembly.instantiate(rubyModule, imports);
38+
await vm.setInstance(instance);
39+
40+
wasi.initialize(instance);
41+
42+
vm.initialize(["ruby.wasm", rootTestFile]);
43+
return { instance, vm };
44+
};
45+
46+
const main = async () => {
47+
const rootTestFile = "/__root__/test/test_unit.rb";
48+
const { vm } = await instantiate(rootTestFile);
49+
50+
vm.eval(`
51+
# HACK: Until we've fixed the issue in the test-unit or power_assert
52+
# See https://github.com/test-unit/test-unit/pull/221
53+
module Kernel
54+
alias test_unit_original_require require
55+
56+
def require(path)
57+
if path == "power_assert"
58+
raise LoadError, "power_assert is not supported in this environment"
59+
end
60+
test_unit_original_require(path)
61+
end
62+
end
63+
64+
require 'test/unit'
65+
require_relative '${rootTestFile}'
66+
Test::Unit::AutoRunner.run
67+
`);
68+
};
69+
70+
main();

0 commit comments

Comments
 (0)