Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/cross-language-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
go-version: '1.25'

- name: Install dependencies
run: npm install
run: npm ci

- name: Install Go dependencies
shell: bash
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
python-version: '3.11'

- name: Install dependencies
run: npm install
run: npm ci

- name: Check for secrets leaks
run: npx secretlint "**/*"
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ node_modules/
.idea/
.vscode/*
!.vscode/settings.json
# Regenerated per test run; pins nothing (see installFixtureDeps).
tests/**/package-lock.json
coverage/
.env
Expand Down
23 changes: 8 additions & 15 deletions tests/integration/agent_loader/agent_dirname_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
* SPDX-License-Identifier: Apache-2.0
*/

import {exec, spawn} from 'node:child_process';
import * as fs from 'node:fs/promises';
import {spawn} from 'node:child_process';
import * as path from 'node:path';
import {promisify} from 'node:util';
import {afterAll, beforeAll, describe, expect, it} from 'vitest';
import {sendInput} from '../test_case_utils.js';
import {
cleanupFixtureDeps,
installFixtureDeps,
sendInput,
} from '../test_case_utils.js';

const execAsync = promisify(exec);
const dirname = process.cwd();
const TEST_EXECUTION_TIMEOUT = 40000;

Expand All @@ -25,7 +26,7 @@ describe.each(['__dirname', '__filename', 'import_meta_url'])(
);

beforeAll(async () => {
await execAsync('npm install', {cwd: projectPath});
await installFixtureDeps(projectPath);
});

it(
Expand All @@ -47,15 +48,7 @@ describe.each(['__dirname', '__filename', 'import_meta_url'])(
);

afterAll(async () => {
await fs
.rm(path.join(projectPath, 'node_modules'), {
recursive: true,
force: true,
})
.catch(() => {});
await fs
.unlink(path.join(projectPath, 'package-lock.json'))
.catch(() => {});
await cleanupFixtureDeps(projectPath);
});
},
);
35 changes: 10 additions & 25 deletions tests/integration/app_loader/app_loader_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,16 @@
*/

import {App, isApp, isBaseAgent} from '@google/adk';
import {exec, spawn} from 'node:child_process';
import * as fs from 'node:fs/promises';
import {spawn} from 'node:child_process';
import * as path from 'node:path';
import {promisify} from 'node:util';
import {afterAll, beforeAll, describe, expect, it} from 'vitest';
import {AgentLoader} from '../../../dev/src/utils/agent_loader.js';
import {sendInput} from '../test_case_utils.js';
import {
cleanupFixtureDeps,
installFixtureDeps,
sendInput,
} from '../test_case_utils.js';

const execAsync = promisify(exec);
const dirname = process.cwd();
const TEST_EXECUTION_TIMEOUT = 40000;

Expand All @@ -28,7 +29,7 @@ describe('App loader CLI integration', () => {
);

beforeAll(async () => {
await execAsync('npm install', {cwd: projectPath});
await installFixtureDeps(projectPath);
});

it(
Expand All @@ -53,15 +54,7 @@ describe('App loader CLI integration', () => {
);

afterAll(async () => {
await fs
.rm(path.join(projectPath, 'node_modules'), {
recursive: true,
force: true,
})
.catch(() => {});
await fs
.unlink(path.join(projectPath, 'package-lock.json'))
.catch(() => {});
await cleanupFixtureDeps(projectPath);
});
},
);
Expand All @@ -75,7 +68,7 @@ describe('AgentLoader discovery and loading integration', () => {
let loader: AgentLoader;

beforeAll(async () => {
await execAsync('npm install', {cwd: projectPath});
await installFixtureDeps(projectPath);
loader = new AgentLoader(projectPath);
});

Expand Down Expand Up @@ -129,14 +122,6 @@ describe('AgentLoader discovery and loading integration', () => {

afterAll(async () => {
await loader.disposeAll();
await fs
.rm(path.join(projectPath, 'node_modules'), {
recursive: true,
force: true,
})
.catch(() => {});
await fs
.unlink(path.join(projectPath, 'package-lock.json'))
.catch(() => {});
await cleanupFixtureDeps(projectPath);
});
});
36 changes: 27 additions & 9 deletions tests/integration/build_setup/build_setup_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,23 @@ import {exec, spawn} from 'node:child_process';
import * as fs from 'node:fs/promises';
import {promisify} from 'node:util';
import {afterAll, beforeAll, describe, expect, it} from 'vitest';
import {getResponse, sendInput} from '../test_case_utils.js';
import {
cleanupFixtureDeps,
getResponse,
installFixtureDeps,
sendInput,
} from '../test_case_utils.js';

const execAsync = promisify(exec);
const dirname = process.cwd();

const TEST_EXECUTION_TIMEOUT = 20000;

/** The subset of a fixture lockfile this suite asserts on. */
interface FixtureLockfile {
packages: Record<string, {link?: boolean}>;
}

describe('Build setup', () => {
describe.each([
'js_commonjs',
Expand All @@ -26,7 +36,7 @@ describe('Build setup', () => {
const projectPath = `${dirname}/tests/integration/build_setup/${buildSetup}`;

beforeAll(async () => {
await execAsync('npm install', {cwd: projectPath});
await installFixtureDeps(projectPath);

if (buildSetup.startsWith('ts_')) {
let buildResult;
Expand All @@ -45,6 +55,19 @@ describe('Build setup', () => {
}
});

it('should link the workspace packages rather than pack them', async () => {
const lock: FixtureLockfile = JSON.parse(
await fs.readFile(`${projectPath}/package-lock.json`, 'utf-8'),
);

// Not `lstat().isSymbolicLink()`: npm creates junctions on Windows, but
// records `link: true` on every npm major and OS in the matrix.
expect(lock.packages['node_modules/@google/adk']?.link).toBe(true);
expect(lock.packages['node_modules/@google/adk-devtools']?.link).toBe(
true,
);
});

it(
'should build and run agent successfully',
async () => {
Expand Down Expand Up @@ -109,15 +132,10 @@ describe('Build setup', () => {
);

afterAll(async () => {
await fs
.rm(`${projectPath}/node_modules`, {recursive: true, force: true})
.catch(() => {});
await fs.unlink(`${projectPath}/package-lock.json`).catch(() => {});
await cleanupFixtureDeps(projectPath);

if (buildSetup.startsWith('ts_')) {
await fs
.rm(`${projectPath}/dist`, {recursive: true, force: true})
.catch(() => {});
await fs.rm(`${projectPath}/dist`, {recursive: true, force: true});
}
});
});
Expand Down
26 changes: 12 additions & 14 deletions tests/integration/skills/script_js/agent_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import {exec, spawn} from 'node:child_process';
import {spawn} from 'node:child_process';
import * as fs from 'node:fs/promises';
import {promisify} from 'node:util';
import {afterAll, beforeAll, describe, expect, it} from 'vitest';
import {normalizeLineEndings, sendInput} from '../../test_case_utils.js';
import {
cleanupFixtureDeps,
installFixtureDeps,
normalizeLineEndings,
sendInput,
} from '../../test_case_utils.js';

const execAsync = promisify(exec);
const dirname = process.cwd();
const PROJECT_PATH = `${dirname}/tests/integration/skills/script_js`;
const TEST_EXECUTION_TIMEOUT = 60000;
Expand All @@ -30,7 +33,7 @@ const TEST_EXECUTION_TIMEOUT = 60000;
*/
describe('Agent with skills that generates JS script and runs it locally', () => {
beforeAll(async () => {
await execAsync('npm install', {cwd: PROJECT_PATH});
await installFixtureDeps(PROJECT_PATH);
});

it(
Expand Down Expand Up @@ -94,15 +97,10 @@ describe('Agent with skills that generates JS script and runs it locally', () =>

afterAll(async () => {
// delete generated files
await fs
.rm(`${PROJECT_PATH}/ephemeral_entanglement.md`, {force: true})
.catch(() => {});
await fs.rm(`${PROJECT_PATH}/index.html`, {force: true}).catch(() => {});
await fs.rm(`${PROJECT_PATH}/sketch.js`, {force: true}).catch(() => {});
await fs.rm(`${PROJECT_PATH}/ephemeral_entanglement.md`, {force: true});
await fs.rm(`${PROJECT_PATH}/index.html`, {force: true});
await fs.rm(`${PROJECT_PATH}/sketch.js`, {force: true});

await fs
.rm(`${PROJECT_PATH}/node_modules`, {recursive: true, force: true})
.catch(() => {});
await fs.unlink(`${PROJECT_PATH}/package-lock.json`).catch(() => {});
await cleanupFixtureDeps(PROJECT_PATH);
});
});
41 changes: 40 additions & 1 deletion tests/integration/test_case_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ import {
GenerateContentResponse,
GoogleGenAI,
} from '@google/genai';
import {ChildProcessWithoutNullStreams} from 'node:child_process';
import {ChildProcessWithoutNullStreams, exec} from 'node:child_process';
import * as fs from 'node:fs/promises';
import * as path from 'node:path';
import {promisify} from 'node:util';
import {expect} from 'vitest';

const execAsync = promisify(exec);

/**
* Represents a raw generate content response.
*/
Expand Down Expand Up @@ -363,6 +368,40 @@ export abstract class BaseTestServer {
}
}

/**
* Installs an integration fixture's dependencies.
*
* `--install-links=false` is pinned rather than left to the contributor's npm,
* whose default for it flipped between majors: linking the `file:`-specified
* `core` and `dev` workspaces measured 0.6s against 64s for packing their
* transitive graph, and only the link mode resolves that graph through the
* repo-root `node_modules` that the committed root `package-lock.json` pins.
* `--no-audit --no-fund` just drop registry chatter no test asserts on.
*
* Rejects if npm fails, so a broken fixture install surfaces as a hook failure.
*/
export async function installFixtureDeps(projectPath: string): Promise<void> {
await execAsync('npm install --install-links=false --no-audit --no-fund', {
cwd: projectPath,
});
}

/**
* Removes what {@link installFixtureDeps} generated, so no fixture leaves
* `node_modules` or a lockfile in the working tree.
*
* `force` ignores an install that never got that far, but anything else -- a
* locked file, a permission error -- is left to reject, so a fixture that
* cannot be cleaned fails its `afterAll` instead of polluting the next run.
*/
export async function cleanupFixtureDeps(projectPath: string): Promise<void> {
await fs.rm(path.join(projectPath, 'node_modules'), {
recursive: true,
force: true,
});
await fs.rm(path.join(projectPath, 'package-lock.json'), {force: true});
}

export function sendInput(
childProcess: ChildProcessWithoutNullStreams,
input: string,
Expand Down
6 changes: 3 additions & 3 deletions vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import {defineConfig} from 'vitest/config';
* matching `afterAll` hooks recursively remove the resulting `node_modules`.
* That exceeds Vitest's 10s default on a slow or loaded machine.
*
* The twelve `build_setup` hook runs take ~16s combined warm on ubuntu-latest,
* but a cold, network-bound install has been measured at ~70s, so 120s covers
* the worst case. Trade-off: a genuinely stuck hook takes this long to surface.
* The install is neither the dominant cost nor network-bound any more -- see
* `installFixtureDeps`; `npm run build` for the `ts_*` fixtures is what remains.
* Trade-off: a genuinely stuck hook takes this long to surface.
*
* This is the only definition of that budget. Suites must not pass a timeout
* argument to an install or teardown hook: it shadows this floor rather than
Expand Down