Path: blob/main/extensions/copilot/test/e2e/testHelper.ts
13388 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 type { ChatLanguageModelToolReference, ChatPromptReference } from 'vscode';6import { IToolsService } from '../../src/extension/tools/common/toolsService';7import { ITestingServicesAccessor } from '../../src/platform/test/node/services';8import { SimulationWorkspace } from '../../src/platform/test/node/simulationWorkspace';9import { basename } from '../../src/util/vs/base/common/resources';10import { Location, Uri } from '../../src/vscodeTypes';11import { IConversationTestCase } from './scenarioLoader';1213export interface IParsedQuery {14query: string;15participantName: string | undefined;16command?: string;17variables: ChatPromptReference[];18toolReferences: ChatLanguageModelToolReference[];19}2021/**22* This has to recreate some of the variable parsing logic in VS Code so that we can write tests easily with variables23* as strings, but then provide the extension code with the parsed variables in the extension API format.24*/25export async function parseQueryForScenarioTest(accessor: ITestingServicesAccessor, testCase: IConversationTestCase, simulationWorkspace: SimulationWorkspace): Promise<IParsedQuery> {26const query = await parseQueryForTest(accessor, testCase.question, simulationWorkspace);2728// Simulate implicit context enablement29const activeTextEditor = simulationWorkspace.activeTextEditor;30if (activeTextEditor) {31const selection = activeTextEditor.selections?.[0];32if (selection) {33query.variables.push({34id: 'vscode.implicit',35name: `file:${basename(activeTextEditor.document.uri)}`,36value: new Location(activeTextEditor.document.uri, selection),37modelDescription: `User's active selection`38});39} else {40query.variables.push({41id: 'vscode.implicit',42name: `file:${basename(activeTextEditor.document.uri)}`,43value: activeTextEditor.document.uri,44modelDescription: `User's active file`45});46}47}4849return query;50}5152export function createWorkingSetFileVariable(uri: Uri) {53return {54id: 'copilot.file',55name: `file:${basename(uri)}`,56value: uri,57};58}5960export function parseQueryForTest(accessor: ITestingServicesAccessor, query: string, simulationWorkspace: SimulationWorkspace): IParsedQuery {61const variableReg = /#([\w_\-]+)(?::(\S+))?(?=(\s|$|\b))/ig;6263const toolsService = accessor.get(IToolsService);6465const match = query.match(/(?:@(\S+))?\s*(?:\/(\S+))?(.*)/);66let command: string | undefined;67let participantName: string | undefined;68if (match) {69participantName = match[1];70command = match[2];71query = match[3]?.trim() || '';72}7374const variables: ChatPromptReference[] = [];75const toolReferences: ChatLanguageModelToolReference[] = [];76let varMatch: RegExpMatchArray | null;77while (varMatch = variableReg.exec(query)) {78const [_, varName, arg] = varMatch;79const range: [number, number] = [varMatch.index!, varMatch.index! + varMatch[0].length];80if (varName === 'file') {81const value = parseFileVariables(simulationWorkspace, arg);82const varWithArg = `${varName}:${arg}`;83variables.push({ id: `copilot.${varName}`, name: varWithArg, range, value });84} else {85const tool = toolsService.getToolByToolReferenceName(varName);86if (tool) {87toolReferences.push(tool);88}89}90}9192variables.sort((a, b) => (b.range?.[0] ?? 0) - (a.range?.[0] ?? 0));9394return {95query,96participantName,97command,98variables,99toolReferences100};101}102103function parseFileVariables(simulationWorkspace: SimulationWorkspace, filePath: string): Uri {104for (const doc of simulationWorkspace.documents) {105if (basename(doc.document.uri) === filePath) {106return doc.document.uri;107}108}109return Uri.joinPath(simulationWorkspace.workspaceFolders[0], filePath);110}111112113