Skip to content

test(spanner): adopt runtime-agnostic test runner and Bun compatibility - #9460

Merged
danieljbruce merged 88 commits into
mainfrom
bun-runtime/1-test-runner-handwritten-libraries-4-1
Oct 1, 2026
Merged

danieljbruce merged 88 commits into
mainfrom
bun-runtime/1-test-runner-handwritten-libraries-4-1

Conversation

@danieljbruce

@danieljbruce danieljbruce commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Description

This PR migrates @google-cloud/spanner to the runtime-agnostic test runner (bin/run-test.cjs) and resolves test compatibility issues under the Bun runtime.

Impact

  • Multi-Runtime CI & Validation: Enables @google-cloud/spanner unit, system, and observability test suites to execute seamlessly under Bun while maintaining full fidelity and test coverage across Node.js (v22, v24, v26).
  • Zero Production Risk: Changes are strictly confined to test suites, test configurations, and tooling shims. Production library code in src/ is completely untouched.
  • Improved Test Isolation & Hygiene: Eliminates OpenTelemetry context leakage across test cases by properly tearing down context managers in afterEach, avoiding test pollution and flaky observability assertions.
  • Cross-Engine Assertion Stability: Normalizes JavaScriptCore (Bun) vs. V8 (Node.js) behavioral differences around array subclass strict equality and loose object assertions without monkeypatching built-in prototypes globally.

Key Changes

  1. Adopt run-test.cjs in @google-cloud/spanner:

    • Updated test, system-test, and observability-test scripts in handwritten/spanner/package.json to execute through bin/run-test.cjs.
  2. Observability Tests & OpenTelemetry Lifecycle:

    • Initialized contextManager using AsyncLocalStorageContextManager (with fallback to AsyncHooksContextManager) for proper trace context propagation under Bun.
    • Added explicit disableContextAndManager(contextManager) cleanup calls in afterEach across observability test suites (batch-transaction.ts, spanner.ts) to prevent context leakage across test runs.
    • Updated span sorting comparators in batch-transaction.ts, table.ts, and transaction.ts to return numeric differences (spanA.duration[0] - spanB.duration[0] or spanA.startTime[0] - spanB.startTime[0]) rather than boolean comparisons.
  3. Assertion & Compatibility Shim Enhancements (bin/proxyquire-bun-shim.cjs):

    • Enhanced assert.deepEqual with a safe looseDeepEqual fallback for primitive values, Date, RegExp, Buffer, Map, and Set to mirror Node.js loose equality semantics under Bun.
    • Implemented a scoped prototype workaround for assert.deepStrictEqual to address Bun <= 1.4 Array subclass constructor matching while preserving own properties.
    • Removed the global Array.prototype.sort override in favor of clean numeric comparators in test code.
  4. Session Pool Test Stability:

    • Addressed event-loop timing discrepancies in session-pool.ts to ensure consistent session acquisition and eviction assertions across runtimes.

Testing

  • Presubmit unit tests passing across all supported Node.js versions (Node 22, 24, 26).
  • Presubmit Bun unit tests passing (presubmit-bun/units).
  • Spanner system tests (spanner-system-tests & spanner-regular-sessions-system-tests) passing in Cloud Build.

quirogas and others added 30 commits September 21, 2026 21:26
Adds bin/run-test.cjs and bin/proxyquire-bun-shim.cjs to run Mocha tests across both Node.js and Bun without breaking Node coverage or parallelism.

When invoked under Node.js, bin/run-test.cjs delegates to c8 and Mocha with worker-thread parallelism enabled. When invoked under Bun (via bun --bun or JS_RUNTIME=bun), it skips c8, disables Mocha worker threads (--no-parallel), preloads the Bun proxyquire compatibility shim, and executes Mocha directly in-process so #!/usr/bin/env node shebangs do not silently switch execution back to Node.js.
…ogging-bunyan, logging-winston, pubsub, spanner, spanner-driver, and storage
…into bun-runtime/1-test-runner-handwritten-libraries

# Conflicts:
#	core/packages/gax/.mocharc.js
…into bun-runtime/1-test-runner-handwritten-libraries-2
…gging, logging-bunyan, logging-winston, pubsub, spanner, spanner-driver, and storage"

This reverts commit 8088cf4.
@danieljbruce danieljbruce changed the title Bun runtime/1 test runner handwritten libraries 4 1 test(spanner): adopt runtime-agnostic test runner and Bun compatibility Sep 30, 2026
…runner-handwritten-libraries-4-1

# Conflicts:
#	bin/proxyquire-bun-shim.cjs
@danieljbruce

Copy link
Copy Markdown
Contributor Author

/gemini review

@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 several compatibility fixes and enhancements for running tests under the Bun runtime, particularly focusing on Spanner and Storage. Key changes include adding a design document for an opt-in Bun fetch transport shim, updating proxyquire and assert shims in bin/proxyquire-bun-shim.cjs to handle loose deep equality and prototype issues in Bun, and adapting Spanner observability tests to correctly manage OpenTelemetry context managers and sort spans/events using high-resolution time arrays. Feedback is provided on optimizing array cloning in the assert.deepStrictEqual shim to preserve sparse array semantics and improve performance.

Comment thread bin/proxyquire-bun-shim.cjs Outdated
@danieljbruce

Copy link
Copy Markdown
Contributor Author

/gemini review

@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 several updates to the Spanner client tests to improve compatibility with Bun and Node.js environments, including updating error message assertions, fixing span and event sorting logic, and wrapping test executions with a custom runner. Additionally, it integrates OpenTelemetry tracing setup across multiple test suites. The review feedback highlights two key improvement opportunities: correcting a type mismatch and preventing resource leaks in spanner.ts by instantiating the tracer provider in a before block, and addressing a Mocha anti-pattern in session-pool.ts by moving tracer provider instantiation from the describe block to a before hook with proper cleanup.

Comment thread handwritten/spanner/observability-test/spanner.ts Outdated
Comment thread handwritten/spanner/test/session-pool.ts Outdated
@danieljbruce

Copy link
Copy Markdown
Contributor Author

/gemini review

@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 test compatibility improvements for Bun and Node.js, including wrapping test executions, updating error assertions, and fixing incorrect span sorting logic. It also integrates OpenTelemetry context managers into several test suites. Feedback highlights a critical issue in batch-transaction.ts where the global context manager is registered in beforeEach instead of once at the suite level, which can cause subsequent tests to run with a disabled context manager.

Comment thread handwritten/spanner/observability-test/batch-transaction.ts Outdated
Comment thread handwritten/spanner/observability-test/batch-transaction.ts
@danieljbruce

Copy link
Copy Markdown
Contributor Author

/gemini review

@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 several updates to improve compatibility with the Bun runtime, including handling Bun-specific error messages, prototype equality checks, and readonly property assignment errors. It also fixes several bugs in the test suite where span and event sorting logic was broken due to incorrect comparison operators, updating them to correctly compare high-resolution time arrays. Additionally, test execution scripts in package.json have been updated to use a custom test runner shim, and OpenTelemetry context managers and tracer providers are now properly registered and cleaned up across tests. No review comments were provided, so there is no feedback to address.

@danieljbruce
danieljbruce marked this pull request as ready for review September 30, 2026 19:42
@danieljbruce
danieljbruce requested review from a team as code owners September 30, 2026 19:42
@github-actions
github-actions Bot requested a review from shivanee-p September 30, 2026 19:53

@feywind feywind 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.

I think the most interesting thing here is the possibility of building up some runtime-agnostic utilities for testing and such. It would help if we want Deno later too.

const {SimpleSpanProcessor} = require('@opentelemetry/sdk-trace-base');
import {Session, Spanner} from '../src';
import * as bt from '../src/batch-transaction';
const {

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.

I'm sure there's a reason for it given that there's another above, but I'm curious why the mix of require and import?

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.

This follows the existing pattern across the observability-test suite (e.g., observability-test/spanner.ts), where the OpenTelemetry packages and ./helper were originally loaded via require() (in part because NodeTracerProvider is passed an untyped exporter property in its config object, which would fail strict TypeScript checks if imported via ES import, and @opentelemetry/sdk-trace-base was originally a transitive dependency). We kept require() here to stay consistent with the rest of the file.

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.

I also noticed when we did the Node upgrade last year that we had to play with require/import a lot to eliminate compiler errors so that might be another reason we see this discrepancy.

assert.throws(() => {
replaceProjectIdToken(frozenObj, projectId);
}, /Cannot assign to read only property/);
}, /Cannot assign to read only property|Attempted to assign to readonly property/);

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.

Not really commentary for this PR, but it might be cool to collect some of these differences in test-utils if we are supporting bun going forward anyway. Or maybe find other way to do these checks that don't rely on strings. (Again, different task...)

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.

Agreed — centralizing runtime-agnostic error assertions and environment checks in test-utils (or asserting on the error type rather than engine-specific message strings) will be much cleaner as we expand Bun (and potentially Deno) support. We'll track this as a follow-up task.

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.

Will be addressed in #9492

// Node 20+ relaxed this for Array subclasses with constructor = Array.
if (parseInt(process.versions.node.split('.')[0], 10) < 20) {
if (
parseInt(process.versions.node.split('.')[0], 10) < 20 ||

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.

Same for this kind of thing. We could have predicates in test-utils that do this sort of test.

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.

Agreed — adding runtime capability predicates or assertion helpers in test-utils so individual package tests don't need inline process.versions checks makes sense as a follow-up.

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.

Will be addressed in #9492

@danieljbruce
danieljbruce merged commit ea613fb into main Oct 1, 2026
46 checks passed
@danieljbruce
danieljbruce deleted the bun-runtime/1-test-runner-handwritten-libraries-4-1 branch October 1, 2026 13:53
danieljbruce added a commit that referenced this pull request Oct 1, 2026
Use assertArraySubclassStrictEqual, isReadOnlyPropertyError, and isNullOrUndefinedPropertyError from google-test-utils in Spanner unit tests to address PR #9460 review feedback.

[Generated-by: AI]
danieljbruce added a commit that referenced this pull request Oct 1, 2026
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]
gcf-merge-on-green Bot pushed a commit that referenced this pull request Oct 1, 2026
…ssertion helpers (#9492)

### 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](https://togithub.com/googleapis/google-cloud-node/pull/9460#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](https://togithub.com/googleapis/google-cloud-node/pull/9460#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](https://togithub.com/googleapis/google-cloud-node/pull/9460#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`.
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.

3 participants