Skip to content

Commit 309ce9f

Browse files
committed
add db resolve regression coverage
1 parent 943f6af commit 309ce9f

4 files changed

Lines changed: 120 additions & 9 deletions

File tree

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
"prepublishOnly": "node scripts/build",
8181
"watch": "node scripts/build --watch",
8282
"coverage": "npx c8 -- npm test",
83-
"test": "npm run test:parser && npm run test:compiler -- --parallel && npm run test:browser && npm run test:toilconfig && npm run test:transform && npm run test:cli && npm run test:json && npm run test:data && npm run test:dbstatic",
83+
"test": "npm run test:parser && npm run test:compiler -- --parallel && npm run test:browser && npm run test:toilconfig && npm run test:transform && npm run test:cli && npm run test:json && npm run test:data && npm run test:dbresolve && npm run test:dbstatic && npm run test:streams",
8484
"test:parser": "node --enable-source-maps tests/parser",
8585
"test:compiler": "node --enable-source-maps --no-warnings tests/compiler",
8686
"test:browser": "node --enable-source-maps tests/browser",
@@ -91,7 +91,9 @@
9191
"test:cli": "node tests/cli/options.js",
9292
"test:json": "node tests/json/run.mjs",
9393
"test:data": "node tests/data/run.mjs",
94+
"test:dbresolve": "node tests/dbresolve/run.mjs",
9495
"test:dbstatic": "node tests/dbstatic/run.mjs",
96+
"test:streams": "node tests/streams/run.mjs && node tests/streams/catalog.mjs && node tests/streams/codegen.mjs",
9597
"asbuild": "npm run asbuild:debug && npm run asbuild:release",
9698
"asbuild:debug": "node bin/toilscript --config src/toilconfig.json --target debug",
9799
"asbuild:release": "node bin/toilscript --config src/toilconfig.json --target release",

std/assembly/toildb.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ import { DataWriter } from "data";
2121
export function __toildbResolve(name: string): u32 {
2222
const nb = Uint8Array.wrap(String.UTF8.encode(name));
2323
const out = new Uint8Array(4);
24-
toildbHost.resolveCollection(nb.dataStart, nb.byteLength, out.dataStart);
24+
const status = toildbHost.resolveCollection(nb.dataStart, nb.byteLength, out.dataStart);
25+
if (status < 0) abort("ToilDB collection resolve failed", "toildb", 0, 0);
2526
return load<u32>(out.dataStart);
2627
}
2728

tests/dbmigrate/run.mjs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
// (nothing imports them) and the weave INJECTS the cross-file imports into
1010
// the value type's module. So each spec here is a value-type `app.ts` plus a
1111
// separate `migrations/*.migration.ts`, compiled in an isolated dir.
12-
import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
12+
import { readFileSync, mkdtempSync, mkdirSync, writeFileSync, rmSync, openSync, closeSync } from "node:fs";
1313
import { tmpdir } from "node:os";
1414
import { join, dirname } from "node:path";
1515
import { fileURLToPath } from "node:url";
@@ -35,17 +35,30 @@ function build(files, { expectFail = false } = {}) {
3535
writeFileSync(fp, files[rel]);
3636
}
3737
const out = join(tmp, "out.wasm");
38-
const r = spawnSync("node", [bin, "app.ts", "-o", out, "--runtime", "stub"], {
38+
const log = join(tmp, "compile.log");
39+
let logFd = -1;
40+
const stdio = expectFail
41+
? (logFd = openSync(log, "w"), ["ignore", logFd, logFd])
42+
: ["ignore", "ignore", "inherit"];
43+
const r = spawnSync("node", ["--enable-source-maps", bin, "app.ts", "-o", out, "--runtime", "stub"], {
3944
cwd: tmp,
40-
stdio: ["ignore", "ignore", expectFail ? "pipe" : "inherit"],
45+
stdio,
4146
});
47+
if (logFd >= 0) closeSync(logFd);
48+
const output = expectFail ? readFileSync(log, "utf8") : "";
4249
let buf = null;
4350
if (!expectFail) {
4451
if (r.status !== 0) { rmSync(tmp, { recursive: true, force: true }); fail("COMPILE FAILED"); }
4552
buf = readFileSync(out);
4653
}
4754
rmSync(tmp, { recursive: true, force: true });
48-
return { status: r.status, stderr: String(r.stderr || ""), buf };
55+
return {
56+
status: r.status,
57+
stderr: output,
58+
error: r.error,
59+
signal: r.signal,
60+
buf,
61+
};
4962
}
5063

5164
// The value-type module shared by the migrating specs: current `User` (exported so
@@ -180,11 +193,11 @@ export class UserId { id: u64 = 0; }
180193
@data
181194
export class User { id: u64 = 0; }
182195
@database
183-
class App { @collection users: Documents<UserId, User>; }
196+
export class App { @collection users: Documents<UserId, User>; }
184197
export function probe(): u64 { return App.users.require(new UserId()).id; }
185198
`,
186199
"migrations/User.migration.ts": `
187-
import { User, UserId } from "../app";
200+
import { App, User, UserId } from "../app";
188201
@data
189202
export class UserV1 { id: u32 = 0; }
190203
@migrate
@@ -197,7 +210,7 @@ export function up(old: UserV1): User {
197210
}, { expectFail: true });
198211
if (bad.status === 0) fail("a @migrate that touches the database must be a compile error");
199212
if (!bad.stderr.includes("migrate"))
200-
fail(`expected a @migrate diagnostic, got: ${bad.stderr.slice(0, 200)}`);
213+
fail(`expected a @migrate diagnostic, got: status=${bad.status} signal=${bad.signal} error=${bad.error ?? ""} output=${bad.stderr.slice(0, 200)}`);
201214

202215
// --- (d2) a @migrate OUTSIDE a migrations/*.migration.ts file must NOT compile ---
203216
const misplaced = build({

tests/dbresolve/run.mjs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
// Compile a tiny ToilDB program and instantiate it against a host that rejects
2+
// collection resolution. The generated binding must trap immediately instead of
3+
// reading the zeroed output buffer as handle 0 and calling request-time DB ops.
4+
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from "node:fs";
5+
import { tmpdir } from "node:os";
6+
import { join, dirname } from "node:path";
7+
import { fileURLToPath } from "node:url";
8+
import { spawnSync } from "node:child_process";
9+
10+
const here = dirname(fileURLToPath(import.meta.url));
11+
const root = join(here, "..", "..");
12+
const tmp = mkdtempSync(join(tmpdir(), "dbresolve-"));
13+
const app = join(tmp, "app.ts");
14+
const out = join(tmp, "out.wasm");
15+
16+
function fail(msg) {
17+
console.error(`ToilDB resolve test suite: ${msg}`);
18+
rmSync(tmp, { recursive: true, force: true });
19+
process.exit(1);
20+
}
21+
22+
writeFileSync(app, `
23+
@data
24+
class Key {
25+
id: string = "";
26+
constructor(id: string = "") { this.id = id; }
27+
}
28+
29+
@data
30+
class User {
31+
name: string = "";
32+
}
33+
34+
@database
35+
class App {
36+
@collection static users: Documents<Key, User>;
37+
}
38+
39+
export function probe(): i32 {
40+
return App.users.exists(new Key("alice")) ? 1 : 0;
41+
}
42+
`);
43+
44+
const compile = spawnSync(
45+
"node",
46+
[join(root, "bin", "toilscript.js"), app, "-o", out, "--runtime", "stub"],
47+
{ stdio: "inherit" },
48+
);
49+
if (compile.status !== 0) fail("COMPILE FAILED");
50+
51+
let memory = null;
52+
let existsCalls = 0;
53+
function readString(ptr) {
54+
if (!ptr || !memory) return "";
55+
const u32 = new Uint32Array(memory.buffer);
56+
const len = u32[(ptr - 4) >>> 2];
57+
const u16 = new Uint16Array(memory.buffer, ptr, len >>> 1);
58+
return String.fromCharCode.apply(null, u16);
59+
}
60+
61+
const imports = {
62+
env: {
63+
abort(msgPtr, filePtr, line, col) {
64+
const msg = readString(msgPtr);
65+
const file = readString(filePtr);
66+
throw new Error(`abort ${file}:${line}:${col}${msg ? " (" + msg + ")" : ""}`);
67+
},
68+
"data.resolve_collection"() {
69+
return -1007;
70+
},
71+
"data.exists"(handle) {
72+
existsCalls++;
73+
if (handle === 0) throw new Error("BUG: request-time DB op used handle 0");
74+
return 0;
75+
},
76+
},
77+
};
78+
79+
try {
80+
const { instance } = await WebAssembly.instantiate(readFileSync(out), imports);
81+
memory = instance.exports.memory;
82+
instance.exports.probe();
83+
fail("probe unexpectedly returned after resolve_collection failed");
84+
} catch (e) {
85+
const msg = String(e && e.message ? e.message : e);
86+
if (!msg.includes("ToilDB collection resolve failed")) {
87+
fail(`expected resolve failure abort, got: ${msg}`);
88+
}
89+
if (existsCalls !== 0) {
90+
fail(`request-time DB op was called ${existsCalls} time(s) after failed resolve`);
91+
}
92+
}
93+
94+
rmSync(tmp, { recursive: true, force: true });
95+
console.log("ToilDB resolve test suite: ALL PASS");

0 commit comments

Comments
 (0)