From 15f08ad7c4e8a5d9934c199b19bb2b9bd70e681d Mon Sep 17 00:00:00 2001 From: Amaad Martin Date: Sun, 2 Aug 2026 19:46:57 -0700 Subject: [PATCH 1/2] Fix: pin the ADK test log level inside the vitest workers vitest runs globalSetup once in the main process, before any worker exists, so the setLogLevel(ERROR) call in tests/global_setup.ts could never reach the code under test: the level is module state on the logger's currentLogger instance, which a worker does not inherit. The bare '@google/adk' specifier in that file compounded it. Only the per-project alias maps that specifier to core/src; from tests/ it resolves through the workspace symlink to the built core/dist bundle, a second module instance with its own level -- and one that has to be built before any unit test can start. Move the pin to a setupFiles module, which vitest evaluates inside each worker before the test file, and import the logger module by relative path so it is the same instance the aliased tests use. Every project block gets the entry: projects inherit nothing from the root config. https://vitest.dev/config/globalsetup https://vitest.dev/config/setupfiles --- tests/global_setup.ts | 15 --------------- tests/setup_log_level.ts | 16 ++++++++++++++++ vitest.config.ts | 7 ++++++- 3 files changed, 22 insertions(+), 16 deletions(-) delete mode 100644 tests/global_setup.ts create mode 100644 tests/setup_log_level.ts diff --git a/tests/global_setup.ts b/tests/global_setup.ts deleted file mode 100644 index f41775252..000000000 --- a/tests/global_setup.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @license - * Copyright 2026 Google LLC - * SPDX-License-Identifier: Apache-2.0 - */ - -import {LogLevel, setLogLevel} from '@google/adk'; - -export function setup() { - setLogLevel(LogLevel.ERROR); -} - -export function teardown() { - setLogLevel(LogLevel.INFO); -} diff --git a/tests/setup_log_level.ts b/tests/setup_log_level.ts new file mode 100644 index 000000000..0ab7613f2 --- /dev/null +++ b/tests/setup_log_level.ts @@ -0,0 +1,16 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {LogLevel, setLogLevel} from '../core/src/utils/logger.js'; + +// Must run inside the test worker: vitest `globalSetup` executes in the main +// process, and the log level is module-level state a worker never inherits. +// +// Import the logger module directly, not the `@google/adk` barrel. Only the +// per-project `alias` maps that specifier to `core/src`; from here it resolves +// through the workspace symlink to the built `core/dist` bundle, which is a +// second module instance with its own log level (and need not exist yet). +setLogLevel(LogLevel.ERROR); diff --git a/vitest.config.ts b/vitest.config.ts index 1b7eaee63..c107296bf 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -35,6 +35,7 @@ export default defineConfig({ test: { name: 'unit:core', environment: 'node', + setupFiles: ['./tests/setup_log_level.ts'], alias: { '@google/adk': path.resolve(__dirname, './core/src'), '@google/adk-integrations': path.resolve( @@ -49,6 +50,7 @@ export default defineConfig({ test: { name: 'unit:dev', environment: 'node', + setupFiles: ['./tests/setup_log_level.ts'], alias: { '@google/adk': path.resolve(__dirname, './core/src'), '@google/adk-integrations': path.resolve( @@ -63,6 +65,7 @@ export default defineConfig({ test: { name: 'unit:integrations', environment: 'node', + setupFiles: ['./tests/setup_log_level.ts'], alias: { '@google/adk': path.resolve(__dirname, './core/src'), '@google/adk-integrations': path.resolve( @@ -77,6 +80,7 @@ export default defineConfig({ test: { name: 'integration', environment: 'node', + setupFiles: ['./tests/setup_log_level.ts'], hookTimeout: INTEGRATION_HOOK_TIMEOUT_MS, testTimeout: INTEGRATION_TEST_TIMEOUT_MS, alias: { @@ -93,6 +97,7 @@ export default defineConfig({ test: { name: 'e2e', environment: 'node', + setupFiles: ['./tests/setup_log_level.ts'], alias: { '@google/adk': path.resolve(__dirname, './core/src'), '@google/adk-integrations': path.resolve( @@ -107,6 +112,7 @@ export default defineConfig({ test: { name: 'cross-language', environment: 'node', + setupFiles: ['./tests/setup_log_level.ts'], alias: { '@google/adk': path.resolve(__dirname, './core/src'), '@google/adk-integrations': path.resolve( @@ -136,6 +142,5 @@ export default defineConfig({ lines: 86, }, }, - globalSetup: ['./tests/global_setup.ts'], }, }); From 71a0f79efc9aa7e760db22968aa309d5bd45bb67 Mon Sep 17 00:00:00 2001 From: Amaad Martin Date: Sun, 2 Aug 2026 19:47:02 -0700 Subject: [PATCH 2/2] Test: add a probe that fails if the worker log-level pin stops applying The pin had no observing test, which is why it sat broken. There is no public accessor for the effective level, so the probe asserts on what the logger writes instead of reading private state: it swaps in a Console bound to an in-memory stream, since the winston Console transport writes to whatever globalThis.console is at log time. The error case is load-bearing -- it proves the capture harness works, so the info case cannot pass vacuously. --- core/test/utils/log_level_pin_test.ts | 58 +++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 core/test/utils/log_level_pin_test.ts diff --git a/core/test/utils/log_level_pin_test.ts b/core/test/utils/log_level_pin_test.ts new file mode 100644 index 000000000..aff22075b --- /dev/null +++ b/core/test/utils/log_level_pin_test.ts @@ -0,0 +1,58 @@ +/** + * @license + * Copyright 2026 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +import {getLogger} from '@google/adk'; +import {Console} from 'node:console'; +import {Writable} from 'node:stream'; +import {describe, expect, it} from 'vitest'; + +/** + * Returns everything `fn` writes through the global console. + * + * The winston Console transport backing the ADK logger writes to the streams + * of whatever `globalThis.console` is at log time, so swapping in a `Console` + * bound to an in-memory stream captures it. Vitest installs its own `Console` + * in the worker, which is why spying on `process.stdout.write` or + * `console.log` captures nothing here. + */ +function captureConsoleOutput(fn: () => void): string { + const chunks: string[] = []; + const stream = new Writable({ + write(chunk: unknown, _encoding: string, callback: () => void) { + chunks.push(String(chunk)); + callback(); + }, + }); + const original = globalThis.console; + globalThis.console = new Console({stdout: stream, stderr: stream}); + try { + fn(); + } finally { + globalThis.console = original; + } + return chunks.join(''); +} + +/** + * Pins the `setupFiles` wiring in `vitest.config.ts`. The log level is + * module-level state, so a `globalSetup` file running in the Vitest main + * process cannot reach the worker this test runs in; only a setup file can. + * There is no public accessor for the effective level, so these assert on what + * the logger writes. + */ +describe('test worker log level', () => { + it('suppresses info logs', () => { + expect(captureConsoleOutput(() => getLogger().info('info-pin-probe'))).toBe( + '', + ); + }); + + it('still emits error logs', () => { + expect( + captureConsoleOutput(() => getLogger().error('error-pin-probe')), + ).toContain('error-pin-probe'); + }); +});