test: add integration test to verify stdout/stderr routing (#17280)

Co-authored-by: Allen Hutchison <adh@google.com>
This commit is contained in:
Vedant Mahajan
2026-01-29 05:31:57 +05:30
committed by GitHub
parent 0b169e9867
commit 9bb175a506
4 changed files with 106 additions and 0 deletions

View 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}]}}]}

View 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}]}}]}

View 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/,
);
});
});

View File

@@ -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: {