From f85e89dea5e74f78d30babdc861ed26ab95c8b3f Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Mon, 3 Aug 2026 19:11:33 +0000 Subject: [PATCH 01/15] fix(core): discovery url handling, error code review helper, and pack-n-play test timeouts --- .../test/fixtures/esm-package/test/test.js | 3 ++- .../test/fixtures/leaky/test/test.ts | 3 ++- .../test/fixtures/pass/test/test.ts | 3 ++- core/dev-packages/pack-n-play/test/test.ts | 5 +++-- core/packages/gcp-metadata/src/index.ts | 22 ++++++++++++++----- .../nodejs-googleapis-common/src/discovery.ts | 12 +++++++--- .../test/test.discovery.ts | 4 ++-- 7 files changed, 37 insertions(+), 15 deletions(-) diff --git a/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js b/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js index 037babedf227..2adbf53d289c 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js +++ b/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js @@ -16,7 +16,8 @@ import {packNTest} from 'pack-n-play'; import * as assert from 'assert'; import {describe, it} from 'mocha'; -describe('ESM package', () => { +describe('ESM package', function () { + this.timeout(120000); it('should support esm property', () => packNTest({ sample: { diff --git a/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts b/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts index 3a9dc7f39b39..9c56bc9c0741 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts +++ b/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts @@ -16,7 +16,8 @@ import {packNTest} from 'pack-n-play'; import * as assert from 'assert'; import {describe, it} from 'mocha'; -describe('leaky tests', () => { +describe('leaky tests', function () { + this.timeout(120000); it('should fail packing n testing', async () => { await assert.rejects( packNTest({ diff --git a/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts b/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts index 2dd02fb27cff..50d0d07d620d 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts +++ b/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts @@ -15,7 +15,8 @@ import {packNTest} from 'pack-n-play'; import {describe, it} from 'mocha'; -describe('passing tests', () => { +describe('passing tests', function () { + this.timeout(120000); it('should pass the test', async () => { await packNTest({ sample: { diff --git a/core/dev-packages/pack-n-play/test/test.ts b/core/dev-packages/pack-n-play/test/test.ts index da954b94ba08..06aef544b4e7 100644 --- a/core/dev-packages/pack-n-play/test/test.ts +++ b/core/dev-packages/pack-n-play/test/test.ts @@ -18,7 +18,8 @@ import execa = require('execa'); import {describe, it} from 'mocha'; describe('pack-n-play', () => { - it('should run tests', async () => { + it('should run tests', async function () { + this.timeout(600000); // 10 minutes const fixturesPath = path.resolve('./test/fixtures'); const dirs = fs .readdirSync(fixturesPath) @@ -29,7 +30,7 @@ describe('pack-n-play', () => { stdio: 'inherit', cwd: dir, }; - await execa('npm', ['install'], opts); + await execa('npm', ['install', '--no-audit', '--no-fund'], opts); await execa('npm', ['link', '../../../'], opts); await execa('npm', ['test'], opts); } diff --git a/core/packages/gcp-metadata/src/index.ts b/core/packages/gcp-metadata/src/index.ts index 723fcf1d3f1e..c591ada49ef8 100644 --- a/core/packages/gcp-metadata/src/index.ts +++ b/core/packages/gcp-metadata/src/index.ts @@ -383,12 +383,24 @@ export async function isAvailable() { if (err.response && err.response.status === 404) { return false; } else { + const errObj = e as any; + const getErrorCode = (err: any): string => { + let target = err; + if ( + target instanceof Error && + target.cause && + !('code' in target) && + target.name !== 'AggregateError' + ) { + target = target.cause; + } + return target?.code ? target.code.toString() : 'UNKNOWN'; + }; + const codes = - e instanceof Error && e.name === 'AggregateError' - ? (e as any).errors.map((error: any) => - error.code ? error.code.toString() : 'UNKNOWN', - ) - : [err.code ? err.code.toString() : 'UNKNOWN']; + errObj instanceof Error && errObj.name === 'AggregateError' + ? (errObj as any).errors.map(getErrorCode) + : [getErrorCode(errObj)]; const isExpected = codes.every((code: string) => [ diff --git a/core/packages/nodejs-googleapis-common/src/discovery.ts b/core/packages/nodejs-googleapis-common/src/discovery.ts index c00c663bed28..2a665dba0291 100644 --- a/core/packages/nodejs-googleapis-common/src/discovery.ts +++ b/core/packages/nodejs-googleapis-common/src/discovery.ts @@ -13,7 +13,6 @@ import * as fs from 'fs'; import {Gaxios} from 'gaxios'; -import resolve = require('url'); import * as util from 'util'; import {GlobalOptions, ServiceOptions, APIRequestParams} from './api'; @@ -136,8 +135,15 @@ export class Discovery { apiDiscoveryUrl: string | {url?: string}, ): Promise { if (typeof apiDiscoveryUrl === 'string') { - const parts = resolve.parse(apiDiscoveryUrl); - if (apiDiscoveryUrl && !parts.protocol) { + let isUrl = false; + try { + const parsed = new URL(apiDiscoveryUrl); + isUrl = parsed.protocol === 'http:' || parsed.protocol === 'https:'; + } catch (e) { + // Not a valid URL + } + + if (apiDiscoveryUrl && !isUrl) { this.log('Reading from file ' + apiDiscoveryUrl); const file = await readFile(apiDiscoveryUrl, {encoding: 'utf8'}); return this.makeEndpoint(JSON.parse(file)); diff --git a/core/packages/nodejs-googleapis-common/test/test.discovery.ts b/core/packages/nodejs-googleapis-common/test/test.discovery.ts index 0e656c5fb6b1..6745982a218f 100644 --- a/core/packages/nodejs-googleapis-common/test/test.discovery.ts +++ b/core/packages/nodejs-googleapis-common/test/test.discovery.ts @@ -24,7 +24,7 @@ describe(__filename, () => { nock.cleanAll(); }); it('should discover an API', async () => { - const discoUrl = 'http://test.local'; + const discoUrl = 'http://test.local:80'; const scope = nock(discoUrl) .get('/') .replyWithFile(200, './test/fixtures/compute-v1.json', { @@ -37,7 +37,7 @@ describe(__filename, () => { scope.done(); }); it('should discover an API through second weird path', async () => { - const discoUrl = 'http://test.local'; + const discoUrl = 'http://test.local:80'; const scope = nock(discoUrl) .get('/') .replyWithFile(200, './test/fixtures/compute-v1.json', { From 639081b2a9dcd5ceb098a633927c4b4b60c86d6f Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Mon, 3 Aug 2026 19:42:11 +0000 Subject: [PATCH 02/15] fix(gcp-metadata): use recursive getErrorCodes helper for nested wrapper error causes --- core/packages/gcp-metadata/src/index.ts | 26 ++++++++++++------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/core/packages/gcp-metadata/src/index.ts b/core/packages/gcp-metadata/src/index.ts index c591ada49ef8..3d98d38f9b03 100644 --- a/core/packages/gcp-metadata/src/index.ts +++ b/core/packages/gcp-metadata/src/index.ts @@ -384,23 +384,21 @@ export async function isAvailable() { return false; } else { const errObj = e as any; - const getErrorCode = (err: any): string => { - let target = err; - if ( - target instanceof Error && - target.cause && - !('code' in target) && - target.name !== 'AggregateError' - ) { - target = target.cause; + const getErrorCodes = (err: any): string[] => { + if (!err) return ['UNKNOWN']; + if (err.name === 'AggregateError' && Array.isArray(err.errors)) { + return err.errors.flatMap(getErrorCodes); } - return target?.code ? target.code.toString() : 'UNKNOWN'; + if (err.code) { + return [err.code.toString()]; + } + if (err.cause) { + return getErrorCodes(err.cause); + } + return ['UNKNOWN']; }; - const codes = - errObj instanceof Error && errObj.name === 'AggregateError' - ? (errObj as any).errors.map(getErrorCode) - : [getErrorCode(errObj)]; + const codes = getErrorCodes(errObj); const isExpected = codes.every((code: string) => [ From 4bc3be36f597d493f15b907a29251cc54922c38b Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Mon, 3 Aug 2026 20:08:40 +0000 Subject: [PATCH 03/15] chore(core): remove pack-n-play timeout fixes (moved to #9021) --- .../pack-n-play/test/fixtures/esm-package/test/test.js | 3 +-- .../pack-n-play/test/fixtures/leaky/test/test.ts | 3 +-- .../dev-packages/pack-n-play/test/fixtures/pass/test/test.ts | 3 +-- core/dev-packages/pack-n-play/test/test.ts | 5 ++--- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js b/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js index 2adbf53d289c..037babedf227 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js +++ b/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js @@ -16,8 +16,7 @@ import {packNTest} from 'pack-n-play'; import * as assert from 'assert'; import {describe, it} from 'mocha'; -describe('ESM package', function () { - this.timeout(120000); +describe('ESM package', () => { it('should support esm property', () => packNTest({ sample: { diff --git a/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts b/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts index 9c56bc9c0741..3a9dc7f39b39 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts +++ b/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts @@ -16,8 +16,7 @@ import {packNTest} from 'pack-n-play'; import * as assert from 'assert'; import {describe, it} from 'mocha'; -describe('leaky tests', function () { - this.timeout(120000); +describe('leaky tests', () => { it('should fail packing n testing', async () => { await assert.rejects( packNTest({ diff --git a/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts b/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts index 50d0d07d620d..2dd02fb27cff 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts +++ b/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts @@ -15,8 +15,7 @@ import {packNTest} from 'pack-n-play'; import {describe, it} from 'mocha'; -describe('passing tests', function () { - this.timeout(120000); +describe('passing tests', () => { it('should pass the test', async () => { await packNTest({ sample: { diff --git a/core/dev-packages/pack-n-play/test/test.ts b/core/dev-packages/pack-n-play/test/test.ts index 06aef544b4e7..da954b94ba08 100644 --- a/core/dev-packages/pack-n-play/test/test.ts +++ b/core/dev-packages/pack-n-play/test/test.ts @@ -18,8 +18,7 @@ import execa = require('execa'); import {describe, it} from 'mocha'; describe('pack-n-play', () => { - it('should run tests', async function () { - this.timeout(600000); // 10 minutes + it('should run tests', async () => { const fixturesPath = path.resolve('./test/fixtures'); const dirs = fs .readdirSync(fixturesPath) @@ -30,7 +29,7 @@ describe('pack-n-play', () => { stdio: 'inherit', cwd: dir, }; - await execa('npm', ['install', '--no-audit', '--no-fund'], opts); + await execa('npm', ['install'], opts); await execa('npm', ['link', '../../../'], opts); await execa('npm', ['test'], opts); } From ef76bc01bb38746c27b998ea7476f91c7d6ab0fc Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Mon, 3 Aug 2026 20:17:02 +0000 Subject: [PATCH 04/15] chore(core): add pack-n-play timeout fixes back to core PR --- .../pack-n-play/test/fixtures/esm-package/test/test.js | 3 ++- .../pack-n-play/test/fixtures/leaky/test/test.ts | 3 ++- .../dev-packages/pack-n-play/test/fixtures/pass/test/test.ts | 3 ++- core/dev-packages/pack-n-play/test/test.ts | 5 +++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js b/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js index 037babedf227..2adbf53d289c 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js +++ b/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js @@ -16,7 +16,8 @@ import {packNTest} from 'pack-n-play'; import * as assert from 'assert'; import {describe, it} from 'mocha'; -describe('ESM package', () => { +describe('ESM package', function () { + this.timeout(120000); it('should support esm property', () => packNTest({ sample: { diff --git a/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts b/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts index 3a9dc7f39b39..9c56bc9c0741 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts +++ b/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts @@ -16,7 +16,8 @@ import {packNTest} from 'pack-n-play'; import * as assert from 'assert'; import {describe, it} from 'mocha'; -describe('leaky tests', () => { +describe('leaky tests', function () { + this.timeout(120000); it('should fail packing n testing', async () => { await assert.rejects( packNTest({ diff --git a/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts b/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts index 2dd02fb27cff..50d0d07d620d 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts +++ b/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts @@ -15,7 +15,8 @@ import {packNTest} from 'pack-n-play'; import {describe, it} from 'mocha'; -describe('passing tests', () => { +describe('passing tests', function () { + this.timeout(120000); it('should pass the test', async () => { await packNTest({ sample: { diff --git a/core/dev-packages/pack-n-play/test/test.ts b/core/dev-packages/pack-n-play/test/test.ts index da954b94ba08..06aef544b4e7 100644 --- a/core/dev-packages/pack-n-play/test/test.ts +++ b/core/dev-packages/pack-n-play/test/test.ts @@ -18,7 +18,8 @@ import execa = require('execa'); import {describe, it} from 'mocha'; describe('pack-n-play', () => { - it('should run tests', async () => { + it('should run tests', async function () { + this.timeout(600000); // 10 minutes const fixturesPath = path.resolve('./test/fixtures'); const dirs = fs .readdirSync(fixturesPath) @@ -29,7 +30,7 @@ describe('pack-n-play', () => { stdio: 'inherit', cwd: dir, }; - await execa('npm', ['install'], opts); + await execa('npm', ['install', '--no-audit', '--no-fund'], opts); await execa('npm', ['link', '../../../'], opts); await execa('npm', ['test'], opts); } From ac1f4781c303d05946bba244f9dc8c18d07339bf Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Mon, 3 Aug 2026 21:07:52 +0000 Subject: [PATCH 05/15] add tests for error.cause and AggregateError --- core/packages/gcp-metadata/test/index.test.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/core/packages/gcp-metadata/test/index.test.ts b/core/packages/gcp-metadata/test/index.test.ts index 83517852b622..f88a018efffa 100644 --- a/core/packages/gcp-metadata/test/index.test.ts +++ b/core/packages/gcp-metadata/test/index.test.ts @@ -493,6 +493,45 @@ describe('unit test', () => { }); }); + it('should fail on isAvailable if ENOTFOUND is wrapped in error.cause', async () => { + const secondary = secondaryHostRequest(500, 'ENOTFOUND'); + const innerErr = Object.assign(new Error('ENOTFOUND'), {code: 'ENOTFOUND'}); + const wrapperErr = Object.assign(new Error('Wrapper error'), { + cause: innerErr, + }); + const primary = nock(HOST) + .get(`${PATH}/${TYPE}`) + .replyWithError(wrapperErr); + const isGCE = await gcp.isAvailable(); + await secondary; + primary.done(); + assert.strictEqual(false, isGCE); + }); + + it('should fail on isAvailable if ENOTFOUND is wrapped inside an AggregateError or nested cause', async () => { + const secondary = secondaryHostRequest(500, 'ENOTFOUND'); + const innerErr1 = Object.assign(new Error('ENOTFOUND'), { + code: 'ENOTFOUND', + }); + const innerErr2 = Object.assign(new Error('EHOSTUNREACH'), { + code: 'EHOSTUNREACH', + }); + const wrapperErr = Object.assign(new Error('Wrapper error'), { + cause: innerErr1, + }); + const aggregateErr = new AggregateError( + [wrapperErr, innerErr2], + 'Aggregate error', + ); + const primary = nock(HOST) + .get(`${PATH}/${TYPE}`) + .replyWithError(aggregateErr); + const isGCE = await gcp.isAvailable(); + await secondary; + primary.done(); + assert.strictEqual(false, isGCE); + }); + it('should return first successful response', async () => { const secondary = secondaryHostRequest(500); const primary = nock(HOST).get(`${PATH}/${TYPE}`).reply(404); From 66743aef33eb6b6f6b68edb6db646ec55c07aa8c Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 4 Aug 2026 17:05:03 +0000 Subject: [PATCH 06/15] feat(ci): add matrix sharding for unit test performance chore(ci): reduce unit-test output due to log truncation chore: run all ci tests --- .../calculate-shard-matrix/action.yaml | 22 +++ .../actions/check-shard-status/action.yaml | 31 ++++ .github/actions/run-unit-tests/action.yaml | 33 +++++ .github/workflows/presubmit.yaml | 53 +++++-- .github/workflows/windows-presubmit.yaml | 50 +++++-- ci/run_conditional_tests.sh | 133 +++++++++++++----- ci/run_single_test.sh | 6 +- 7 files changed, 263 insertions(+), 65 deletions(-) create mode 100644 .github/actions/calculate-shard-matrix/action.yaml create mode 100644 .github/actions/check-shard-status/action.yaml create mode 100644 .github/actions/run-unit-tests/action.yaml diff --git a/.github/actions/calculate-shard-matrix/action.yaml b/.github/actions/calculate-shard-matrix/action.yaml new file mode 100644 index 000000000000..660e725ee84f --- /dev/null +++ b/.github/actions/calculate-shard-matrix/action.yaml @@ -0,0 +1,22 @@ +name: "Calculate Shard Matrix" +description: "Calculates the dynamic test shard matrix and shard total" +outputs: + shard_matrix: + description: "JSON array of shard indices" + value: ${{ steps.set-matrix.outputs.shard_matrix }} + shard_total: + description: "Total number of shards" + value: ${{ steps.set-matrix.outputs.shard_total }} +runs: + using: "composite" + steps: + - name: Calculate Matrix + id: set-matrix + shell: bash + env: + DRY_RUN_SHARDS: true + BUILD_TYPE: presubmit + TEST_TYPE: units + GIT_DIFF_ARG: "HEAD^1" + run: | + bash ci/run_conditional_tests.sh --strict diff --git a/.github/actions/check-shard-status/action.yaml b/.github/actions/check-shard-status/action.yaml new file mode 100644 index 000000000000..37f57a2992ae --- /dev/null +++ b/.github/actions/check-shard-status/action.yaml @@ -0,0 +1,31 @@ +name: "Check Shard Status" +description: "Checks if all unit test shards passed for a given Node version" +inputs: + node-version: + required: true + description: "Node version to check" +runs: + using: "composite" + steps: + - name: Check shard status + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + with: + script: | + const { data: { jobs } } = await github.rest.actions.listJobsForWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.runId, + }); + const nodeVersion = '${{ inputs.node-version }}'; + const shardJobs = jobs.filter(j => j.name.includes(`units (Node ${nodeVersion},`)); + if (shardJobs.length === 0) { + core.setFailed(`No shard jobs found for Node ${nodeVersion}`); + return; + } + const failed = shardJobs.filter(j => j.conclusion === 'failure' || j.conclusion === 'cancelled'); + if (failed.length > 0) { + const failedLinks = failed.map(j => `- ${j.name}: ${j.html_url}`).join('\n'); + core.setFailed(`${failed.length} shards failed for Node ${nodeVersion}:\n${failedLinks}`); + } else { + core.info(`All shards passed for Node ${nodeVersion}`); + } diff --git a/.github/actions/run-unit-tests/action.yaml b/.github/actions/run-unit-tests/action.yaml new file mode 100644 index 000000000000..69bf22693bdc --- /dev/null +++ b/.github/actions/run-unit-tests/action.yaml @@ -0,0 +1,33 @@ +name: "Run Unit Test Shard" +description: "Sets up Node.js, pnpm, and runs a unit test shard" +inputs: + node-version: + required: true + description: "Node.js version to test" + shard-total: + required: true + description: "Total number of shards" + shard-index: + required: true + description: "0-based shard index" +runs: + using: "composite" + steps: + - name: Use Node.js ${{ inputs.node-version }} + uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + with: + node-version: ${{ inputs.node-version }} + - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 + with: + version: ^10.0.0 + - run: node --version + shell: bash + - name: Run unit tests + shell: bash + run: bash ci/run_conditional_tests.sh --strict + env: + BUILD_TYPE: presubmit + TEST_TYPE: units + SHARD_TOTAL: ${{ inputs.shard-total }} + SHARD_INDEX: ${{ inputs.shard-index }} + GIT_DIFF_ARG: HEAD^1 diff --git a/.github/workflows/presubmit.yaml b/.github/workflows/presubmit.yaml index c5de30eba54e..902233f76caa 100644 --- a/.github/workflows/presubmit.yaml +++ b/.github/workflows/presubmit.yaml @@ -5,29 +5,37 @@ on: pull_request: name: presubmit jobs: + setup: + runs-on: ubuntu-latest + outputs: + shard-matrix: ${{ steps.set-matrix.outputs.shard_matrix }} + shard-total: ${{ steps.set-matrix.outputs.shard_total }} + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + fetch-depth: 2 + persist-credentials: false + - id: set-matrix + uses: ./.github/actions/calculate-shard-matrix units: + needs: setup + name: units (Node ${{ matrix.node-version }}, Shard ${{ matrix.shard-index }}) runs-on: ubuntu-latest strategy: + fail-fast: false matrix: node-version: [22, 24, 26] + shard-index: ${{ fromJSON(needs.setup.outputs.shard-matrix) }} steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 with: - fetch-depth: 300 + fetch-depth: 2 persist-credentials: false - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + - uses: ./.github/actions/run-unit-tests with: node-version: ${{ matrix.node-version }} - - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 - with: - version: ^10.0.0 - - run: node --version - - run: ci/run_conditional_tests.sh - name: Run unit tests - env: - BUILD_TYPE: presubmit - TEST_TYPE: units + shard-total: ${{ needs.setup.outputs.shard-total }} + shard-index: ${{ matrix.shard-index }} lint: runs-on: ubuntu-latest continue-on-error: true @@ -42,6 +50,25 @@ jobs: node-version: 24 - run: npm install - run: node ./bin/linter.mjs --strict - name: Run monorepo linter env: GIT_DIFF_ARG: "HEAD^1" + + # Dummy jobs to satisfy branch protection requirements for each node version + units-status: + name: units (${{ matrix.node-version }}) + needs: units + runs-on: ubuntu-latest + if: always() + strategy: + fail-fast: false + matrix: + node-version: [22, 24, 26] + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + persist-credentials: false + - name: Check shard status + uses: ./.github/actions/check-shard-status + with: + node-version: ${{ matrix.node-version }} +>>>>>>> 657c33a5f3 (feat(ci): run unit-tests in parallel to speed them up) diff --git a/.github/workflows/windows-presubmit.yaml b/.github/workflows/windows-presubmit.yaml index 31105cfa46de..afd68aee344a 100644 --- a/.github/workflows/windows-presubmit.yaml +++ b/.github/workflows/windows-presubmit.yaml @@ -5,27 +5,53 @@ on: pull_request: name: presubmit-windows jobs: + setup: + runs-on: ubuntu-latest + outputs: + shard-matrix: ${{ steps.set-matrix.outputs.shard_matrix }} + shard-total: ${{ steps.set-matrix.outputs.shard_total }} + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 + with: + fetch-depth: 2 + persist-credentials: false + - id: set-matrix + uses: ./.github/actions/calculate-shard-matrix units: + needs: setup + name: units (Node ${{ matrix.node-version }}, Shard ${{ matrix.shard-index }}) runs-on: windows-latest strategy: + fail-fast: false matrix: node-version: [22, 24, 26] + shard-index: ${{ fromJSON(needs.setup.outputs.shard-matrix) }} steps: - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 with: - fetch-depth: 300 + fetch-depth: 2 persist-credentials: false - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0 + - uses: ./.github/actions/run-unit-tests with: node-version: ${{ matrix.node-version }} - - uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4 + shard-total: ${{ needs.setup.outputs.shard-total }} + shard-index: ${{ matrix.shard-index }} + + # Dummy jobs to satisfy branch protection requirements for each node version + units-status: + name: units (${{ matrix.node-version }}) + needs: units + runs-on: ubuntu-latest + if: always() + strategy: + fail-fast: false + matrix: + node-version: [22, 24, 26] + steps: + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 with: - version: ^10.0.0 - - run: node --version - - run: bash ci/run_conditional_tests.sh - name: Run windows unit tests - shell: bash - env: - BUILD_TYPE: presubmit - TEST_TYPE: units + persist-credentials: false + - name: Check shard status + uses: ./.github/actions/check-shard-status + with: + node-version: ${{ matrix.node-version }} diff --git a/ci/run_conditional_tests.sh b/ci/run_conditional_tests.sh index cb6ae744a087..29f42bf888b2 100755 --- a/ci/run_conditional_tests.sh +++ b/ci/run_conditional_tests.sh @@ -36,25 +36,50 @@ if [[ "$(node -v)" == v22* ]]; then export NODE_OPTIONS="${NODE_OPTIONS} --no-experimental-require-module" fi -if [ ${BUILD_TYPE} == "presubmit" ]; then - # For presubmit build, we want to know the difference from the - # common commit in origin/main. - GIT_DIFF_ARG="origin/main..." +for arg in "$@"; do + case "${arg}" in + --strict) + STRICT=true + ;; + esac +done - # Then fetch enough history for finding the common commit. - git fetch origin main --deepen=300 +if [[ "${STRICT}" == "true" || "${STRICT}" == "1" ]]; then + if [ -z "${GIT_DIFF_ARG}" ]; then + echo "Error: STRICT mode requires GIT_DIFF_ARG to be set." >&2 + exit 1 + fi + set +e + git diff --quiet ${GIT_DIFF_ARG} + diff_status=$? + set -e + if [[ ${diff_status} -ne 0 && ${diff_status} -ne 1 ]]; then + echo "Error: STRICT mode git diff ${GIT_DIFF_ARG} failed with exit code ${diff_status}." >&2 + exit 1 + fi +else + if [ -z "${GIT_DIFF_ARG}" ]; then + if [ "${BUILD_TYPE}" == "presubmit" ]; then + # For presubmit build, we want to know the difference from the + # common commit in origin/main. + GIT_DIFF_ARG="origin/main..." -elif [ ${BUILD_TYPE} == "continuous" ]; then - # For continuous build, we want to know the difference in the last - # commit. This assumes we use squash commit when merging PRs. - GIT_DIFF_ARG="HEAD~.." + # Then fetch enough history for finding the common commit. + git fetch origin main --deepen=300 - # Then fetch one last commit for getting the diff. - git fetch origin main --deepen=1 + elif [ "${BUILD_TYPE}" == "continuous" ]; then + # For continuous build, we want to know the difference in the last + # commit. This assumes we use squash commit when merging PRs. + GIT_DIFF_ARG="HEAD~.." -else - # Run everything. - GIT_DIFF_ARG="" + # Then fetch one last commit for getting the diff. + git fetch origin main --deepen=1 + + else + # Run everything. + GIT_DIFF_ARG="" + fi + fi fi # Then detect changes in the test scripts. @@ -66,11 +91,10 @@ set -e if [[ "${changed}" -eq 0 ]]; then echo "no change detected in ci" else - echo "skipping trigger of tests for now: tracking in #7540" - # echo "change detected in ci, we should test everything" - # echo "result of git diff ${GIT_DIFF_ARG} ci:" - # git diff ${GIT_DIFF_ARG} ci - # GIT_DIFF_ARG="" + echo "change detected in ci, we should test everything" + echo "result of git diff ${GIT_DIFF_ARG} ci:" + git diff ${GIT_DIFF_ARG} ci + GIT_DIFF_ARG="" fi # Now we have a fixed list, but we can change it to autodetect if @@ -87,7 +111,7 @@ subdirs=( ) RETVAL=0 -# These following APIs need an explicit credential file to run properly (or oAuth2, which we don't support in this repo). +# These following APIs need an explicit credential file to run properly (or oAuth2, which we don't support in this repo). # When we hit these packages, we will run the "samples with credentials" trigger, which contains the credentials as an env variable tests_with_credentials="core/packages/google-auth-library-nodejs/ packages/google-analytics-admin/ packages/google-area120-tables/ packages/google-analytics-data/ packages/google-iam-credentials/ packages/google-apps-meet/ packages/google-chat/ packages/google-streetview-publish/ packages/google-cloud-developerconnect/" @@ -95,10 +119,13 @@ tests_with_credentials="core/packages/google-auth-library-nodejs/ packages/googl # Some packages are only used by our bots and automation. These packages do not need to run on Windows and # often employ platform specific code like file system interaction. Some packages may also fail # on Windows due to incompatible npm scripts. -# +# # Until these packages can be updated to be OS agnostic, we will skip them on Windows. windows_exempt_tests="core/ core/packages/ core/dev-packages/ .github/scripts/fixtures/ .github/scripts/tests/ core/packages/gapic-node-processing/ core/packages/typeless-sample-bot/ handwritten/cloud-profiler/" +# Gather all test directories into an array +test_dirs=() + for subdir in ${subdirs[@]}; do for d in `ls -d ${subdir}/*/`; do if [ -s "ignore.json" ] && jq -e ".ignored[] | select(. == \"$d\")" ignore.json > /dev/null 2>&1; then @@ -121,7 +148,7 @@ for subdir in ${subdirs[@]}; do # System tests for packages are broken and blocking PRs. # See https://github.com/googleapis/google-cloud-node/issues/7976. # - # Per https://github.com/googleapis/google-cloud-node/issues/7921, + # Per https://github.com/googleapis/google-cloud-node/issues/7921, # we are likely to permanently remove these tests in the near future. if [[ "${subdir}" == "packages" && "${TEST_TYPE}" == "system" ]]; then echo "Skipping ${TEST_TYPE} test for packages: ${d}" @@ -131,7 +158,7 @@ for subdir in ${subdirs[@]}; do # Sample tests for packages are broken/flaky and blocking PRs. # See https://github.com/googleapis/google-cloud-node/issues/7976#issuecomment-4210458096. # - # Per https://github.com/googleapis/google-cloud-node/issues/7921, + # Per https://github.com/googleapis/google-cloud-node/issues/7921, # we are likely to permanently remove these tests in the near future. if [[ "${subdir}" == "packages" && "${TEST_TYPE}" == "samples" ]]; then echo "Skipping ${TEST_TYPE} test for packages: ${d}" @@ -209,21 +236,51 @@ for subdir in ${subdirs[@]}; do fi fi if [ "${should_test}" = true ]; then - echo "running test in ${d}" - pushd ${d} - # Temporarily allow failure. - set +e - ${test_script} - ret=$? - set -e - if [ ${ret} -ne 0 ]; then - RETVAL=${ret} - # Since there are so many APIs, we should exit early if there's an error - exit ${RETVAL} - fi - popd + test_dirs+=("${d}") fi done done +# If DRY_RUN_SHARDS is set, output dynamic matrix values to GitHub Actions and exit +if [[ "${DRY_RUN_SHARDS}" == "true" ]]; then + count=${#test_dirs[@]} + if [[ $count -gt 15 ]]; then + matrix="[0, 1, 2, 3, 4]" + total="5" + else + matrix="[0]" + total="1" + fi + if [[ -n "${GITHUB_OUTPUT}" ]]; then + echo "shard_matrix=${matrix}" >> "${GITHUB_OUTPUT}" + echo "shard_total=${total}" >> "${GITHUB_OUTPUT}" + else + echo "shard_matrix=${matrix}" + echo "shard_total=${total}" + fi + exit 0 +fi + +# If SHARD_TOTAL and SHARD_INDEX are provided, we will only run a subset of the tests. +for i in "${!test_dirs[@]}"; do + d="${test_dirs[$i]}" + + if [[ -n "${SHARD_TOTAL}" && -n "${SHARD_INDEX}" ]]; then + if (( SHARD_TOTAL > 0 && i % SHARD_TOTAL != SHARD_INDEX )); then + continue + fi + fi + + echo "running test in ${d}" + pushd "${d}" >/dev/null + # Temporarily allow failure. + set +e + "${test_script}" + ret=$? + set -e + if [ ${ret} -ne 0 ]; then + exit ${ret} + fi + popd >/dev/null +done -exit ${RETVAL} +exit 0 diff --git a/ci/run_single_test.sh b/ci/run_single_test.sh index 662a1184f347..3dfd98ee7a2b 100755 --- a/ci/run_single_test.sh +++ b/ci/run_single_test.sh @@ -37,6 +37,8 @@ if [ ${BUILD_TYPE} != "presubmit" ]; then export MOCHA_REPORTER_OUTPUT=${PROJECT}_sponge_log.xml export MOCHA_REPORTER_SUITENAME=${PROJECT} export MOCHA_REPORTER=xunit +else + export MOCHA_REPORTER=dot fi # Install dependencies @@ -47,8 +49,8 @@ if command -v cygpath >/dev/null 2>&1; then PNPMFILE_PATH=$(cygpath -m "${PNPMFILE_PATH}") fi -echo "pnpm install --ignore-scripts --engine-strict --prod --pnpmfile \"${PNPMFILE_PATH}\"; pnpm install --pnpmfile \"${PNPMFILE_PATH}\"" -pnpm install --ignore-scripts --engine-strict --prod --pnpmfile "${PNPMFILE_PATH}"; pnpm install --pnpmfile "${PNPMFILE_PATH}" +echo "pnpm install --engine-strict --pnpmfile \"${PNPMFILE_PATH}\"" +pnpm install --engine-strict --pnpmfile "${PNPMFILE_PATH}" retval=0 From 491aba81bfa87556e9f022359d28b3093b1b655e Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 4 Aug 2026 17:05:32 +0000 Subject: [PATCH 07/15] core test fixes --- .../pack-n-play/test/fixtures/esm-package/test/test.js | 3 ++- .../pack-n-play/test/fixtures/leaky/test/test.ts | 3 ++- .../dev-packages/pack-n-play/test/fixtures/pass/test/test.ts | 3 ++- core/dev-packages/pack-n-play/test/test.ts | 5 +++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js b/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js index 037babedf227..2adbf53d289c 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js +++ b/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js @@ -16,7 +16,8 @@ import {packNTest} from 'pack-n-play'; import * as assert from 'assert'; import {describe, it} from 'mocha'; -describe('ESM package', () => { +describe('ESM package', function () { + this.timeout(120000); it('should support esm property', () => packNTest({ sample: { diff --git a/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts b/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts index 3a9dc7f39b39..9c56bc9c0741 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts +++ b/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts @@ -16,7 +16,8 @@ import {packNTest} from 'pack-n-play'; import * as assert from 'assert'; import {describe, it} from 'mocha'; -describe('leaky tests', () => { +describe('leaky tests', function () { + this.timeout(120000); it('should fail packing n testing', async () => { await assert.rejects( packNTest({ diff --git a/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts b/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts index 2dd02fb27cff..50d0d07d620d 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts +++ b/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts @@ -15,7 +15,8 @@ import {packNTest} from 'pack-n-play'; import {describe, it} from 'mocha'; -describe('passing tests', () => { +describe('passing tests', function () { + this.timeout(120000); it('should pass the test', async () => { await packNTest({ sample: { diff --git a/core/dev-packages/pack-n-play/test/test.ts b/core/dev-packages/pack-n-play/test/test.ts index da954b94ba08..06aef544b4e7 100644 --- a/core/dev-packages/pack-n-play/test/test.ts +++ b/core/dev-packages/pack-n-play/test/test.ts @@ -18,7 +18,8 @@ import execa = require('execa'); import {describe, it} from 'mocha'; describe('pack-n-play', () => { - it('should run tests', async () => { + it('should run tests', async function () { + this.timeout(600000); // 10 minutes const fixturesPath = path.resolve('./test/fixtures'); const dirs = fs .readdirSync(fixturesPath) @@ -29,7 +30,7 @@ describe('pack-n-play', () => { stdio: 'inherit', cwd: dir, }; - await execa('npm', ['install'], opts); + await execa('npm', ['install', '--no-audit', '--no-fund'], opts); await execa('npm', ['link', '../../../'], opts); await execa('npm', ['test'], opts); } From 4339fd166f94196a0ce8d2fb373a8d1406d4be76 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Wed, 5 Aug 2026 12:02:07 +0000 Subject: [PATCH 08/15] revert unnecessary addition of port --- core/packages/nodejs-googleapis-common/test/test.discovery.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/packages/nodejs-googleapis-common/test/test.discovery.ts b/core/packages/nodejs-googleapis-common/test/test.discovery.ts index 6745982a218f..0e656c5fb6b1 100644 --- a/core/packages/nodejs-googleapis-common/test/test.discovery.ts +++ b/core/packages/nodejs-googleapis-common/test/test.discovery.ts @@ -24,7 +24,7 @@ describe(__filename, () => { nock.cleanAll(); }); it('should discover an API', async () => { - const discoUrl = 'http://test.local:80'; + const discoUrl = 'http://test.local'; const scope = nock(discoUrl) .get('/') .replyWithFile(200, './test/fixtures/compute-v1.json', { @@ -37,7 +37,7 @@ describe(__filename, () => { scope.done(); }); it('should discover an API through second weird path', async () => { - const discoUrl = 'http://test.local:80'; + const discoUrl = 'http://test.local'; const scope = nock(discoUrl) .get('/') .replyWithFile(200, './test/fixtures/compute-v1.json', { From dc343c6dcaaf68d089d3a6c9b5862e4d389bc07b Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Wed, 5 Aug 2026 12:03:42 +0000 Subject: [PATCH 09/15] Revert "core test fixes" This reverts commit 491aba81bfa87556e9f022359d28b3093b1b655e. --- .../pack-n-play/test/fixtures/esm-package/test/test.js | 3 +-- .../pack-n-play/test/fixtures/leaky/test/test.ts | 3 +-- .../dev-packages/pack-n-play/test/fixtures/pass/test/test.ts | 3 +-- core/dev-packages/pack-n-play/test/test.ts | 5 ++--- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js b/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js index 2adbf53d289c..037babedf227 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js +++ b/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js @@ -16,8 +16,7 @@ import {packNTest} from 'pack-n-play'; import * as assert from 'assert'; import {describe, it} from 'mocha'; -describe('ESM package', function () { - this.timeout(120000); +describe('ESM package', () => { it('should support esm property', () => packNTest({ sample: { diff --git a/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts b/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts index 9c56bc9c0741..3a9dc7f39b39 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts +++ b/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts @@ -16,8 +16,7 @@ import {packNTest} from 'pack-n-play'; import * as assert from 'assert'; import {describe, it} from 'mocha'; -describe('leaky tests', function () { - this.timeout(120000); +describe('leaky tests', () => { it('should fail packing n testing', async () => { await assert.rejects( packNTest({ diff --git a/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts b/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts index 50d0d07d620d..2dd02fb27cff 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts +++ b/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts @@ -15,8 +15,7 @@ import {packNTest} from 'pack-n-play'; import {describe, it} from 'mocha'; -describe('passing tests', function () { - this.timeout(120000); +describe('passing tests', () => { it('should pass the test', async () => { await packNTest({ sample: { diff --git a/core/dev-packages/pack-n-play/test/test.ts b/core/dev-packages/pack-n-play/test/test.ts index 06aef544b4e7..da954b94ba08 100644 --- a/core/dev-packages/pack-n-play/test/test.ts +++ b/core/dev-packages/pack-n-play/test/test.ts @@ -18,8 +18,7 @@ import execa = require('execa'); import {describe, it} from 'mocha'; describe('pack-n-play', () => { - it('should run tests', async function () { - this.timeout(600000); // 10 minutes + it('should run tests', async () => { const fixturesPath = path.resolve('./test/fixtures'); const dirs = fs .readdirSync(fixturesPath) @@ -30,7 +29,7 @@ describe('pack-n-play', () => { stdio: 'inherit', cwd: dir, }; - await execa('npm', ['install', '--no-audit', '--no-fund'], opts); + await execa('npm', ['install'], opts); await execa('npm', ['link', '../../../'], opts); await execa('npm', ['test'], opts); } From d5aeba8d8aedbf23ae2c2ec4153c21c06b840bb1 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Fri, 7 Aug 2026 17:16:56 +0000 Subject: [PATCH 10/15] fix regressions from recent PRs --- .pnpmfile.cjs | 25 +++++++++++++++++++++++++ ci/run_single_test.sh | 11 +++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 .pnpmfile.cjs diff --git a/.pnpmfile.cjs b/.pnpmfile.cjs new file mode 100644 index 000000000000..8229d2f1fa80 --- /dev/null +++ b/.pnpmfile.cjs @@ -0,0 +1,25 @@ +module.exports = { + hooks: { + readPackage(pkg, context) { + // Override yargs for Node >= 24 to prevent ESM scope require errors + if (pkg.dependencies && pkg.dependencies.yargs) { + mutateYargs(pkg.dependencies, pkg.name, 'dependencies', context); + } + if (pkg.devDependencies && pkg.devDependencies.yargs) { + mutateYargs(pkg.devDependencies, pkg.name, 'devDependencies', context); + } + return pkg; + } + } +}; + +function mutateYargs(deps, pkgName, depType, context) { + const nodeVersion = process.version; + const majorVersion = parseInt(nodeVersion.replace('v', '').split('.')[0], 10); + + if (majorVersion >= 24) { + console.log(`[pnpmfile] Node.js version is ${nodeVersion} (>= 24). Overriding yargs to 18.0.0 in ${pkgName}`); + deps.yargs = '18.0.0'; + } +} + diff --git a/ci/run_single_test.sh b/ci/run_single_test.sh index ccb32c6d6d80..3dfd98ee7a2b 100755 --- a/ci/run_single_test.sh +++ b/ci/run_single_test.sh @@ -42,8 +42,15 @@ else fi # Install dependencies -echo "pnpm install --ignore-scripts --engine-strict --prod; pnpm install" -pnpm install --ignore-scripts --engine-strict --prod; pnpm install +# Normalize POSIX paths to Windows-compatible mixed paths (forward slashes) on Windows Git Bash +# so native Node.js and pnpm processes can resolve .pnpmfile.cjs without segmentation faults. +PNPMFILE_PATH="${PROJECT_ROOT}/.pnpmfile.cjs" +if command -v cygpath >/dev/null 2>&1; then + PNPMFILE_PATH=$(cygpath -m "${PNPMFILE_PATH}") +fi + +echo "pnpm install --engine-strict --pnpmfile \"${PNPMFILE_PATH}\"" +pnpm install --engine-strict --pnpmfile "${PNPMFILE_PATH}" retval=0 From 40133a284664958968271cdc4a4d85c77695ee4d Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Fri, 7 Aug 2026 18:31:23 +0000 Subject: [PATCH 11/15] attempt to fix bigtable unit tests --- .../test/metrics-collector/version/get-version-script.js | 5 ++++- .../bigtable/test/metrics-collector/version/version.ts | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/handwritten/bigtable/test/metrics-collector/version/get-version-script.js b/handwritten/bigtable/test/metrics-collector/version/get-version-script.js index 2950deaca99f..a7261a04c187 100644 --- a/handwritten/bigtable/test/metrics-collector/version/get-version-script.js +++ b/handwritten/bigtable/test/metrics-collector/version/get-version-script.js @@ -60,7 +60,10 @@ async function main() { const packageJSON = fs.readFileSync(packagePath); const expectedVersion = JSON.parse(packageJSON.toString()).version; - const fakeBigtable = new Bigtable(); + const fakeBigtable = new Bigtable({ + projectId: 'projectId', + credentials: {client_email: 'bogus', private_key: 'bogus'}, + }); const testMetricsHandler = new TestMetricsHandlerKeepName(); fakeBigtable._metricsConfigManager = new ClientSideMetricsConfigManager([ testMetricsHandler, diff --git a/handwritten/bigtable/test/metrics-collector/version/version.ts b/handwritten/bigtable/test/metrics-collector/version/version.ts index 06bf9d61a1db..3ee129170055 100644 --- a/handwritten/bigtable/test/metrics-collector/version/version.ts +++ b/handwritten/bigtable/test/metrics-collector/version/version.ts @@ -17,7 +17,8 @@ import {describe} from 'mocha'; import {execSync} from 'node:child_process'; describe('Bigtable/CSMVersion', () => { - it('Fetches the right client side metrics version', async () => { + it('Fetches the right client side metrics version', function () { + this.timeout(30000); execSync('cd test/metrics-collector/version && node get-version-script'); }); }); From 5259b8fd9fe6fc84d973db3e741e5835c906e2ee Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Mon, 10 Aug 2026 16:58:18 +0000 Subject: [PATCH 12/15] increase timeouts for pack-n-play --- .../pack-n-play/test/fixtures/esm-package/test/test.js | 2 +- core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts | 2 +- core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js b/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js index 2adbf53d289c..7d01aeaae791 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js +++ b/core/dev-packages/pack-n-play/test/fixtures/esm-package/test/test.js @@ -17,7 +17,7 @@ import * as assert from 'assert'; import {describe, it} from 'mocha'; describe('ESM package', function () { - this.timeout(120000); + this.timeout(300000); it('should support esm property', () => packNTest({ sample: { diff --git a/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts b/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts index 9c56bc9c0741..6e8f7ffe17c3 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts +++ b/core/dev-packages/pack-n-play/test/fixtures/leaky/test/test.ts @@ -17,7 +17,7 @@ import * as assert from 'assert'; import {describe, it} from 'mocha'; describe('leaky tests', function () { - this.timeout(120000); + this.timeout(300000); it('should fail packing n testing', async () => { await assert.rejects( packNTest({ diff --git a/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts b/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts index 50d0d07d620d..d2ff7417372d 100644 --- a/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts +++ b/core/dev-packages/pack-n-play/test/fixtures/pass/test/test.ts @@ -16,7 +16,7 @@ import {packNTest} from 'pack-n-play'; import {describe, it} from 'mocha'; describe('passing tests', function () { - this.timeout(120000); + this.timeout(300000); it('should pass the test', async () => { await packNTest({ sample: { From ff669479d0897d06daedd124df09d2f0f083dd77 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 11 Aug 2026 14:28:06 -0700 Subject: [PATCH 13/15] Apply suggestion from @bshaffer --- .github/actions/check-shard-status/action.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/check-shard-status/action.yaml b/.github/actions/check-shard-status/action.yaml index 37f57a2992ae..a3bc51f3abc0 100644 --- a/.github/actions/check-shard-status/action.yaml +++ b/.github/actions/check-shard-status/action.yaml @@ -22,7 +22,7 @@ runs: core.setFailed(`No shard jobs found for Node ${nodeVersion}`); return; } - const failed = shardJobs.filter(j => j.conclusion === 'failure' || j.conclusion === 'cancelled'); + const failed = shardJobs.filter(j => j.conclusion !== 'success'); if (failed.length > 0) { const failedLinks = failed.map(j => `- ${j.name}: ${j.html_url}`).join('\n'); core.setFailed(`${failed.length} shards failed for Node ${nodeVersion}:\n${failedLinks}`); From 646734e2504ca8dad1b620ae219ae5117119e612 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 11 Aug 2026 14:35:35 -0700 Subject: [PATCH 14/15] Apply suggestion from @bshaffer --- .github/workflows/presubmit.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/presubmit.yaml b/.github/workflows/presubmit.yaml index ba2c8ce7af07..7788f5c35df8 100644 --- a/.github/workflows/presubmit.yaml +++ b/.github/workflows/presubmit.yaml @@ -52,7 +52,7 @@ jobs: - run: npm run lint name: Run monorepo linter - # Dummy jobs to satisfy branch protection requirements for each node version + # Consolidate shards into jobs representing aggregate status to simplify branch protection requirements units-status: name: units (${{ matrix.node-version }}) needs: units From 3234034a5783e8b4f334b57b9d23aa5548b1c4e0 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 11 Aug 2026 22:41:10 +0000 Subject: [PATCH 15/15] change DRY_RUN_SHARD to RUN_TESTS_MDOE and fix merge --- .../calculate-shard-matrix/action.yaml | 2 +- .github/actions/run-unit-tests/action.yaml | 1 + ci/run_conditional_tests.sh | 22 +++++++++++++++++-- .../test/metrics-collector/version/version.ts | 2 +- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/actions/calculate-shard-matrix/action.yaml b/.github/actions/calculate-shard-matrix/action.yaml index 660e725ee84f..51e8e5c78ece 100644 --- a/.github/actions/calculate-shard-matrix/action.yaml +++ b/.github/actions/calculate-shard-matrix/action.yaml @@ -14,7 +14,7 @@ runs: id: set-matrix shell: bash env: - DRY_RUN_SHARDS: true + RUN_TESTS_MODE: CALCULATE_SHARD_MATRIX BUILD_TYPE: presubmit TEST_TYPE: units GIT_DIFF_ARG: "HEAD^1" diff --git a/.github/actions/run-unit-tests/action.yaml b/.github/actions/run-unit-tests/action.yaml index 69bf22693bdc..61aa5b9b4f15 100644 --- a/.github/actions/run-unit-tests/action.yaml +++ b/.github/actions/run-unit-tests/action.yaml @@ -26,6 +26,7 @@ runs: shell: bash run: bash ci/run_conditional_tests.sh --strict env: + RUN_TESTS_MODE: RUN_UNIT_TESTS BUILD_TYPE: presubmit TEST_TYPE: units SHARD_TOTAL: ${{ inputs.shard-total }} diff --git a/ci/run_conditional_tests.sh b/ci/run_conditional_tests.sh index 29f42bf888b2..e42d0f82bdc5 100755 --- a/ci/run_conditional_tests.sh +++ b/ci/run_conditional_tests.sh @@ -49,6 +49,10 @@ if [[ "${STRICT}" == "true" || "${STRICT}" == "1" ]]; then echo "Error: STRICT mode requires GIT_DIFF_ARG to be set." >&2 exit 1 fi + if [[ -z "${RUN_TESTS_MODE}" ]]; then + echo "Error: STRICT mode requires RUN_TESTS_MODE to be set." >&2 + exit 1 + fi set +e git diff --quiet ${GIT_DIFF_ARG} diff_status=$? @@ -82,6 +86,20 @@ else fi fi +if [[ -n "${RUN_TESTS_MODE}" ]]; then + if [[ "${RUN_TESTS_MODE}" != "CALCULATE_SHARD_MATRIX" && "${RUN_TESTS_MODE}" != "RUN_UNIT_TESTS" ]]; then + echo "Error: RUN_TESTS_MODE must be either CALCULATE_SHARD_MATRIX or RUN_UNIT_TESTS." >&2 + exit 1 + fi +fi + +if [[ "${RUN_TESTS_MODE}" == "RUN_UNIT_TESTS" ]]; then + if [[ -z "${SHARD_TOTAL}" || -z "${SHARD_INDEX}" ]]; then + echo "Error: SHARD_TOTAL and SHARD_INDEX must be set when RUN_TESTS_MODE is RUN_UNIT_TESTS." >&2 + exit 1 + fi +fi + # Then detect changes in the test scripts. set +e @@ -240,8 +258,8 @@ for subdir in ${subdirs[@]}; do fi done done -# If DRY_RUN_SHARDS is set, output dynamic matrix values to GitHub Actions and exit -if [[ "${DRY_RUN_SHARDS}" == "true" ]]; then +# If RUN_TESTS_MODE is CALCULATE_SHARD_MATRIX, output dynamic matrix values to GitHub Actions and exit +if [[ "${RUN_TESTS_MODE}" == "CALCULATE_SHARD_MATRIX" ]]; then count=${#test_dirs[@]} if [[ $count -gt 15 ]]; then matrix="[0, 1, 2, 3, 4]" diff --git a/handwritten/bigtable/test/metrics-collector/version/version.ts b/handwritten/bigtable/test/metrics-collector/version/version.ts index 36107a7dbc6a..bbf32d5c234e 100644 --- a/handwritten/bigtable/test/metrics-collector/version/version.ts +++ b/handwritten/bigtable/test/metrics-collector/version/version.ts @@ -17,7 +17,7 @@ import {describe} from 'mocha'; import {execSync} from 'node:child_process'; describe('Bigtable/CSMVersion', () => { - it.skip('Fetches the right client side metrics version', async () => { + it.skip('Fetches the right client side metrics version', async function () { this.timeout(30000); execSync('cd test/metrics-collector/version && node get-version-script'); });