mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 14:10:37 -07:00
test: add integration test to verify stdout/stderr routing (#17280)
Co-authored-by: Allen Hutchison <adh@google.com>
This commit is contained in:
1
integration-tests/stdout-stderr-output-error.responses
Normal file
1
integration-tests/stdout-stderr-output-error.responses
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"I could not find the file `nonexistent-file-that-does-not-exist.txt` in the current directory or its subdirectories. Please verify the file path or name."}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":10,"candidatesTokenCount":25,"totalTokenCount":35,"promptTokensDetails":[{"modality":"TEXT","tokenCount":10}]}}]}
|
||||||
1
integration-tests/stdout-stderr-output.responses
Normal file
1
integration-tests/stdout-stderr-output.responses
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"method":"generateContentStream","response":[{"candidates":[{"content":{"parts":[{"text":"Hello! How can I help you today?"}],"role":"model"},"finishReason":"STOP","index":0}],"usageMetadata":{"promptTokenCount":5,"candidatesTokenCount":9,"totalTokenCount":14,"promptTokensDetails":[{"modality":"TEXT","tokenCount":5}]}}]}
|
||||||
62
integration-tests/stdout-stderr-output.test.ts
Normal file
62
integration-tests/stdout-stderr-output.test.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* Copyright 2025 Google LLC
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { TestRig } from './test-helper.js';
|
||||||
|
|
||||||
|
describe('stdout-stderr-output', () => {
|
||||||
|
let rig: TestRig;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
rig = new TestRig();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await rig.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should send model response to stdout and app messages to stderr', async ({
|
||||||
|
signal,
|
||||||
|
}) => {
|
||||||
|
await rig.setup('prompt-output-test', {
|
||||||
|
fakeResponsesPath: join(
|
||||||
|
import.meta.dirname,
|
||||||
|
'stdout-stderr-output.responses',
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { stdout, exitCode } = await rig.runWithStreams(['-p', 'Say hello'], {
|
||||||
|
signal,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(exitCode).toBe(0);
|
||||||
|
expect(stdout.toLowerCase()).toContain('hello');
|
||||||
|
expect(stdout).not.toMatch(/^\[ERROR\]/m);
|
||||||
|
expect(stdout).not.toMatch(/^\[INFO\]/m);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing file with message to stdout and error to stderr', async ({
|
||||||
|
signal,
|
||||||
|
}) => {
|
||||||
|
await rig.setup('error-output-test', {
|
||||||
|
fakeResponsesPath: join(
|
||||||
|
import.meta.dirname,
|
||||||
|
'stdout-stderr-output-error.responses',
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const { stdout, exitCode } = await rig.runWithStreams(
|
||||||
|
['-p', '@nonexistent-file-that-does-not-exist.txt explain this'],
|
||||||
|
{ signal },
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(exitCode).toBe(0);
|
||||||
|
expect(stdout.toLowerCase()).toMatch(
|
||||||
|
/could not find|not exist|does not exist/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -555,6 +555,48 @@ export class TestRig {
|
|||||||
return filteredLines.join('\n');
|
return filteredLines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the CLI and returns stdout and stderr separately.
|
||||||
|
* Useful for tests that need to verify correct stream routing.
|
||||||
|
*/
|
||||||
|
runWithStreams(
|
||||||
|
args: string[],
|
||||||
|
options?: { signal?: AbortSignal },
|
||||||
|
): Promise<{ stdout: string; stderr: string; exitCode: number | null }> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const { command, initialArgs } = this._getCommandAndArgs([
|
||||||
|
'--approval-mode=yolo',
|
||||||
|
]);
|
||||||
|
|
||||||
|
const allArgs = [...initialArgs, ...args];
|
||||||
|
|
||||||
|
const child = spawn(command, allArgs, {
|
||||||
|
cwd: this.testDir!,
|
||||||
|
stdio: 'pipe',
|
||||||
|
env: { ...process.env, GEMINI_CLI_HOME: this.homeDir! },
|
||||||
|
signal: options?.signal,
|
||||||
|
});
|
||||||
|
this._spawnedProcesses.push(child);
|
||||||
|
|
||||||
|
let stdout = '';
|
||||||
|
let stderr = '';
|
||||||
|
|
||||||
|
child.on('error', reject);
|
||||||
|
|
||||||
|
child.stdout!.on('data', (chunk) => {
|
||||||
|
stdout += chunk;
|
||||||
|
});
|
||||||
|
child.stderr!.on('data', (chunk) => {
|
||||||
|
stderr += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stdin!.end();
|
||||||
|
child.on('close', (exitCode) => {
|
||||||
|
resolve({ stdout, stderr, exitCode });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
runCommand(
|
runCommand(
|
||||||
args: string[],
|
args: string[],
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
Reference in New Issue
Block a user