diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/assert-throws-shared-expected.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/assert-throws-shared-expected.ts.json new file mode 100644 index 000000000..aeed90273 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/assert-throws-shared-expected.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/assert-throws-shared-expected.ts" + ], + "diags": [] +} diff --git a/packages/runtime/src/scr_assert.c b/packages/runtime/src/scr_assert.c index 6e9384f15..f21e2ba26 100644 --- a/packages/runtime/src/scr_assert.c +++ b/packages/runtime/src/scr_assert.c @@ -2425,18 +2425,29 @@ void scr_assert_loose_deep_result(bool equal, bool negated, ScrStr *msg, bool ha /* Node's expectsError over an error-INSTANCE expected (assert.throws/ * rejects second argument): every key of the expected dyn error (the * %error marker skipped; name/message/code is the encoding's surface) - * must deep-strict-equal the caught value's — extra ACTUAL keys are fine - * (Node walks the expected's keys only). A mismatch fails through the + * must be in the caught value (own or inherited) and deep-strict-equal + * it — extra ACTUAL keys are fine (Node walks the expected's keys only). A mismatch fails through the * deep-equal report (a mismatched key implies the full comparison * differs, so the diff is honest); a non-object pair falls back to the * full deep comparison outright. */ +/* actual[key] where `key in actual` holds: an own property or one on the + * prototype chain (an error's name, and its message when it is not own, + * sit on the prototype). NULL when the key is absent. */ +static ScrDyn *scr_assert_dyn_key(const ScrDyn *actual, const char *key, size_t key_len) { + for (const ScrDyn *o = actual; o != NULL && o->kind == SCR_DYN_OBJ; o = o->prototype) { + ScrDyn *v = scr_dyn_obj_get(o, key, key_len); + if (v != NULL) return v; + } + return NULL; +} + void scr_assert_expects_err_dyn(ScrDyn *actual, ScrDyn *expected, ScrStr *msg, bool has_msg) { if (actual->kind == SCR_DYN_OBJ && expected->kind == SCR_DYN_OBJ) { bool ok = true; for (size_t i = 0; i < scr_dyn_obj_count(expected) && ok; i++) { ScrDynProp e = scr_dyn_obj_prop_at(expected, i); if (e.key_len == 6 && memcmp(e.key, "%error", 6) == 0) continue; - ScrDyn *av = scr_dyn_obj_get(actual, e.key, e.key_len); + ScrDyn *av = scr_assert_dyn_key(actual, e.key, e.key_len); if (av == NULL || !scr_assert_dyn_deep_eq(av, e.value)) ok = false; } if (ok) return; diff --git a/tests/corpus/assert-throws-shared-expected.ts b/tests/corpus/assert-throws-shared-expected.ts new file mode 100644 index 000000000..a81dbfb24 --- /dev/null +++ b/tests/corpus/assert-throws-shared-expected.ts @@ -0,0 +1,46 @@ +// assert.throws and assert.rejects with an expected object held in a +// variable compare each of its keys with the thrown error's, own or +// inherited: an error's name is on its prototype, so { name, message, code } +// matches errors the runtime throws as well as errors the program throws. +import assert from "node:assert"; + +const invalidUrl = { code: "ERR_INVALID_URL", name: "TypeError", message: "Invalid URL" }; +const outOfRange = { code: "ERR_OUT_OF_RANGE", name: "RangeError" }; +const thrownType = { name: "TypeError", message: "bad input" }; +const wrongName = { name: "RangeError", message: "bad input" }; + +function check(label: string, fn: () => void): void { + try { + fn(); + console.log(label, "passed"); + } catch (error) { + const e = error as NodeJS.ErrnoException; + console.log(label, "failed", e.name, e.code); + } +} + +check("url", () => assert.throws(() => new URL("not a url"), invalidUrl)); +check("buffer", () => assert.throws(() => Buffer.alloc(-1), outOfRange)); +check("thrown", () => + assert.throws(() => { + throw new TypeError("bad input"); + }, thrownType), +); +check("mismatch", () => + assert.throws(() => { + throw new TypeError("bad input"); + }, wrongName), +); + +async function main(): Promise { + await assert.rejects(Promise.reject(new TypeError("bad input")), thrownType); + console.log("rejects passed"); + try { + await assert.rejects(Promise.reject(new TypeError("bad input")), wrongName); + console.log("rejects mismatch passed"); + } catch (error) { + console.log("rejects mismatch failed", (error as NodeJS.ErrnoException).code); + } +} + +void main();