pre-release commit

This commit is contained in:
koalazf.99
2025-07-22 19:59:07 +08:00
parent c5dee4bb17
commit a9d6965bef
485 changed files with 111444 additions and 2 deletions

View File

@@ -0,0 +1,30 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { strict as assert } from 'assert';
import { test } from 'node:test';
import { TestRig } from './test-helper.js';
test('reads a file', (t) => {
const rig = new TestRig();
rig.setup(t.name);
rig.createFile('test.txt', 'hello world');
const output = rig.run(`read the file name test.txt`);
assert.ok(output.toLowerCase().includes('hello'));
});
test('writes a file', (t) => {
const rig = new TestRig();
rig.setup(t.name);
rig.createFile('test.txt', '');
rig.run(`edit test.txt to have a hello world message`);
const fileContent = rig.readFile('test.txt');
assert.ok(fileContent.toLowerCase().includes('hello'));
});

View File

@@ -0,0 +1,19 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { test } from 'node:test';
import { strict as assert } from 'assert';
import { TestRig } from './test-helper.js';
test('should be able to search the web', async (t) => {
const rig = new TestRig();
rig.setup(t.name);
const prompt = `what planet do we live on`;
const result = await rig.run(prompt);
assert.ok(result.toLowerCase().includes('earth'));
});

View File

@@ -0,0 +1,24 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { test } from 'node:test';
import { strict as assert } from 'assert';
import { TestRig } from './test-helper.js';
test('should be able to list a directory', async (t) => {
const rig = new TestRig();
rig.setup(t.name);
rig.createFile('file1.txt', 'file 1 content');
rig.mkdir('subdir');
rig.sync();
const prompt = `Can you list the files in the current directory. Display them in the style of 'ls'`;
const result = rig.run(prompt);
const lines = result.split('\n').filter((line) => line.trim() !== '');
assert.ok(lines.some((line) => line.includes('file1.txt')));
assert.ok(lines.some((line) => line.includes('subdir')));
});

View File

@@ -0,0 +1,22 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { test } from 'node:test';
import { strict as assert } from 'assert';
import { TestRig } from './test-helper.js';
test.skip('should be able to read multiple files', async (t) => {
const rig = new TestRig();
rig.setup(t.name);
rig.createFile('file1.txt', 'file 1 content');
rig.createFile('file2.txt', 'file 2 content');
const prompt = `Read the files in this directory, list them and print them to the screen`;
const result = await rig.run(prompt);
assert.ok(result.includes('file 1 content'));
assert.ok(result.includes('file 2 content'));
});

View File

@@ -0,0 +1,22 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { test } from 'node:test';
import { strict as assert } from 'assert';
import { TestRig } from './test-helper.js';
test('should be able to replace content in a file', async (t) => {
const rig = new TestRig();
rig.setup(t.name);
const fileName = 'file_to_replace.txt';
rig.createFile(fileName, 'original content');
const prompt = `Can you replace 'original' with 'replaced' in the file 'file_to_replace.txt'`;
await rig.run(prompt);
const newFileContent = rig.readFile(fileName);
assert.strictEqual(newFileContent, 'replaced content');
});

View File

@@ -0,0 +1,146 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { spawnSync, spawn } from 'child_process';
import { mkdirSync, rmSync, createWriteStream } from 'fs';
import { join, dirname, basename } from 'path';
import { fileURLToPath } from 'url';
import { glob } from 'glob';
async function main() {
const __dirname = dirname(fileURLToPath(import.meta.url));
const rootDir = join(__dirname, '..');
const integrationTestsDir = join(rootDir, '.integration-tests');
if (process.env.GEMINI_SANDBOX === 'docker' && !process.env.IS_DOCKER) {
console.log('Building sandbox for Docker...');
const buildResult = spawnSync('npm', ['run', 'build:all'], {
stdio: 'inherit',
});
if (buildResult.status !== 0) {
console.error('Sandbox build failed.');
process.exit(1);
}
}
const runId = `${Date.now()}`;
const runDir = join(integrationTestsDir, runId);
mkdirSync(runDir, { recursive: true });
const args = process.argv.slice(2);
const keepOutput =
process.env.KEEP_OUTPUT === 'true' || args.includes('--keep-output');
if (keepOutput) {
const keepOutputIndex = args.indexOf('--keep-output');
if (keepOutputIndex > -1) {
args.splice(keepOutputIndex, 1);
}
console.log(`Keeping output for test run in: ${runDir}`);
}
const verbose = args.includes('--verbose');
if (verbose) {
const verboseIndex = args.indexOf('--verbose');
if (verboseIndex > -1) {
args.splice(verboseIndex, 1);
}
}
const testPatterns =
args.length > 0
? args.map((arg) => `integration-tests/${arg}.test.js`)
: ['integration-tests/*.test.js'];
const testFiles = glob.sync(testPatterns, { cwd: rootDir, absolute: true });
for (const testFile of testFiles) {
const testFileName = basename(testFile);
console.log(`\tFound test file: ${testFileName}`);
}
let allTestsPassed = true;
for (const testFile of testFiles) {
const testFileName = basename(testFile);
const testFileDir = join(runDir, testFileName);
mkdirSync(testFileDir, { recursive: true });
console.log(
`------------- Running test file: ${testFileName} ------------------------------`,
);
const nodeArgs = ['--test'];
if (verbose) {
nodeArgs.push('--test-reporter=spec');
}
nodeArgs.push(testFile);
const child = spawn('node', nodeArgs, {
stdio: 'pipe',
env: {
...process.env,
GEMINI_CLI_INTEGRATION_TEST: 'true',
INTEGRATION_TEST_FILE_DIR: testFileDir,
KEEP_OUTPUT: keepOutput.toString(),
VERBOSE: verbose.toString(),
TEST_FILE_NAME: testFileName,
},
});
let outputStream;
if (keepOutput) {
const outputFile = join(testFileDir, 'output.log');
outputStream = createWriteStream(outputFile);
console.log(`Output for ${testFileName} written to: ${outputFile}`);
}
child.stdout.on('data', (data) => {
if (verbose) {
process.stdout.write(data);
}
if (outputStream) {
outputStream.write(data);
}
});
child.stderr.on('data', (data) => {
if (verbose) {
process.stderr.write(data);
}
if (outputStream) {
outputStream.write(data);
}
});
const exitCode = await new Promise((resolve) => {
child.on('close', (code) => {
if (outputStream) {
outputStream.end(() => {
resolve(code);
});
} else {
resolve(code);
}
});
});
if (exitCode !== 0) {
console.error(`Test file failed: ${testFileName}`);
allTestsPassed = false;
}
}
if (!keepOutput) {
rmSync(runDir, { recursive: true, force: true });
}
if (!allTestsPassed) {
console.error('One or more test files failed.');
process.exit(1);
}
}
main();

View File

@@ -0,0 +1,31 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { test } from 'node:test';
import { strict as assert } from 'assert';
import { TestRig } from './test-helper.js';
test('should be able to run a shell command', async (t) => {
const rig = new TestRig();
rig.setup(t.name);
rig.createFile('blah.txt', 'some content');
const prompt = `Can you use ls to list the contexts of the current folder`;
const result = rig.run(prompt);
assert.ok(result.includes('blah.txt'));
});
test('should be able to run a shell command via stdin', async (t) => {
const rig = new TestRig();
rig.setup(t.name);
rig.createFile('blah.txt', 'some content');
const prompt = `Can you use ls to list the contexts of the current folder`;
const result = rig.run({ stdin: prompt });
assert.ok(result.includes('blah.txt'));
});

View File

@@ -0,0 +1,21 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { test } from 'node:test';
import { strict as assert } from 'assert';
import { TestRig } from './test-helper.js';
test('should be able to save to memory', async (t) => {
const rig = new TestRig();
rig.setup(t.name);
const prompt = `remember that my favorite color is blue.
what is my favorite color? tell me that and surround it with $ symbol`;
const result = await rig.run(prompt);
assert.ok(result.toLowerCase().includes('$blue$'));
});

View File

@@ -0,0 +1,70 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { test, describe, before, after } from 'node:test';
import { strict as assert } from 'node:assert';
import { TestRig } from './test-helper.js';
import { spawn } from 'child_process';
import { join } from 'path';
import { fileURLToPath } from 'url';
import { writeFileSync, unlinkSync } from 'fs';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
const serverScriptPath = join(__dirname, './temp-server.js');
const serverScript = `
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
const server = new McpServer({
name: 'addition-server',
version: '1.0.0',
});
server.registerTool(
'add',
{
title: 'Addition Tool',
description: 'Add two numbers',
inputSchema: { a: z.number(), b: z.number() },
},
async ({ a, b }) => ({
content: [{ type: 'text', text: String(a + b) }],
}),
);
const transport = new StdioServerTransport();
await server.connect(transport);
`;
describe('simple-mcp-server', () => {
const rig = new TestRig();
let child;
before(() => {
writeFileSync(serverScriptPath, serverScript);
child = spawn('node', [serverScriptPath], {
stdio: ['pipe', 'pipe', 'pipe'],
});
child.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
// Wait for the server to be ready
return new Promise((resolve) => setTimeout(resolve, 500));
});
after(() => {
child.kill();
unlinkSync(serverScriptPath);
});
test('should add two numbers', () => {
rig.setup('should add two numbers');
const output = rig.run('add 5 and 10');
assert.ok(output.includes('15'));
});
});

View File

@@ -0,0 +1,101 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { execSync } from 'child_process';
import { mkdirSync, writeFileSync, readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { env } from 'process';
const __dirname = dirname(fileURLToPath(import.meta.url));
function sanitizeTestName(name) {
return name
.toLowerCase()
.replace(/[^a-z0-9]/g, '-')
.replace(/-+/g, '-');
}
export class TestRig {
constructor() {
this.bundlePath = join(__dirname, '..', 'bundle/gemini.js');
this.testDir = null;
}
setup(testName) {
this.testName = testName;
const sanitizedName = sanitizeTestName(testName);
this.testDir = join(env.INTEGRATION_TEST_FILE_DIR, sanitizedName);
mkdirSync(this.testDir, { recursive: true });
}
createFile(fileName, content) {
const filePath = join(this.testDir, fileName);
writeFileSync(filePath, content);
return filePath;
}
mkdir(dir) {
mkdirSync(join(this.testDir, dir));
}
sync() {
// ensure file system is done before spawning
execSync('sync', { cwd: this.testDir });
}
run(promptOrOptions, ...args) {
let command = `node ${this.bundlePath} --yolo`;
const execOptions = {
cwd: this.testDir,
encoding: 'utf-8',
};
if (typeof promptOrOptions === 'string') {
command += ` --prompt "${promptOrOptions}"`;
} else if (
typeof promptOrOptions === 'object' &&
promptOrOptions !== null
) {
if (promptOrOptions.prompt) {
command += ` --prompt "${promptOrOptions.prompt}"`;
}
if (promptOrOptions.stdin) {
execOptions.input = promptOrOptions.stdin;
}
}
command += ` ${args.join(' ')}`;
const output = execSync(command, execOptions);
if (env.KEEP_OUTPUT === 'true' || env.VERBOSE === 'true') {
const testId = `${env.TEST_FILE_NAME.replace(
'.test.js',
'',
)}:${this.testName.replace(/ /g, '-')}`;
console.log(`--- TEST: ${testId} ---`);
console.log(output);
console.log(`--- END TEST: ${testId} ---`);
}
return output;
}
readFile(fileName) {
const content = readFileSync(join(this.testDir, fileName), 'utf-8');
if (env.KEEP_OUTPUT === 'true' || env.VERBOSE === 'true') {
const testId = `${env.TEST_FILE_NAME.replace(
'.test.js',
'',
)}:${this.testName.replace(/ /g, '-')}`;
console.log(`--- FILE: ${testId}/${fileName} ---`);
console.log(content);
console.log(`--- END FILE: ${testId}/${fileName} ---`);
}
return content;
}
}

View File

@@ -0,0 +1,21 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { test } from 'node:test';
import { strict as assert } from 'assert';
import { TestRig } from './test-helper.js';
test('should be able to write a file', async (t) => {
const rig = new TestRig();
rig.setup(t.name);
const prompt = `show me an example of using the write tool. put a dad joke in dad.txt`;
await rig.run(prompt);
const newFilePath = 'dad.txt';
const newFileContent = rig.readFile(newFilePath);
assert.notEqual(newFileContent, '');
});