Path: blob/main/src/vs/platform/agentHost/test/common/commandLineHelpers.test.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import assert from 'assert';6import { isWindows } from '../../../../base/common/platform.js';7import { URI } from '../../../../base/common/uri.js';8import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';9import { extractCdPrefix, stripRedundantCdPrefix } from '../../common/commandLineHelpers.js';1011suite('extractCdPrefix', () => {1213ensureNoDisposablesAreLeakedInTestSuite();1415const cases: Array<{16name: string;17commandLine: string;18isPowerShell: boolean;19expected: { directory: string; command: string } | undefined;20}> = [21// bash matches22{ name: 'bash: simple cd', commandLine: 'cd /tmp && ls', isPowerShell: false, expected: { directory: '/tmp', command: 'ls' } },23{ name: 'bash: quoted dir', commandLine: 'cd "/path with spaces" && ls -la', isPowerShell: false, expected: { directory: '/path with spaces', command: 'ls -la' } },24{ name: 'bash: extra spaces after &&', commandLine: 'cd /tmp && echo hi', isPowerShell: false, expected: { directory: '/tmp', command: 'echo hi' } },2526// bash non-matches27{ name: 'bash: no cd prefix', commandLine: 'ls -la', isPowerShell: false, expected: undefined },28{ name: 'bash: cd with semicolon (not allowed in bash variant)', commandLine: 'cd /tmp; ls', isPowerShell: false, expected: undefined },29{ name: 'bash: cd alone', commandLine: 'cd /tmp', isPowerShell: false, expected: undefined },30{ name: 'bash: Set-Location not bash', commandLine: 'Set-Location /tmp && ls', isPowerShell: false, expected: undefined },3132// powershell matches33{ name: 'pwsh: cd && ', commandLine: 'cd C:\\foo && dir', isPowerShell: true, expected: { directory: 'C:\\foo', command: 'dir' } },34{ name: 'pwsh: cd ;', commandLine: 'cd C:\\foo; dir', isPowerShell: true, expected: { directory: 'C:\\foo', command: 'dir' } },35{ name: 'pwsh: cd /d', commandLine: 'cd /d C:\\foo && dir', isPowerShell: true, expected: { directory: 'C:\\foo', command: 'dir' } },36{ name: 'pwsh: Set-Location', commandLine: 'Set-Location C:\\foo; dir', isPowerShell: true, expected: { directory: 'C:\\foo', command: 'dir' } },37{ name: 'pwsh: Set-Location -Path', commandLine: 'Set-Location -Path C:\\foo && dir', isPowerShell: true, expected: { directory: 'C:\\foo', command: 'dir' } },38{ name: 'pwsh: quoted dir', commandLine: 'cd "C:\\path with spaces"; dir', isPowerShell: true, expected: { directory: 'C:\\path with spaces', command: 'dir' } },39{ name: 'pwsh: case insensitive', commandLine: 'CD C:\\foo && dir', isPowerShell: true, expected: { directory: 'C:\\foo', command: 'dir' } },4041// powershell non-matches42{ name: 'pwsh: no cd prefix', commandLine: 'dir', isPowerShell: true, expected: undefined },43{ name: 'pwsh: cd alone', commandLine: 'cd C:\\foo', isPowerShell: true, expected: undefined },44];4546for (const tc of cases) {47test(tc.name, () => {48const result = extractCdPrefix(tc.commandLine, tc.isPowerShell);49assert.deepStrictEqual(result, tc.expected);50});51}52});5354suite('stripRedundantCdPrefix', () => {5556ensureNoDisposablesAreLeakedInTestSuite();5758const wd = URI.file('/repo/project');5960test('rewrites bash command when cd matches working directory', () => {61const params: Record<string, unknown> = { command: 'cd /repo/project && npm test' };62const changed = stripRedundantCdPrefix('bash', params, wd);63assert.strictEqual(changed, true);64assert.strictEqual(params.command, 'npm test');65});6667test('rewrites bash command tolerating trailing slash', () => {68const params: Record<string, unknown> = { command: 'cd /repo/project/ && ls' };69const changed = stripRedundantCdPrefix('bash', params, wd);70assert.strictEqual(changed, true);71assert.strictEqual(params.command, 'ls');72});7374test('does not rewrite when cd target differs', () => {75const params: Record<string, unknown> = { command: 'cd /tmp && ls' };76const changed = stripRedundantCdPrefix('bash', params, wd);77assert.strictEqual(changed, false);78assert.strictEqual(params.command, 'cd /tmp && ls');79});8081test('does not rewrite for non-shell tools', () => {82const params: Record<string, unknown> = { command: 'cd /repo/project && ls' };83const changed = stripRedundantCdPrefix('read_file', params, wd);84assert.strictEqual(changed, false);85assert.strictEqual(params.command, 'cd /repo/project && ls');86});8788test('handles missing working directory', () => {89const params: Record<string, unknown> = { command: 'cd /repo/project && ls' };90const changed = stripRedundantCdPrefix('bash', params, undefined);91assert.strictEqual(changed, false);92});9394test('handles missing parameters', () => {95const changed = stripRedundantCdPrefix('bash', undefined, wd);96assert.strictEqual(changed, false);97});9899test('handles non-string command', () => {100const params: Record<string, unknown> = { command: 42 };101const changed = stripRedundantCdPrefix('bash', params, wd);102assert.strictEqual(changed, false);103});104105test('rewrites powershell with semicolon separator', () => {106const params: Record<string, unknown> = { command: 'cd /repo/project; dir' };107const changed = stripRedundantCdPrefix('powershell', params, wd);108assert.strictEqual(changed, true);109assert.strictEqual(params.command, 'dir');110});111112test('matches mixed path separators (forward-slash extracted vs native fsPath wd)', () => {113// On Windows, the model may emit `cd C:/repo/project && …` while114// URI.file('C:\\repo\\project').fsPath uses backslashes. The helper115// must normalize separators so the prefix is still recognized.116// On POSIX, `C:\…` is not a meaningful path, so the cross-separator117// test only makes sense on Windows.118if (!isWindows) {119return;120}121const winWd = URI.file('C:\\repo\\project');122const params: Record<string, unknown> = { command: 'cd C:/repo/project && npm test' };123const changed = stripRedundantCdPrefix('bash', params, winWd);124assert.strictEqual(changed, true);125assert.strictEqual(params.command, 'npm test');126});127128test('matches backslash extracted dir against backslash native wd on Windows', () => {129// Inverse direction: the model emits backslashes and the native wd is130// also backslashes. This is the most common Windows case and must131// match without relying on POSIX-shape paths.132if (!isWindows) {133return;134}135const winWd = URI.file('C:\\repo\\project');136const params: Record<string, unknown> = { command: 'cd C:\\repo\\project && ls' };137const changed = stripRedundantCdPrefix('bash', params, winWd);138assert.strictEqual(changed, true);139assert.strictEqual(params.command, 'ls');140});141});142143144