Skip to content

Commit

Permalink
add coverage test
Browse files Browse the repository at this point in the history
  • Loading branch information
hyrious committed Feb 18, 2024
1 parent 41244a8 commit 014ee2b
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 34 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
coverage
dist
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
},
"scripts": {
"build": "esbuild-dev scripts/build.ts",
"test": "c8 --include=src esbuild-dev --no-warnings --loader test/index.ts"
"test": "c8 --include=src --reporter=html esbuild-dev --no-warnings --loader test/index.ts"
},
"devDependencies": {
"@hyrious/dts": "^0.2.1",
Expand Down
14 changes: 7 additions & 7 deletions src/ruby.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ export class RubyClass extends RubyBaseObject {
}

export class RubyModule extends RubyBaseObject {
constructor(public name: string, public old?: boolean) {
constructor(
public name: string,
public old?: boolean,
) {
super();
}
}
Expand All @@ -46,12 +49,9 @@ export class RubyHash {
this.entries = entries || [];
if (default_ !== undefined) this.default = default_;
}
/** Returns a new object that is a ruby Hash with `compare_by_identity` enabled. */
compareByIdentity(): RubyObject {
var obj = new RubyObject(Symbol.for("Hash"));
obj.wrapped = this;
return obj;
}
// compareByIdentity() {
// throw new Error("Not implemented");
// }
}

export class RubyRange extends RubyObject {
Expand Down
91 changes: 87 additions & 4 deletions test/dump.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { describe, rb_load } from "./helper";

function dumps(
value: unknown,
opts: { pre?: string; post?: string } & marshal.DumpOptions = {}
opts: { pre?: string; post?: string } & marshal.DumpOptions = {},
): Promise<string> {
return rb_load(marshal.dump(value, opts), opts.pre, opts.post);
}
Expand All @@ -20,14 +20,39 @@ describe("dump", test => {
});

test("number", async () => {
assert.is(await dumps(42), "42");
assert.is(await dumps(-42), "-42");
assert.is(await dumps(114514), "114514");
assert.is(await dumps(-1919810), "-1919810");
assert.is(await dumps(1145141919810), "1145141919810");
assert.is(await dumps(-11451419198), "-11451419198");
assert.is(await dumps(123.456), "123.456");
assert.is(await dumps(new marshal.RubyInteger(114514)), "114514");
assert.is(await dumps(new marshal.RubyInteger(1145141919810)), "1145141919810");
assert.is(await dumps(new marshal.RubyFloat(-0)), "-0.0");
assert.is(await dumps(1 / 0), "Infinity");
assert.is(await dumps(-1 / 0), "-Infinity");
assert.is(await dumps(NaN), "NaN");
});

test("string", async () => {
assert.is(await dumps("hello"), '"hello"');
assert.is(await dumps(new TextEncoder().encode("hello")), '"hello"');
});

test("regexp", async () => {
assert.is(await dumps(/hello/), "/hello/");
});

test("hash", async () => {
assert.is(await dumps(new marshal.RubyHash([["a", 1]])), '{"a"=>1}');
assert.is(await dumps(new Map([["a", 1]])), '{"a"=>1}');

let a = new marshal.RubyHash([
["x", 1],
["x", 1],
]);
assert.is(await dumps(a), '{"x"=>1}');
});

test("circular", async () => {
Expand Down Expand Up @@ -62,17 +87,75 @@ describe("dump", test => {
assert.is(await dumps(obj, { hashStringKeysToSymbol: true }), "{:a=>1, :b=>2}");
});

test("error on undefined", async () => {
try {
await dumps({ a: void 0 });
assert.unreachable("should throw error");
} catch (e) {
assert.instance(e, TypeError);
assert.match(e.message, /can't dump/);
}
});

test("user class", async () => {
let a = new marshal.RubyObject(Symbol.for("MyHash"));
a[marshal.S_EXTENDS] = [Symbol.for("MyHash")];
a.wrapped = {}; // a Hash
assert.is(await dumps(a, { pre: "class MyHash < Hash; end", post: "print a.class" }), "MyHash");
});

test("user defined", async () => {
let a = new marshal.RubyObject(Symbol.for("UserDefined"));
a.userDefined = Uint8Array.of(42);
const pre = `
class UserDefined
attr_accessor :a
def self._load(data)
obj = allocate
obj.a = data
obj
end
end
`;
const post = `print a.a`;
assert.is(await dumps(a, { pre, post }), "*");
});

test("user marshal", async () => {
let a = new marshal.RubyObject(Symbol.for("A"));
a.userMarshal = []; // an Array
const pre = "class A; def marshal_load(obj) print obj.inspect end end";
assert.is(await dumps(a, { pre, post: "" }), "[]");
});

test("known", async () => {
class A {}
let a = new A();
try {
await dumps(new A());
a[marshal.S_EXTENDS] = [Symbol.for("A")];
await dumps(a);
assert.unreachable("should throw error");
} catch (e) {
assert.instance(e, TypeError);
assert.match(e.message, /can't dump/);
}
let pre = "class A end";
assert.match(await dumps(new A(), { pre, known: { A } }), /^#<A:0x[a-f0-9]+>$/);
assert.match(await dumps(new A(), { pre, unknown: a => a?.constructor?.name }), /^#<A:0x[a-f0-9]+>$/);
assert.match(await dumps(a, { pre, known: { A } }), /^#<A:0x[a-f0-9]+>$/);
assert.match(await dumps(a, { pre, unknown: a => a?.constructor?.name }), /^#<A:0x[a-f0-9]+>$/);
});

test("struct", async () => {
let a = new marshal.RubyStruct(Symbol.for("A"));
a.members = { [Symbol.for("a")]: 1 };
assert.is(await dumps(a, { pre: "A = Struct.new :a" }), "#<struct A a=1>");
});

test("class and module", async () => {
assert.is(await dumps(new marshal.RubyClass("A"), { pre: "class A end" }), "A");
assert.is(await dumps(new marshal.RubyModule("A"), { pre: "module A end" }), "A");
});

test("range", async () => {
assert.is(await dumps(new marshal.RubyRange(1, 10, true)), "1...10");
});
});
12 changes: 9 additions & 3 deletions test/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ import os from "os";
import path from "path";
import { suite, Test } from "uvu";

const tests: Test<unknown>[] = [];

export function describe<T = any>(title: string, callback: (test: Test<T>) => void) {
const test = suite<T>(title);
callback(test);
test.run();
tests.push(test);
}

export function runTests() {
tests.forEach(t => t.run());
}

/**
Expand All @@ -20,8 +26,8 @@ export async function rb_eval(code: string): Promise<string> {
await fs.promises.writeFile(file, code);
const output = await new Promise<string>((resolve, reject) =>
cp.exec(`ruby ${JSON.stringify(file)}`, (err, stdout, stderr) =>
err ? reject(err) : stderr ? reject(new Error(stderr)) : resolve(stdout)
)
err ? reject(err) : stderr ? reject(new Error(stderr)) : resolve(stdout),
),
);
await fs.promises.unlink(file);
return output;
Expand Down
28 changes: 11 additions & 17 deletions test/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Suite } from "uvu/parse";
import { readdirSync } from "fs";
import { build } from "esbuild";
import { join } from "path";
import { join, dirname } from "path";
import { fileURLToPath } from "url";
import { runTests } from "./helper";

const ignored = ["index.ts", "helper.ts"];
const suites: Suite[] = [];
Expand All @@ -10,23 +11,16 @@ const pattern = (() => {
return p ? new RegExp(p, "i") : /\.ts$/;
})();

readdirSync(__dirname).forEach(name => {
const dir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));

readdirSync(dir).forEach(name => {
if (ignored.includes(name)) return;
if (pattern.test(name)) suites.push({ name, file: join(__dirname, name) });
if (pattern.test(name)) suites.push({ name, file: join(dir, name) });
});
suites.sort((a, b) => a.name.localeCompare(b.name));

const outfile = "./node_modules/.cache/test.mjs";
await build({
stdin: {
contents: suites.map(e => `import ${JSON.stringify("./" + e.name)}`).join("\n"),
resolveDir: __dirname,
},
bundle: true,
format: "esm",
platform: "node",
external: ["uvu/*"],
outfile,
}).catch(() => process.exit(1));
for (const e of suites) {
await import("./" + e.name);
}

await import(join(process.cwd(), outfile));
runTests();
25 changes: 23 additions & 2 deletions test/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ function loads(code: string, options?: marshal.LoadOptions): Promise<any> {
}

describe("load", test => {
test("error on short data", async () => {
try {
await marshal.load(Uint8Array.of());
assert.unreachable("should throw error");
} catch (e) {
assert.instance(e, TypeError);
assert.match(e.message, /too short/);
}
try {
await marshal.load(Uint8Array.of(0x4, 0x9, 0, 0));
assert.unreachable("should throw error");
} catch (e) {
assert.instance(e, TypeError);
assert.match(e.message, /can't be read/);
}
});

test("trivial value", async () => {
assert.is(await loads(`nil`), null);
assert.is(await loads(`true`), true);
Expand Down Expand Up @@ -47,6 +64,10 @@ describe("load", test => {
assert.is(await loads(`:symbol`), Symbol.for("symbol"));
});

test("regexp", async () => {
assert.equal(await loads(`/hello/`), /hello/);
});

test("hash", async () => {
let hash = await loads(`a = Hash.new(false); a[:a] = true; a`);
assert.is(hash[marshal.S_DEFAULT], false);
Expand All @@ -59,7 +80,7 @@ describe("load", test => {
assert.equal(obj1[marshal.S_EXTENDS], [Symbol.for("M")]);

let obj2: marshal.RubyObject = await loads(
`module M end; class A end; a = A.new; a.singleton_class.prepend M; a`
`module M end; class A end; a = A.new; a.singleton_class.prepend M; a`,
);
assert.is(obj2.class, Symbol.for("A"));
assert.equal(obj2[marshal.S_EXTENDS], [Symbol.for("M"), Symbol.for("A")]);
Expand All @@ -78,7 +99,7 @@ describe("load", test => {

test("struct", async () => {
let struct = marshal.load(
await rb_str`"\004\bS:\023Struct::Useful\a:\006ai\006:\006bi\a"`
await rb_str`"\004\bS:\023Struct::Useful\a:\006ai\006:\006bi\a"`,
) as marshal.RubyStruct;
assert.instance(struct, marshal.RubyStruct);
assert.is(struct.class, Symbol.for("Struct::Useful"));
Expand Down

0 comments on commit 014ee2b

Please sign in to comment.