mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-19 09:33:53 +00:00
Merge tag 'v0.1.21' of github.com:google-gemini/gemini-cli into chore/sync-gemini-cli-v0.1.21
This commit is contained in:
@@ -25,7 +25,6 @@ vi.mock('../utils/summarizer.js');
|
||||
|
||||
import { isCommandAllowed } from '../utils/shell-utils.js';
|
||||
import { ShellTool } from './shell.js';
|
||||
import { ToolErrorType } from './tool-error.js';
|
||||
import { type Config } from '../config/config.js';
|
||||
import {
|
||||
type ShellExecutionResult,
|
||||
@@ -98,22 +97,25 @@ describe('ShellTool', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateToolParams', () => {
|
||||
it('should return null for a valid command', () => {
|
||||
expect(shellTool.validateToolParams({ command: 'ls -l' })).toBeNull();
|
||||
describe('build', () => {
|
||||
it('should return an invocation for a valid command', () => {
|
||||
const invocation = shellTool.build({ command: 'ls -l' });
|
||||
expect(invocation).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return an error for an empty command', () => {
|
||||
expect(shellTool.validateToolParams({ command: ' ' })).toBe(
|
||||
it('should throw an error for an empty command', () => {
|
||||
expect(() => shellTool.build({ command: ' ' })).toThrow(
|
||||
'Command cannot be empty.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return an error for a non-existent directory', () => {
|
||||
it('should throw an error for a non-existent directory', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
expect(
|
||||
shellTool.validateToolParams({ command: 'ls', directory: 'rel/path' }),
|
||||
).toBe("Directory 'rel/path' is not a registered workspace directory.");
|
||||
expect(() =>
|
||||
shellTool.build({ command: 'ls', directory: 'rel/path' }),
|
||||
).toThrow(
|
||||
"Directory 'rel/path' is not a registered workspace directory.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -139,10 +141,8 @@ describe('ShellTool', () => {
|
||||
};
|
||||
|
||||
it('should wrap command on linux and parse pgrep output', async () => {
|
||||
const promise = shellTool.execute(
|
||||
{ command: 'my-command &' },
|
||||
mockAbortSignal,
|
||||
);
|
||||
const invocation = shellTool.build({ command: 'my-command &' });
|
||||
const promise = invocation.execute(mockAbortSignal);
|
||||
resolveShellExecution({ pid: 54321 });
|
||||
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
@@ -164,8 +164,9 @@ describe('ShellTool', () => {
|
||||
|
||||
it('should not wrap command on windows', async () => {
|
||||
vi.mocked(os.platform).mockReturnValue('win32');
|
||||
const promise = shellTool.execute({ command: 'dir' }, mockAbortSignal);
|
||||
resolveExecutionPromise({
|
||||
const invocation = shellTool.build({ command: 'dir' });
|
||||
const promise = invocation.execute(mockAbortSignal);
|
||||
resolveShellExecution({
|
||||
rawOutput: Buffer.from(''),
|
||||
output: '',
|
||||
stdout: '',
|
||||
@@ -187,10 +188,8 @@ describe('ShellTool', () => {
|
||||
|
||||
it('should format error messages correctly', async () => {
|
||||
const error = new Error('wrapped command failed');
|
||||
const promise = shellTool.execute(
|
||||
{ command: 'user-command' },
|
||||
mockAbortSignal,
|
||||
);
|
||||
const invocation = shellTool.build({ command: 'user-command' });
|
||||
const promise = invocation.execute(mockAbortSignal);
|
||||
resolveShellExecution({
|
||||
error,
|
||||
exitCode: 1,
|
||||
@@ -209,40 +208,19 @@ describe('ShellTool', () => {
|
||||
expect(result.llmContent).not.toContain('pgrep');
|
||||
});
|
||||
|
||||
it('should return error with error property for invalid parameters', async () => {
|
||||
const result = await shellTool.execute(
|
||||
{ command: '' }, // Empty command is invalid
|
||||
mockAbortSignal,
|
||||
it('should throw an error for invalid parameters', () => {
|
||||
expect(() => shellTool.build({ command: '' })).toThrow(
|
||||
'Command cannot be empty.',
|
||||
);
|
||||
|
||||
expect(result.llmContent).toContain(
|
||||
'Could not execute command due to invalid parameters:',
|
||||
);
|
||||
expect(result.returnDisplay).toBe('Command cannot be empty.');
|
||||
expect(result.error).toEqual({
|
||||
message: 'Command cannot be empty.',
|
||||
type: ToolErrorType.INVALID_TOOL_PARAMS,
|
||||
});
|
||||
});
|
||||
|
||||
it('should return error with error property for invalid directory', async () => {
|
||||
it('should throw an error for invalid directory', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
const result = await shellTool.execute(
|
||||
{ command: 'ls', directory: 'nonexistent' },
|
||||
mockAbortSignal,
|
||||
expect(() =>
|
||||
shellTool.build({ command: 'ls', directory: 'nonexistent' }),
|
||||
).toThrow(
|
||||
`Directory 'nonexistent' is not a registered workspace directory.`,
|
||||
);
|
||||
|
||||
expect(result.llmContent).toContain(
|
||||
'Could not execute command due to invalid parameters:',
|
||||
);
|
||||
expect(result.returnDisplay).toBe(
|
||||
"Directory 'nonexistent' is not a registered workspace directory.",
|
||||
);
|
||||
expect(result.error).toEqual({
|
||||
message:
|
||||
"Directory 'nonexistent' is not a registered workspace directory.",
|
||||
type: ToolErrorType.INVALID_TOOL_PARAMS,
|
||||
});
|
||||
});
|
||||
|
||||
it('should summarize output when configured', async () => {
|
||||
@@ -253,7 +231,8 @@ describe('ShellTool', () => {
|
||||
'summarized output',
|
||||
);
|
||||
|
||||
const promise = shellTool.execute({ command: 'ls' }, mockAbortSignal);
|
||||
const invocation = shellTool.build({ command: 'ls' });
|
||||
const promise = invocation.execute(mockAbortSignal);
|
||||
resolveExecutionPromise({
|
||||
output: 'long output',
|
||||
rawOutput: Buffer.from('long output'),
|
||||
@@ -285,9 +264,8 @@ describe('ShellTool', () => {
|
||||
});
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true); // Pretend the file exists
|
||||
|
||||
await expect(
|
||||
shellTool.execute({ command: 'a-command' }, mockAbortSignal),
|
||||
).rejects.toThrow(error);
|
||||
const invocation = shellTool.build({ command: 'a-command' });
|
||||
await expect(invocation.execute(mockAbortSignal)).rejects.toThrow(error);
|
||||
|
||||
const tmpFile = path.join(os.tmpdir(), 'shell_pgrep_abcdef.tmp');
|
||||
expect(vi.mocked(fs.unlinkSync)).toHaveBeenCalledWith(tmpFile);
|
||||
@@ -304,11 +282,8 @@ describe('ShellTool', () => {
|
||||
});
|
||||
|
||||
it('should throttle text output updates', async () => {
|
||||
const promise = shellTool.execute(
|
||||
{ command: 'stream' },
|
||||
mockAbortSignal,
|
||||
updateOutputMock,
|
||||
);
|
||||
const invocation = shellTool.build({ command: 'stream' });
|
||||
const promise = invocation.execute(mockAbortSignal, updateOutputMock);
|
||||
|
||||
// First chunk, should be throttled.
|
||||
mockShellOutputCallback({
|
||||
@@ -347,11 +322,8 @@ describe('ShellTool', () => {
|
||||
});
|
||||
|
||||
it('should immediately show binary detection message and throttle progress', async () => {
|
||||
const promise = shellTool.execute(
|
||||
{ command: 'cat img' },
|
||||
mockAbortSignal,
|
||||
updateOutputMock,
|
||||
);
|
||||
const invocation = shellTool.build({ command: 'cat img' });
|
||||
const promise = invocation.execute(mockAbortSignal, updateOutputMock);
|
||||
|
||||
mockShellOutputCallback({ type: 'binary_detected' });
|
||||
expect(updateOutputMock).toHaveBeenCalledOnce();
|
||||
@@ -399,8 +371,8 @@ describe('ShellTool', () => {
|
||||
describe('shouldConfirmExecute', () => {
|
||||
it('should request confirmation for a new command and whitelist it on "Always"', async () => {
|
||||
const params = { command: 'npm install' };
|
||||
const confirmation = await shellTool.shouldConfirmExecute(
|
||||
params,
|
||||
const invocation = shellTool.build(params);
|
||||
const confirmation = await invocation.shouldConfirmExecute(
|
||||
new AbortController().signal,
|
||||
);
|
||||
|
||||
@@ -413,19 +385,15 @@ describe('ShellTool', () => {
|
||||
);
|
||||
|
||||
// Should now be whitelisted
|
||||
const secondConfirmation = await shellTool.shouldConfirmExecute(
|
||||
{ command: 'npm test' },
|
||||
const secondInvocation = shellTool.build({ command: 'npm test' });
|
||||
const secondConfirmation = await secondInvocation.shouldConfirmExecute(
|
||||
new AbortController().signal,
|
||||
);
|
||||
expect(secondConfirmation).toBe(false);
|
||||
});
|
||||
|
||||
it('should skip confirmation if validation fails', async () => {
|
||||
const confirmation = await shellTool.shouldConfirmExecute(
|
||||
{ command: '' },
|
||||
new AbortController().signal,
|
||||
);
|
||||
expect(confirmation).toBe(false);
|
||||
it('should throw an error if validation fails', () => {
|
||||
expect(() => shellTool.build({ command: '' })).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -581,8 +549,8 @@ describe('validateToolParams', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateToolParams', () => {
|
||||
it('should return null for valid directory', () => {
|
||||
describe('build', () => {
|
||||
it('should return an invocation for valid directory', () => {
|
||||
const config = {
|
||||
getCoreTools: () => undefined,
|
||||
getExcludeTools: () => undefined,
|
||||
@@ -591,14 +559,14 @@ describe('validateToolParams', () => {
|
||||
createMockWorkspaceContext('/root', ['/users/test']),
|
||||
} as unknown as Config;
|
||||
const shellTool = new ShellTool(config);
|
||||
const result = shellTool.validateToolParams({
|
||||
const invocation = shellTool.build({
|
||||
command: 'ls',
|
||||
directory: 'test',
|
||||
});
|
||||
expect(result).toBeNull();
|
||||
expect(invocation).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return error for directory outside workspace', () => {
|
||||
it('should throw an error for directory outside workspace', () => {
|
||||
const config = {
|
||||
getCoreTools: () => undefined,
|
||||
getExcludeTools: () => undefined,
|
||||
@@ -607,10 +575,11 @@ describe('validateToolParams', () => {
|
||||
createMockWorkspaceContext('/root', ['/users/test']),
|
||||
} as unknown as Config;
|
||||
const shellTool = new ShellTool(config);
|
||||
const result = shellTool.validateToolParams({
|
||||
command: 'ls',
|
||||
directory: 'test2',
|
||||
});
|
||||
expect(result).toContain('is not a registered workspace directory');
|
||||
expect(() =>
|
||||
shellTool.build({
|
||||
command: 'ls',
|
||||
directory: 'test2',
|
||||
}),
|
||||
).toThrow('is not a registered workspace directory');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user