|
8 | 8 | import type { WastJson } from "../src/schema.ts"; |
9 | 9 | import { CoreOnlyExecutor } from "../src/executor.ts"; |
10 | 10 | import type { CommandExecutor } from "../src/executor.ts"; |
11 | | -import { TrapError } from "../src/executor.ts"; |
| 11 | +import { LinkError, TrapError } from "../src/executor.ts"; |
12 | 12 | import { runWastJson, trapMatches } from "../src/runner.ts"; |
13 | 13 |
|
14 | 14 | // (module) - the empty core module, hand-encoded. |
@@ -204,6 +204,34 @@ Deno.test("assert_uninstantiable rejects an unrelated trap cause", async () => { |
204 | 204 | assertEq(result.results[0].status, "failed", "status"); |
205 | 205 | }); |
206 | 206 |
|
| 207 | +Deno.test("assert_unlinkable requires a matching link diagnostic", async () => { |
| 208 | + for ( |
| 209 | + const [message, status] of [ |
| 210 | + ["expected resource found func", "passed"], |
| 211 | + ["different cause", "failed"], |
| 212 | + [ |
| 213 | + "prefix host import 'host/return-three' must be a HostResourceType (the component imports a resource type); got a function", |
| 214 | + "failed", |
| 215 | + ], |
| 216 | + ] as const |
| 217 | + ) { |
| 218 | + const executor = new CoreOnlyExecutor() as CommandExecutor; |
| 219 | + executor.instantiate = () => Promise.reject(new LinkError(message)); |
| 220 | + const result = await runWastJson( |
| 221 | + doc([{ |
| 222 | + type: "assert_unlinkable", |
| 223 | + line: 1, |
| 224 | + filename: "comp.0.wasm", |
| 225 | + module_type: "binary", |
| 226 | + text: "expected resource found func", |
| 227 | + }]), |
| 228 | + load, |
| 229 | + executor, |
| 230 | + ); |
| 231 | + assertEq(result.results[0].status, status, message); |
| 232 | + } |
| 233 | +}); |
| 234 | + |
207 | 235 | // Exact diagnostic equivalents: the core `unreachable` trap row. The runtime |
208 | 236 | // (runtime/src/exec/boundary.ts mapCoreException) passes each JS engine's raw |
209 | 237 | // trap text through untouched; this table is where the suite's |
@@ -373,12 +401,44 @@ Deno.test("trapMatches: verified diagnostic equivalents match only their named o |
373 | 401 | "wasm `unreachable` instruction executed", |
374 | 402 | "guest trapped: unreachable", |
375 | 403 | ], |
| 404 | + [ |
| 405 | + "cannot remove owned resource while borrowed", |
| 406 | + "handle still lent out", |
| 407 | + ], |
| 408 | + ["string pointer not aligned to 2", "misaligned string pointer"], |
| 409 | + [ |
| 410 | + "was not found", |
| 411 | + "host import 'host/missing' must be a HostResourceType (the component imports a resource type); got undefined", |
| 412 | + ], |
| 413 | + [ |
| 414 | + "expected resource found func", |
| 415 | + "host import 'host/return-three' must be a HostResourceType (the component imports a resource type); got a function", |
| 416 | + ], |
376 | 417 | ]; |
377 | 418 | for (const [expected, actual] of equivalents) { |
378 | 419 | assertEq(trapMatches(expected, actual), true, `${expected} / ${actual}`); |
379 | 420 | } |
380 | 421 | }); |
381 | 422 |
|
| 423 | +Deno.test("generic variant diagnostic is not globally equivalent", () => { |
| 424 | + assertEq( |
| 425 | + trapMatches( |
| 426 | + "discriminant 2 out of range [0..2)", |
| 427 | + "invalid variant discriminant", |
| 428 | + ), |
| 429 | + false, |
| 430 | + "global matcher", |
| 431 | + ); |
| 432 | + assertEq( |
| 433 | + trapMatches( |
| 434 | + "discriminant 2 out of range [0..2)", |
| 435 | + "discriminant 3 out of range [0..3)", |
| 436 | + ), |
| 437 | + false, |
| 438 | + "different discriminant and case count", |
| 439 | + ); |
| 440 | +}); |
| 441 | + |
382 | 442 | Deno.test("trapMatches: narrow diagnostic rows reject adjacent but different traps", () => { |
383 | 443 | const nonEquivalents: Array<[string, string]> = [ |
384 | 444 | ["integer overflow", "integer underflow"], |
@@ -407,6 +467,11 @@ Deno.test("trapMatches: narrow diagnostic rows reject adjacent but different tra |
407 | 467 | "uncaught exception propagated out of component", |
408 | 468 | "guest trapped: unreachable", |
409 | 469 | ], |
| 470 | + ["string pointer not aligned to 2", "misaligned list pointer"], |
| 471 | + [ |
| 472 | + "was not found", |
| 473 | + "prefix host import 'host/missing' must be a HostResourceType (the component imports a resource type); got undefined", |
| 474 | + ], |
410 | 475 | ]; |
411 | 476 | for (const [expected, actual] of nonEquivalents) { |
412 | 477 | assertEq(trapMatches(expected, actual), false, `${expected} / ${actual}`); |
@@ -440,6 +505,11 @@ Deno.test("trapMatches: exact equivalents reject expected and actual affixes", ( |
440 | 505 | "invalid `task.return` signature and/or options for current task", |
441 | 506 | "task.return with canonical options differing from the task's", |
442 | 507 | ], |
| 508 | + ["string pointer not aligned to 2", "misaligned string pointer"], |
| 509 | + [ |
| 510 | + "expected resource found func", |
| 511 | + "host import 'host/return-three' must be a HostResourceType (the component imports a resource type); got a function", |
| 512 | + ], |
443 | 513 | ] |
444 | 514 | ) { |
445 | 515 | for ( |
|
0 commit comments