diff --git a/bin/proxyquire-bun-shim.cjs b/bin/proxyquire-bun-shim.cjs index 3f3bd2e8963..0dd3d9ea235 100644 --- a/bin/proxyquire-bun-shim.cjs +++ b/bin/proxyquire-bun-shim.cjs @@ -222,7 +222,7 @@ if ( } catch { real = undefined; } - if (real && typeof real === 'object') { + if (real && (typeof real === 'object' || typeof real === 'function')) { for (const k of Object.keys(real)) { if (!(k in stub)) stub[k] = real[k]; } diff --git a/handwritten/spanner/observability-test/batch-transaction.ts b/handwritten/spanner/observability-test/batch-transaction.ts index 47c124d3922..f3738c67ece 100644 --- a/handwritten/spanner/observability-test/batch-transaction.ts +++ b/handwritten/spanner/observability-test/batch-transaction.ts @@ -19,7 +19,7 @@ import {util} from '@google-cloud/common'; import * as pfy from '@google-cloud/promisify'; import * as assert from 'assert'; -import {before, beforeEach, afterEach, describe, it} from 'mocha'; +import {before, after, beforeEach, afterEach, describe, it} from 'mocha'; import * as extend from 'extend'; import * as proxyquire from 'proxyquire'; import * as sinon from 'sinon'; @@ -32,6 +32,11 @@ const { const {SimpleSpanProcessor} = require('@opentelemetry/sdk-trace-base'); import {Session, Spanner} from '../src'; import * as bt from '../src/batch-transaction'; +const { + AsyncLocalStorageContextManager, + AsyncHooksContextManager, +} = require('@opentelemetry/context-async-hooks'); +const {setGlobalContextManager, disableContextAndManager} = require('./helper'); const fakePfy = extend({}, pfy, { promisifyAll(klass, options) { @@ -118,6 +123,17 @@ describe('BatchTransaction', () => { exporter: traceExporter, spanProcessors: [new SimpleSpanProcessor(traceExporter)], }); + provider.register(); + + const contextManager = new ( + AsyncLocalStorageContextManager || AsyncHooksContextManager + )(); + setGlobalContextManager(contextManager); + + after(async () => { + await provider.shutdown(); + disableContextAndManager(contextManager); + }); afterEach(() => { traceExporter.reset(); @@ -160,9 +176,11 @@ describe('BatchTransaction', () => { assert.strictEqual(spans.length, 2, 'Exactly 2 spans expected'); // Sort the spans by duration. - spans.sort((spanA, spanB) => { - spanA.duration < spanB.duration; - }); + spans.sort( + (spanA, spanB) => + spanA.duration[0] - spanB.duration[0] || + spanA.duration[1] - spanB.duration[1], + ); const actualSpanNames: string[] = []; spans.forEach(span => { @@ -224,9 +242,11 @@ describe('BatchTransaction', () => { assert.strictEqual(spans.length, 2, 'Exactly 2 spans expected'); // Sort the spans by duration. - spans.sort((spanA, spanB) => { - spanA.duration < spanB.duration; - }); + spans.sort( + (spanA, spanB) => + spanA.duration[0] - spanB.duration[0] || + spanA.duration[1] - spanB.duration[1], + ); const actualSpanNames: string[] = []; spans.forEach(span => { diff --git a/handwritten/spanner/observability-test/database.ts b/handwritten/spanner/observability-test/database.ts index 35737a1efb3..92eed3bd9ba 100644 --- a/handwritten/spanner/observability-test/database.ts +++ b/handwritten/spanner/observability-test/database.ts @@ -1229,7 +1229,10 @@ describe('Database', () => { assert.ok( errorMessage.includes( "Cannot read properties of null (reading 'proto')", - ) || errorMessage.includes("Cannot read property 'proto' of null"), + ) || + errorMessage.includes("Cannot read property 'proto' of null") || + (errorMessage.includes('null is not an object') && + errorMessage.includes('proto')), ); // We expect an exception to have been caught as well as a Session event. diff --git a/handwritten/spanner/observability-test/helper.ts b/handwritten/spanner/observability-test/helper.ts index 6ce1ba8b6a4..fc610a4f912 100644 --- a/handwritten/spanner/observability-test/helper.ts +++ b/handwritten/spanner/observability-test/helper.ts @@ -43,6 +43,9 @@ export const cacheSessionEvents = ['Cache hit: has usable multiplexed session']; * and referring to context causes type/value collision errors. */ export function setGlobalContextManager(manager: ContextManager) { + if (typeof manager.enable === 'function') { + manager.enable(); + } context.setGlobalContextManager(manager); } diff --git a/handwritten/spanner/observability-test/spanner.ts b/handwritten/spanner/observability-test/spanner.ts index a9bb614160f..8e50d6ef623 100644 --- a/handwritten/spanner/observability-test/spanner.ts +++ b/handwritten/spanner/observability-test/spanner.ts @@ -47,6 +47,7 @@ const { cacheSessionEvents, } = require('./helper'); const { + AsyncLocalStorageContextManager, AsyncHooksContextManager, } = require('@opentelemetry/context-async-hooks'); @@ -146,12 +147,10 @@ async function setup( describe('EndToEnd', async () => { const sandbox = sinon.createSandbox(); - const contextManager = new AsyncHooksContextManager(); + const contextManager = new ( + AsyncLocalStorageContextManager || AsyncHooksContextManager + )(); setGlobalContextManager(contextManager); - afterEach(async () => { - disableContextAndManager(contextManager); - }); - const traceExporter = new InMemorySpanExporter(); const sampler = new AlwaysOnSampler(); const tracerProvider = new NodeTracerProvider({ @@ -177,6 +176,7 @@ describe('EndToEnd', async () => { await spanner.close(); await server.tryShutdown(() => {}); sandbox.restore(); + disableContextAndManager(contextManager); }); afterEach(async () => { @@ -2033,9 +2033,25 @@ describe('End to end tracing headers', () => { let spannerMock: mock.MockSpanner; let observabilityOptions: typeof ObservabilityOptions; + let provider: InstanceType; + + before(() => { + const traceExporter = new InMemorySpanExporter(); + provider = new NodeTracerProvider({ + sampler: new AlwaysOnSampler(), + exporter: traceExporter, + spanProcessors: [new SimpleSpanProcessor(traceExporter)], + }); + }); + + after(async () => { + await provider.shutdown(); + }); + beforeEach(async () => { sandbox = sinon.createSandbox(); observabilityOptions = { + tracerProvider: provider, enableEndToEndTracing: true, }; diff --git a/handwritten/spanner/observability-test/table.ts b/handwritten/spanner/observability-test/table.ts index 26db0ff42b2..36257b34e87 100644 --- a/handwritten/spanner/observability-test/table.ts +++ b/handwritten/spanner/observability-test/table.ts @@ -117,9 +117,11 @@ describe('Table', () => { ); // Sort the spans by duration. - spans.sort((spanA, spanB) => { - spanA.duration < spanB.duration; - }); + spans.sort( + (spanA, spanB) => + spanA.duration[0] - spanB.duration[0] || + spanA.duration[1] - spanB.duration[1], + ); return spans; } diff --git a/handwritten/spanner/observability-test/transaction.ts b/handwritten/spanner/observability-test/transaction.ts index c89c1b0a925..126b52141b8 100644 --- a/handwritten/spanner/observability-test/transaction.ts +++ b/handwritten/spanner/observability-test/transaction.ts @@ -130,9 +130,11 @@ describe('Transaction', () => { const spans = traceExporter.getFinishedSpans(); // Sort the spans by startTime. - spans.sort((spanA, spanB) => { - spanA.startTime < spanB.startTime; - }); + spans.sort( + (spanA, spanB) => + spanA.startTime[0] - spanB.startTime[0] || + spanA.startTime[1] - spanB.startTime[1], + ); const spanNames: string[] = []; const eventNames: string[] = []; diff --git a/handwritten/spanner/package.json b/handwritten/spanner/package.json index 8f849dc627c..10116807548 100644 --- a/handwritten/spanner/package.json +++ b/handwritten/spanner/package.json @@ -34,10 +34,10 @@ "docs": "jsdoc -c .jsdoc.js", "predocs": "pnpm run compile", "lint": "gts check", - "system-test": "mocha build/system-test --timeout 1600000", - "observability-test": "mocha build/observability-test --timeout 1600000", - "cleanup": "mocha scripts/cleanup.js --timeout 30000", - "test": "mocha build/test build/test/common build/observability-test", + "system-test": "node ../../bin/run-test.cjs build/system-test --timeout 1600000", + "observability-test": "node ../../bin/run-test.cjs build/observability-test --timeout 1600000", + "cleanup": "node ../../bin/run-test.cjs scripts/cleanup.js --timeout 30000", + "test": "node ../../bin/run-test.cjs build/test build/test/common build/observability-test", "ycsb": "node ./benchmark/ycsb.js run -P ./benchmark/workloada -p table=usertable -p cloudspanner.instance=ycsb-instance -p operationcount=100 -p cloudspanner.database=ycsb", "fix": "gts fix", "clean": "gts clean", @@ -48,7 +48,7 @@ "preobservability-test": "pnpm run compile", "benchwrapper": "node bin/benchwrapper.js", "precompile": "gts clean", - "coverage": "c8 mocha build/test build/test/common && c8 report --check-coverage" + "coverage": "node ../../bin/run-test.cjs build/test build/test/common && c8 report --check-coverage" }, "dependencies": { "@babel/core": "7.27.7", diff --git a/handwritten/spanner/test/codec.ts b/handwritten/spanner/test/codec.ts index 43b3e5a2ac5..733aff5cbc2 100644 --- a/handwritten/spanner/test/codec.ts +++ b/handwritten/spanner/test/codec.ts @@ -990,7 +990,7 @@ describe('codec', () => { value: music.Genre.JAZZ, fullName: 'examples.spanner.music.Genre', }).toJSON(), - 1, + '1', ); }); }); diff --git a/handwritten/spanner/test/database.ts b/handwritten/spanner/test/database.ts index a9d04fa526f..a0fd4991b0d 100644 --- a/handwritten/spanner/test/database.ts +++ b/handwritten/spanner/test/database.ts @@ -975,7 +975,10 @@ describe('Database', () => { assert.ok( errorMessage.includes( "Cannot read properties of null (reading 'proto')", - ) || errorMessage.includes("Cannot read property 'proto' of null"), + ) || + errorMessage.includes("Cannot read property 'proto' of null") || + (errorMessage.includes('null is not an object') && + errorMessage.includes('proto')), ); done(); diff --git a/handwritten/spanner/test/helper.ts b/handwritten/spanner/test/helper.ts index 668df900db9..30d04e189f7 100644 --- a/handwritten/spanner/test/helper.ts +++ b/handwritten/spanner/test/helper.ts @@ -125,7 +125,7 @@ describe('helper', () => { assert.throws(() => { replaceProjectIdToken(frozenObj, projectId); - }, /Cannot assign to read only property/); + }, /Cannot assign to read only property|Attempted to assign to readonly property/); }); it('should replace more than one {{projectId}}', () => { diff --git a/handwritten/spanner/test/partial-result-stream.ts b/handwritten/spanner/test/partial-result-stream.ts index 032f4e1db3d..dc1938c2057 100644 --- a/handwritten/spanner/test/partial-result-stream.ts +++ b/handwritten/spanner/test/partial-result-stream.ts @@ -196,7 +196,10 @@ describe('PartialResultStream', () => { // Node 18's assert.deepStrictEqual strictly requires prototype equality, // which fails when comparing RowImpl (an Array subclass) with a plain Array literal. // 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 || + process.versions.bun + ) { assert.deepStrictEqual([...row], EXPECTED_ROW); } else { assert.deepStrictEqual(row, EXPECTED_ROW); @@ -260,7 +263,10 @@ describe('PartialResultStream', () => { // Node 18's assert.deepStrictEqual strictly requires prototype equality, // which fails when comparing RowImpl (an Array subclass) with a plain Array literal. // 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 || + process.versions.bun + ) { assert.deepStrictEqual([...row], EXPECTED_ROW); } else { assert.deepStrictEqual(row, EXPECTED_ROW); diff --git a/handwritten/spanner/test/session-pool.ts b/handwritten/spanner/test/session-pool.ts index d279da8699f..1418b170d50 100644 --- a/handwritten/spanner/test/session-pool.ts +++ b/handwritten/spanner/test/session-pool.ts @@ -15,7 +15,7 @@ */ import * as assert from 'assert'; -import {before, beforeEach, afterEach, describe, it} from 'mocha'; +import {after, before, beforeEach, afterEach, describe, it} from 'mocha'; import * as events from 'events'; import PQueue from 'p-queue'; import * as proxyquire from 'proxyquire'; @@ -29,6 +29,12 @@ import * as sp from '../src/session-pool'; import {Transaction} from '../src/transaction'; import {grpc} from 'google-gax'; const {startTrace} = require('../src/instrument'); +const { + AlwaysOnSampler, + NodeTracerProvider, + InMemorySpanExporter, +} = require('@opentelemetry/sdk-trace-node'); +const {SimpleSpanProcessor} = require('@opentelemetry/sdk-trace-base'); let pQueueOverride: typeof PQueue | null = null; @@ -613,7 +619,7 @@ describe('SessionPool', () => { sessionPool.options.acquireTimeout = 1; sessionPool._acquires.add = fn => { - return new Promise(r => setTimeout(r, 3)).then(fn); + return new Promise(resolve => setTimeout(resolve, 3)).then(fn); }; try { @@ -1374,6 +1380,22 @@ describe('SessionPool', () => { }); describe('trace annotations on active span', () => { + let traceExporter: InstanceType; + let provider: InstanceType; + + before(() => { + traceExporter = new InMemorySpanExporter(); + provider = new NodeTracerProvider({ + sampler: new AlwaysOnSampler(), + exporter: traceExporter, + spanProcessors: [new SimpleSpanProcessor(traceExporter)], + }); + }); + + after(async () => { + await provider.shutdown(); + }); + beforeEach(() => { sessionPool.isOpen = true; sessionPool._isValidSession = () => true; @@ -1381,49 +1403,54 @@ describe('SessionPool', () => { it('annotations when acquiring a session', done => { const topLevelSpanName = 'testSessionPool.acquire'; - startTrace(topLevelSpanName, {}, async span => { - const fakeSession = createSession(); - const now = Date.now(); - - const stub = sandbox - .stub(sessionPool, '_getSession') - .resolves(fakeSession); - const session = await sessionPool._acquire(); - const [startTime] = stub.getCall(0).args; - - assert(isAround(startTime, now)); - assert.strictEqual(session, fakeSession); - - await sessionPool._release(session); - span.end(); - - const events = span.events; - assert.strictEqual(!events, false, 'Events must be set'); - assert.strictEqual( - events.length > 0, - true, - 'Expecting at least 1 event', - ); - - // Sort the events by earliest time of occurence. - events.sort((evtA, evtB) => { - return evtA.time < evtB.time; - }); - - const gotEventNames: string[] = []; - events.forEach(event => { - gotEventNames.push(event.name); - }); - - const wantEventNames = ['Acquiring session', 'Acquired session']; - assert.deepEqual( - gotEventNames, - wantEventNames, - `Mismatched events\n\tGot: ${gotEventNames}\n\tWant: ${wantEventNames}`, - ); + startTrace( + topLevelSpanName, + {opts: {tracerProvider: provider}}, + async span => { + const fakeSession = createSession(); + const now = Date.now(); + + const stub = sandbox + .stub(sessionPool, '_getSession') + .resolves(fakeSession); + const session = await sessionPool._acquire(); + const [startTime] = stub.getCall(0).args; + + assert(isAround(startTime, now)); + assert.strictEqual(session, fakeSession); - done(); - }); + await sessionPool._release(session); + span.end(); + + const events = span.events; + assert.strictEqual(!events, false, 'Events must be set'); + assert.strictEqual( + events.length > 0, + true, + 'Expecting at least 1 event', + ); + + // Sort the events by earliest time of occurence. + events.sort( + (evtA, evtB) => + evtA.time[0] - evtB.time[0] || evtA.time[1] - evtB.time[1], + ); + + const gotEventNames: string[] = []; + events.forEach(event => { + gotEventNames.push(event.name); + }); + + const wantEventNames = ['Acquiring session', 'Acquired session']; + assert.deepEqual( + gotEventNames, + wantEventNames, + `Mismatched events\n\tGot: ${gotEventNames}\n\tWant: ${wantEventNames}`, + ); + + done(); + }, + ); }); }); });