From a4c19ae4b4ba4b38749713635b6b498d62759f28 Mon Sep 17 00:00:00 2001 From: Malte Ubl Date: Wed, 28 Oct 2026 13:04:21 -0700 Subject: [PATCH] Host the bare compareArray from the upstream assert.js harness harness/assert.js defines `compareArray(a, b)` beside `assert`: a plain function that RETURNS a boolean, whose throwing counterpart is `assert.compareArray` (already hosted). The adapter now injects the bare name into every prepared test and implements the upstream comparison: read both `.length`s, so a nullish operand throws the same TypeError, return false on a length mismatch, otherwise compare elements by SameValue. That also admits the compareArray.js include when the only use is a direct call. A value reference or property read stays excluded, because it would need the upstream function object and its `format` expando. Co-Authored-By: Claude Opus 5.5 --- tests/harness/test262-runner.test.ts | 12 ++++++++++-- tests/harness/test262.test.ts | 20 ++++++++++++++++++++ tests/test262/harness.ts | 22 ++++++++++++++++++++++ tests/test262/support.mjs | 8 +++++++- 4 files changed, 59 insertions(+), 3 deletions(-) diff --git a/tests/harness/test262-runner.test.ts b/tests/harness/test262-runner.test.ts index 3ddf3ca84..3873d1c34 100644 --- a/tests/harness/test262-runner.test.ts +++ b/tests/harness/test262-runner.test.ts @@ -250,14 +250,22 @@ test("built-in error assertions are admitted without permitting constructor alia expect(exclusion(alias, metadata(alias), "strict")).toBe("harness:assert-surface"); }); -test("the compareArray include only admits the implemented assertion form", () => { +test("the compareArray include admits the assertion and the bare call form only", () => { const accepted = source( "includes: [compareArray.js]", "assert.compareArray([1, NaN], [1, NaN]);", ); expect(exclusion(accepted, metadata(accepted), "strict")).toBeUndefined(); const globalHelper = source("includes: [compareArray.js]", "compareArray([1], [1]);"); - expect(exclusion(globalHelper, metadata(globalHelper), "strict")).toBe( + expect(exclusion(globalHelper, metadata(globalHelper), "strict")).toBeUndefined(); + // assert.js supplies compareArray, so a test needs no include to call it. + const withoutInclude = source("description: intl", "assert(!compareArray([1], [2]));"); + expect(exclusion(withoutInclude, metadata(withoutInclude), "strict")).toBeUndefined(); + const valueReference = source( + "includes: [compareArray.js]", + "const compare = compareArray; assert(compare([1], [1]));", + ); + expect(exclusion(valueReference, metadata(valueReference), "strict")).toBe( "harness:compareArray-surface", ); }); diff --git a/tests/harness/test262.test.ts b/tests/harness/test262.test.ts index 678685103..948c016bb 100644 --- a/tests/harness/test262.test.ts +++ b/tests/harness/test262.test.ts @@ -189,6 +189,26 @@ assert.notSameValue(1, "1"); source: "assert.compareArray([1, 2], [1, 3]);", }, { name: "array signed zero mismatch", status: "fail", source: "assert.compareArray([0], [-0]);" }, + { + name: "bare compareArray returns true for matching contents", + status: "pass", + source: "assert(compareArray([1, NaN, -0], [1, NaN, -0]));", + }, + { + name: "bare compareArray returns false for a length or element mismatch", + status: "pass", + source: "assert(!compareArray([1], [1, 2])); assert(!compareArray([1, 2], [1, 3]));", + }, + { + name: "bare compareArray compares operands without a length as equal", + status: "pass", + source: "assert(compareArray(1, 2)); assert(compareArray({}, {}));", + }, + { + name: "bare compareArray does not throw on a mismatch", + status: "fail", + source: "assert(compareArray([1], [2]));", + }, { name: "assert requires true, not truthiness", status: "fail", source: "assert(1);" }, { name: "exact built-in exception", diff --git a/tests/test262/harness.ts b/tests/test262/harness.ts index dce1be99c..8a0f7bd1a 100644 --- a/tests/test262/harness.ts +++ b/tests/test262/harness.ts @@ -60,6 +60,28 @@ export function assert(value: unknown, message?: unknown): void { if (value !== true) throw new Test262Error(text(message, "Expected true")); } +function lengthOf(value: unknown): unknown { + return (value as { length?: unknown }).length; +} + +function elementAt(value: unknown, index: number): unknown { + return (value as Record)[index]; +} + +/** The bare `compareArray(a, b)` the upstream `harness/assert.js` defines + * alongside `assert`. It RETURNS a boolean (the throwing assertion is + * `assert.compareArray`), and it has no shape checks of its own: it reads + * `.length` off both operands, so a null operand throws TypeError there and + * here, and two operands without a length of their own compare equal. */ +export function compareArray(actual: unknown, expected: unknown): boolean { + const length = lengthOf(actual); + if (lengthOf(expected) !== length) return false; + for (let i = 0; typeof length === "number" && i < length; i++) { + if (!same(elementAt(expected, i), elementAt(actual, i))) return false; + } + return true; +} + export namespace assert { export function sameValue(actual: unknown, expected: unknown, message?: unknown): void { if (!same(actual, expected)) throw new Test262Error(text(message, "Expected SameValue")); diff --git a/tests/test262/support.mjs b/tests/test262/support.mjs index 52739f91a..ecb5d45c4 100644 --- a/tests/test262/support.mjs +++ b/tests/test262/support.mjs @@ -169,7 +169,11 @@ export function exclusion(source, meta, variant) { // Supported assertion calls; aliases, mutations, and reflection stay out. } else reason = "harness:assert-surface"; } else if (meta.includes.includes("compareArray.js") && ts.isIdentifier(node) && node.text === "compareArray" && + !(ts.isCallExpression(node.parent) && node.parent.expression === node) && !(ts.isPropertyAccessExpression(node.parent) && node.parent.name === node && ts.isIdentifier(node.parent.expression) && node.parent.expression.text === "assert")) { + // assert.js defines the bare compareArray; only its direct call form is + // hosted (a value reference or property read would need the upstream + // function object and its format expando). reason = "harness:compareArray-surface"; } else if (ts.isIdentifier(node) && node.text === "Test262Error") { if (!ts.isNewExpression(node.parent) || node.parent.expression !== node) reason = "harness:Test262Error-surface"; @@ -192,7 +196,9 @@ export function exclusion(source, meta, variant) { } export function prepare(source, asyncTest = false, marker = completion, variant = "strict", includes = []) { - const names = asyncTest ? "assert, Test262Error, $DONE" : "assert, Test262Error"; + const names = asyncTest + ? "assert, compareArray, Test262Error, $DONE" + : "assert, compareArray, Test262Error"; const end = asyncTest ? "" : `;console.log(${JSON.stringify(marker)});\n`; const helpers = (includes.includes("propertyHelper.js") ? `${propertyHelperSource}\n` : "") +