Path: blob/main/extensions/copilot/src/extension/test/vscode-node/sanity.sanity-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 * as sinon from 'sinon';7import * as vscode from 'vscode';8import { SpyChatResponseStream } from '../../../util/common/test/mockChatResponseStream';9import { timeout } from '../../../util/vs/base/common/async';10import { CancellationToken } from '../../../util/vs/base/common/cancellation';11import { Event } from '../../../util/vs/base/common/event';12import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';13import { Intent } from '../../common/constants';14import { ConversationFeature } from '../../conversation/vscode-node/conversationFeature';15import { IConversationStore } from '../../conversationStore/node/conversationStore';16import { activate } from '../../extension/vscode-node/extension';17import { ChatParticipantRequestHandler } from '../../prompt/node/chatParticipantRequestHandler';18import { ContributedToolName } from '../../tools/common/toolNames';19import { IToolsService } from '../../tools/common/toolsService';20import { TestChatRequest } from '../node/testHelpers';2122/**23* Running these locally? You may have to run `npm run setup` again24*/2526suite('Copilot Chat Sanity Test', function () {27this.timeout(1000 * 60 * 1); // 1 minute2829let realInstaAccessor: IInstantiationService;30let realContext: vscode.ExtensionContext;31let sandbox: sinon.SinonSandbox;32const fakeToken = CancellationToken.None;33// Before everything, activate the extension34suiteSetup(async function () {35sandbox = sinon.createSandbox();36sandbox.stub(vscode.commands, 'registerCommand').returns({ dispose: () => { } });37sandbox.stub(vscode.workspace, 'registerFileSystemProvider').returns({ dispose: () => { } });38const extension = vscode.extensions.getExtension('Github.copilot-chat');39assert.ok(extension, 'Extension is not available');40realContext = await extension.activate();41assert.ok(realContext, '`extension.activate()` did not return context`');42assert.ok(realContext.extensionMode, 'extension context does not have `extensionMode`');43const activateResult = await activate(realContext, true);44assert.ok(activateResult, 'Activation result is not available');45// Assert that the activateResult is a service accessor46assert.strictEqual(typeof (activateResult as IInstantiationService).createInstance, 'function', 'createInstance is not a function');47assert.strictEqual(typeof (activateResult as IInstantiationService).invokeFunction, 'function', 'invokeFunction is not a function');48realInstaAccessor = activateResult as IInstantiationService;49});5051suiteTeardown(async function () {52sandbox.restore();53// Dispose of all subscriptions54realContext.subscriptions.forEach((sub) => {55try {56sub.dispose();57} catch (e) {58console.error(e);59}60});61});6263test('E2E Production Panel Chat Test', async function () {64assert.ok(realInstaAccessor, 'Instantiation service accessor is not available');6566await realInstaAccessor.invokeFunction(async (accessor) => {6768const conversationStore = accessor.get(IConversationStore);69const instaService = accessor.get(IInstantiationService);70const conversationFeature = instaService.createInstance(ConversationFeature);71try {72conversationFeature.activated = true;73let stream = new SpyChatResponseStream();74let interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('Write me a for loop in javascript'), stream, fakeToken, { agentName: '', agentId: '', intentId: '' }, () => false, undefined);7576await interactiveSession.getResult();7778assert.ok(stream.currentProgress, 'Expected progress after first request');79const oldText = stream.currentProgress;8081stream = new SpyChatResponseStream();82interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('Can you make it in typescript instead'), stream, fakeToken, { agentName: '', agentId: '', intentId: '' }, () => false, undefined);83const result2 = await interactiveSession.getResult();8485assert.ok(stream.currentProgress, 'Expected progress after second request');86assert.notStrictEqual(stream.currentProgress, oldText, 'Expected different progress text after second request');8788const conversation = conversationStore.getConversation(result2.metadata.responseId);89assert.ok(conversation, 'Expected conversation to be available');90} finally {91conversationFeature.activated = false;92}93});94});9596/**97* Runs tools outside of a real chat session which is unusual but lets us spy more98* Uses an empty window with no folder open99*/100test('E2E Production agent mode', async function () {101assert.ok(realInstaAccessor, 'Instantiation service accessor is not available');102103await realInstaAccessor.invokeFunction(async (accessor) => {104105const conversationStore = accessor.get(IConversationStore);106const instaService = accessor.get(IInstantiationService);107const toolsService = accessor.get(IToolsService);108const conversationFeature = instaService.createInstance(ConversationFeature);109try {110conversationFeature.activated = true;111let stream = new SpyChatResponseStream();112const testRequest = new TestChatRequest(`You must use the get_errors tool to check the window for errors. It may fail, that's ok, just testing, don't retry.`);113testRequest.tools.set(ContributedToolName.GetErrors, true);114let interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], testRequest, stream, fakeToken, { agentName: '', agentId: '', intentId: Intent.Agent }, () => false, undefined);115116const onWillInvokeTool = Event.toPromise(toolsService.onWillInvokeTool);117const getResultPromise = interactiveSession.getResult();118await Promise.race([onWillInvokeTool, timeout(20_000).then(() => Promise.reject(new Error('timed out waiting for tool call. ' + (stream.currentProgress ? ('Got progress: ' + stream.currentProgress) : ''))))]);119await getResultPromise;120121assert.ok(stream.currentProgress, 'Expected output');122const oldText = stream.currentProgress;123124stream = new SpyChatResponseStream();125interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('And what is 1+1'), stream, fakeToken, { agentName: '', agentId: '', intentId: Intent.Agent }, () => false, undefined);126const result2 = await interactiveSession.getResult();127128assert.ok(stream.currentProgress, 'Expected progress after second request');129assert.notStrictEqual(stream.currentProgress, oldText, 'Expected different progress text after second request');130131const conversation = conversationStore.getConversation(result2.metadata.responseId);132assert.ok(conversation, 'Expected conversation to be available');133} finally {134conversationFeature.activated = false;135}136});137});138139test('Slash Commands work properly', async function () {140assert.ok(realInstaAccessor);141142await realInstaAccessor.invokeFunction(async (accessor) => {143144const instaService = accessor.get(IInstantiationService);145const conversationFeature = instaService.createInstance(ConversationFeature);146try {147conversationFeature.activated = true;148const progressReport = new SpyChatResponseStream();149const interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('What is a fibonacci sequence?'), progressReport, fakeToken, { agentName: '', agentId: '', intentId: 'explain' }, () => false, undefined);150151// Ask a `/explain` question152await interactiveSession.getResult();153assert.ok(progressReport.currentProgress);154} finally {155conversationFeature.activated = false;156}157});158});159160test.skip('E2E Production Inline Chat Test', async function () {161assert.ok(realInstaAccessor);162163await realInstaAccessor.invokeFunction(async (accessor) => {164165const r = vscode.lm.registerLanguageModelChatProvider('test', new class implements vscode.LanguageModelChatProvider {166async provideLanguageModelChatInformation(options: { silent: boolean }, token: vscode.CancellationToken): Promise<vscode.LanguageModelChatInformation[]> {167return [{168id: 'test',169name: 'test',170family: 'test',171version: '0.0.0',172maxInputTokens: 1000,173maxOutputTokens: 1000,174requiresAuthorization: true,175capabilities: {}176}];177}178async provideLanguageModelChatResponse(model: vscode.LanguageModelChatInformation, messages: Array<vscode.LanguageModelChatMessage | vscode.LanguageModelChatMessage2>, options: vscode.ProvideLanguageModelChatResponseOptions, progress: vscode.Progress<vscode.LanguageModelResponsePart2>, token: vscode.CancellationToken): Promise<void> {179throw new Error('Method not implemented.');180}181async provideTokenCount(model: vscode.LanguageModelChatInformation, text: string | vscode.LanguageModelChatMessage | vscode.LanguageModelChatMessage2, token: vscode.CancellationToken): Promise<number> {182return 0;183}184});185186const instaService = accessor.get(IInstantiationService);187const conversationFeature = instaService.createInstance(ConversationFeature);188try {189conversationFeature.activated = true;190191// Create and open a new file192const document = await vscode.workspace.openTextDocument({ language: 'javascript' });193await vscode.window.showTextDocument(document);194195// Wait for a document change event or 10 seconds whatever comes first then assert the text196const textPromise = new Promise<string>((resolve, reject) => {197const listener = vscode.workspace.onDidChangeTextDocument(async (e) => {198if (e.document.uri.scheme !== 'untitled') {199return;200}201if (e.document.getText().length !== 0) {202listener.dispose();203resolve(e.document.getText());204}205});206});207208await vscode.commands.executeCommand('vscode.editorChat.start', {209autoSend: true,210message: 'Write me a for loop in javascript',211position: new vscode.Position(0, 0),212initialSelection: new vscode.Selection(0, 0, 0, 0),213initialRange: new vscode.Range(0, 0, 0, 0),214});215const text = await textPromise;216assert.ok(text.length > 0);217} finally {218conversationFeature.activated = false;219r.dispose();220}221});222});223});224225226