Skip to content

feat(test-utils): add runtime detection predicates and cross-engine assertion helpers - #9492

Merged
gcf-merge-on-green[bot] merged 4 commits into
mainfrom
feat/test-utils-runtime-helpers
Oct 1, 2026
Merged

gcf-merge-on-green[bot] merged 4 commits into
mainfrom
feat/test-utils-runtime-helpers

Conversation

@danieljbruce

@danieljbruce danieljbruce commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Follows up on review feedback from @feywind on #9460 by adding the shared runtime detection predicates and cross-engine assertion helpers to google-test-utils (core/test-utils) and adopting them in the @google-cloud/spanner (handwritten/spanner) unit tests she commented on:

  • Runtime environment predicates (isNode, isBun, getNodeMajorVersion, RuntimeVersions): Centralizes detection of Node.js vs. Bun so tests do not need ad-hoc process.versions parsing (discussion_r4149070743).
  • Array subclass deep-strict equality helpers (requiresStrictArrayPrototypeEquality, assertArraySubclassStrictEqual): Centralizes the workaround for Node.js < 20 and Bun where assert.deepStrictEqual strictly enforces prototype equality when comparing an Array subclass (such as Spanner's RowImpl) against a plain Array literal (discussion_r4149239787).
  • Cross-engine read-only property TypeError matcher (READONLY_PROPERTY_ERROR_REGEX, isReadOnlyPropertyError): Centralizes matching for read-only property assignment errors across V8 (Node.js) and JavaScriptCore (Bun) (discussion_r4149233766).
  • Spanner test adoption (handwritten/spanner):
    • handwritten/spanner/test/helper.ts: Replaces the inline V8/JSC read-only property regex with isReadOnlyPropertyError(err, 'name').
    • handwritten/spanner/test/partial-result-stream.ts: Replaces the two inline process.versions checks with assertArraySubclassStrictEqual(row, EXPECTED_ROW).

Testing

  • Added unit tests in core/test-utils/test/runtime.test.ts covering all exported functions (100% statement/function/line coverage).
  • Verified pnpm test and pnpm run lint in core/test-utils.
  • Verified updated unit tests (helper, partial-result-stream) and ESLint/TypeScript checks in handwritten/spanner.

…ssertion helpers

Adds runtime environment predicates (isNode, isBun, isDeno, getRuntime, getNodeMajorVersion), Array subclass equality helpers (requiresStrictArrayPrototypeEquality, assertArraySubclassStrictEqual), and cross-engine TypeError predicates (isReadOnlyPropertyError, isNullOrUndefinedPropertyError) to google-test-utils.

[Generated-by: AI]
@danieljbruce
danieljbruce requested a review from a team as a code owner October 1, 2026 14:08
@github-actions
github-actions Bot requested a review from shivanee-p October 1, 2026 14:09

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces runtime detection and assertion utilities in core/test-utils to handle differences across Node.js, Bun, and Deno environments, including utilities for checking read-only property errors and asserting array subclass equality. The review feedback highlights that the current implementation of assertArraySubclassStrictEqual only performs a shallow copy, which fails to normalize nested array subclasses (such as arrays of rows) on runtimes requiring strict prototype equality. It is recommended to recursively normalize array subclasses to plain arrays and add corresponding test coverage for nested structures.

Comment on lines +145 to +162
/**
* Asserts `deepStrictEqual` between an `Array` (or `Array` subclass instance such as
* Spanner's `RowImpl`) and an expected array, normalizing the `actual` array via
* spread (`[...actual]`) only on runtimes that enforce strict prototype equality
* for `Array` subclasses.
*/
export function assertArraySubclassStrictEqual<T>(
actual: ReadonlyArray<T>,
expected: ReadonlyArray<T>,
message?: string | Error,
versions?: RuntimeVersions,
): void {
if (requiresStrictArrayPrototypeEquality(versions)) {
assert.deepStrictEqual([...actual], expected, message);
} else {
assert.deepStrictEqual(actual, expected, message);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of assertArraySubclassStrictEqual uses a shallow copy ([...actual]) to normalize the array. However, if the array contains nested array subclasses (for example, an array of Spanner RowImpl instances like [row1, row2]), the shallow copy will not normalize the nested elements. On runtimes that require strict prototype equality (such as Node.js < 20 and Bun), assert.deepStrictEqual will still fail when comparing these nested subclass instances against plain arrays.

To support nested array subclasses and arrays of rows, we should recursively normalize any array subclasses to plain arrays.

function toPlainArray(val: unknown): unknown {
  return Array.isArray(val) ? Array.from(val, toPlainArray) : val;
}

/**
 * Asserts `deepStrictEqual` between an `Array` (or `Array` subclass instance such as
 * Spanner's `RowImpl`) and an expected array, recursively normalizing any `Array`
 * subclasses to plain arrays only on runtimes that enforce strict prototype equality.
 */
export function assertArraySubclassStrictEqual<T>(
  actual: ReadonlyArray<T>,
  expected: ReadonlyArray<T>,
  message?: string | Error,
  versions?: RuntimeVersions,
): void {
  if (requiresStrictArrayPrototypeEquality(versions)) {
    assert.deepStrictEqual(toPlainArray(actual), toPlainArray(expected), message);
  } else {
    assert.deepStrictEqual(actual, expected, message);
  }
}
References
  1. Prefer using Node version-based branching for temporary test workarounds (such as handling prototype equality differences in assert.deepStrictEqual) instead of permanently simplifying or weakening the assertion (e.g., by spreading an array). This keeps strict assertions active for newer Node versions and makes the workaround easy to identify and remove when older Node versions are deprecated.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in c453644 to recursively normalize nested Array subclasses via toPlainArray on runtimes requiring strict prototype equality, and guarded message !== undefined before passing the 3rd argument to assert.deepStrictEqual for Node.js 26 compatibility.

Comment on lines +129 to +147
it('compares Array subclass instances across runtimes using assertArraySubclassStrictEqual', () => {
const row = new CustomRow('a', 'b');
expect(() =>
assertArraySubclassStrictEqual(row, ['a', 'b'], undefined, {
node: '18.20.0',
}),
).not.toThrow();
expect(() =>
assertArraySubclassStrictEqual(row, ['a', 'b'], undefined, {
node: '22.14.0',
}),
).not.toThrow();
expect(() =>
assertArraySubclassStrictEqual(row, ['a', 'c'], undefined, {
node: '22.6.0',
bun: '1.2.4',
}),
).toThrow(assert.AssertionError);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Add a test case to verify that assertArraySubclassStrictEqual correctly handles nested array subclasses (such as an array of rows) on runtimes requiring strict prototype equality.

    it('compares Array subclass instances across runtimes using assertArraySubclassStrictEqual', () => {
      const row = new CustomRow('a', 'b');
      expect(() =>
        assertArraySubclassStrictEqual(row, ['a', 'b'], undefined, {
          node: '18.20.0',
        }),
      ).not.toThrow();
      expect(() =>
        assertArraySubclassStrictEqual(row, ['a', 'b'], undefined, {
          node: '22.14.0',
        }),
      ).not.toThrow();
      expect(() =>
        assertArraySubclassStrictEqual(row, ['a', 'c'], undefined, {
          node: '22.6.0',
          bun: '1.2.4',
        }),
      ).toThrow(assert.AssertionError);

      // Test nested array subclasses (e.g., array of rows)
      const nestedRows = [new CustomRow('a', 'b')];
      expect(() =>
        assertArraySubclassStrictEqual(nestedRows, [['a', 'b']], undefined, {
          node: '18.20.0',
        }),
      ).not.toThrow();
    });

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added test coverage for nested Array subclasses ([new CustomRow('a', 'b')]) and custom assertion messages in c453644.

Use assertArraySubclassStrictEqual, isReadOnlyPropertyError, and isNullOrUndefinedPropertyError from google-test-utils in Spanner unit tests to address PR #9460 review feedback.

[Generated-by: AI]
@danieljbruce
danieljbruce requested a review from a team as a code owner October 1, 2026 14:33
…e workspace deps in cloudbuild

- Recursively normalize nested Array subclasses in assertArraySubclassStrictEqual via toPlainArray and avoid passing undefined message to assert.deepStrictEqual (fixes Node 26 TypeError).
- Add unit tests for nested Array subclasses and custom assertion messages.
- Update Spanner cloudbuild.yaml and cloudbuild-regular-sessions.yaml pnpm filter to ...{./handwritten/spanner}... so upstream workspace dependencies like google-test-utils are built.

[Generated-by: AI]
Remove unused runtime utilities (isDeno, getRuntime, RuntimeEnvironment, isNullOrUndefinedPropertyError) and revert handwritten/spanner/test/database.ts so the PR only includes the helpers and Spanner test locations referenced by @feywind on PR #9460.

[Generated-by: AI]
@danieljbruce
danieljbruce requested a review from feywind October 1, 2026 18:04
@danieljbruce danieljbruce added the automerge Merge the pull request once unit tests and other checks pass. label Oct 1, 2026
@gcf-merge-on-green
gcf-merge-on-green Bot merged commit f971bb5 into main Oct 1, 2026
46 checks passed
@gcf-merge-on-green gcf-merge-on-green Bot removed the automerge Merge the pull request once unit tests and other checks pass. label Oct 1, 2026
@gcf-merge-on-green
gcf-merge-on-green Bot deleted the feat/test-utils-runtime-helpers branch October 1, 2026 18:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants