Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/test/vscode-node/sanity.sanity-test.ts
13399 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import * as sinon from 'sinon';
8
import * as vscode from 'vscode';
9
import { SpyChatResponseStream } from '../../../util/common/test/mockChatResponseStream';
10
import { timeout } from '../../../util/vs/base/common/async';
11
import { CancellationToken } from '../../../util/vs/base/common/cancellation';
12
import { Event } from '../../../util/vs/base/common/event';
13
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
14
import { Intent } from '../../common/constants';
15
import { ConversationFeature } from '../../conversation/vscode-node/conversationFeature';
16
import { IConversationStore } from '../../conversationStore/node/conversationStore';
17
import { activate } from '../../extension/vscode-node/extension';
18
import { ChatParticipantRequestHandler } from '../../prompt/node/chatParticipantRequestHandler';
19
import { ContributedToolName } from '../../tools/common/toolNames';
20
import { IToolsService } from '../../tools/common/toolsService';
21
import { TestChatRequest } from '../node/testHelpers';
22
23
/**
24
* Running these locally? You may have to run `npm run setup` again
25
*/
26
27
suite('Copilot Chat Sanity Test', function () {
28
this.timeout(1000 * 60 * 1); // 1 minute
29
30
let realInstaAccessor: IInstantiationService;
31
let realContext: vscode.ExtensionContext;
32
let sandbox: sinon.SinonSandbox;
33
const fakeToken = CancellationToken.None;
34
// Before everything, activate the extension
35
suiteSetup(async function () {
36
sandbox = sinon.createSandbox();
37
sandbox.stub(vscode.commands, 'registerCommand').returns({ dispose: () => { } });
38
sandbox.stub(vscode.workspace, 'registerFileSystemProvider').returns({ dispose: () => { } });
39
const extension = vscode.extensions.getExtension('Github.copilot-chat');
40
assert.ok(extension, 'Extension is not available');
41
realContext = await extension.activate();
42
assert.ok(realContext, '`extension.activate()` did not return context`');
43
assert.ok(realContext.extensionMode, 'extension context does not have `extensionMode`');
44
const activateResult = await activate(realContext, true);
45
assert.ok(activateResult, 'Activation result is not available');
46
// Assert that the activateResult is a service accessor
47
assert.strictEqual(typeof (activateResult as IInstantiationService).createInstance, 'function', 'createInstance is not a function');
48
assert.strictEqual(typeof (activateResult as IInstantiationService).invokeFunction, 'function', 'invokeFunction is not a function');
49
realInstaAccessor = activateResult as IInstantiationService;
50
});
51
52
suiteTeardown(async function () {
53
sandbox.restore();
54
// Dispose of all subscriptions
55
realContext.subscriptions.forEach((sub) => {
56
try {
57
sub.dispose();
58
} catch (e) {
59
console.error(e);
60
}
61
});
62
});
63
64
test('E2E Production Panel Chat Test', async function () {
65
assert.ok(realInstaAccessor, 'Instantiation service accessor is not available');
66
67
await realInstaAccessor.invokeFunction(async (accessor) => {
68
69
const conversationStore = accessor.get(IConversationStore);
70
const instaService = accessor.get(IInstantiationService);
71
const conversationFeature = instaService.createInstance(ConversationFeature);
72
try {
73
conversationFeature.activated = true;
74
let stream = new SpyChatResponseStream();
75
let interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('Write me a for loop in javascript'), stream, fakeToken, { agentName: '', agentId: '', intentId: '' }, () => false, undefined);
76
77
await interactiveSession.getResult();
78
79
assert.ok(stream.currentProgress, 'Expected progress after first request');
80
const oldText = stream.currentProgress;
81
82
stream = new SpyChatResponseStream();
83
interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('Can you make it in typescript instead'), stream, fakeToken, { agentName: '', agentId: '', intentId: '' }, () => false, undefined);
84
const result2 = await interactiveSession.getResult();
85
86
assert.ok(stream.currentProgress, 'Expected progress after second request');
87
assert.notStrictEqual(stream.currentProgress, oldText, 'Expected different progress text after second request');
88
89
const conversation = conversationStore.getConversation(result2.metadata.responseId);
90
assert.ok(conversation, 'Expected conversation to be available');
91
} finally {
92
conversationFeature.activated = false;
93
}
94
});
95
});
96
97
/**
98
* Runs tools outside of a real chat session which is unusual but lets us spy more
99
* Uses an empty window with no folder open
100
*/
101
test('E2E Production agent mode', async function () {
102
assert.ok(realInstaAccessor, 'Instantiation service accessor is not available');
103
104
await realInstaAccessor.invokeFunction(async (accessor) => {
105
106
const conversationStore = accessor.get(IConversationStore);
107
const instaService = accessor.get(IInstantiationService);
108
const toolsService = accessor.get(IToolsService);
109
const conversationFeature = instaService.createInstance(ConversationFeature);
110
try {
111
conversationFeature.activated = true;
112
let stream = new SpyChatResponseStream();
113
const 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.`);
114
testRequest.tools.set(ContributedToolName.GetErrors, true);
115
let interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], testRequest, stream, fakeToken, { agentName: '', agentId: '', intentId: Intent.Agent }, () => false, undefined);
116
117
const onWillInvokeTool = Event.toPromise(toolsService.onWillInvokeTool);
118
const getResultPromise = interactiveSession.getResult();
119
await Promise.race([onWillInvokeTool, timeout(20_000).then(() => Promise.reject(new Error('timed out waiting for tool call. ' + (stream.currentProgress ? ('Got progress: ' + stream.currentProgress) : ''))))]);
120
await getResultPromise;
121
122
assert.ok(stream.currentProgress, 'Expected output');
123
const oldText = stream.currentProgress;
124
125
stream = new SpyChatResponseStream();
126
interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('And what is 1+1'), stream, fakeToken, { agentName: '', agentId: '', intentId: Intent.Agent }, () => false, undefined);
127
const result2 = await interactiveSession.getResult();
128
129
assert.ok(stream.currentProgress, 'Expected progress after second request');
130
assert.notStrictEqual(stream.currentProgress, oldText, 'Expected different progress text after second request');
131
132
const conversation = conversationStore.getConversation(result2.metadata.responseId);
133
assert.ok(conversation, 'Expected conversation to be available');
134
} finally {
135
conversationFeature.activated = false;
136
}
137
});
138
});
139
140
test('Slash Commands work properly', async function () {
141
assert.ok(realInstaAccessor);
142
143
await realInstaAccessor.invokeFunction(async (accessor) => {
144
145
const instaService = accessor.get(IInstantiationService);
146
const conversationFeature = instaService.createInstance(ConversationFeature);
147
try {
148
conversationFeature.activated = true;
149
const progressReport = new SpyChatResponseStream();
150
const interactiveSession = instaService.createInstance(ChatParticipantRequestHandler, [], new TestChatRequest('What is a fibonacci sequence?'), progressReport, fakeToken, { agentName: '', agentId: '', intentId: 'explain' }, () => false, undefined);
151
152
// Ask a `/explain` question
153
await interactiveSession.getResult();
154
assert.ok(progressReport.currentProgress);
155
} finally {
156
conversationFeature.activated = false;
157
}
158
});
159
});
160
161
test.skip('E2E Production Inline Chat Test', async function () {
162
assert.ok(realInstaAccessor);
163
164
await realInstaAccessor.invokeFunction(async (accessor) => {
165
166
const r = vscode.lm.registerLanguageModelChatProvider('test', new class implements vscode.LanguageModelChatProvider {
167
async provideLanguageModelChatInformation(options: { silent: boolean }, token: vscode.CancellationToken): Promise<vscode.LanguageModelChatInformation[]> {
168
return [{
169
id: 'test',
170
name: 'test',
171
family: 'test',
172
version: '0.0.0',
173
maxInputTokens: 1000,
174
maxOutputTokens: 1000,
175
requiresAuthorization: true,
176
capabilities: {}
177
}];
178
}
179
async provideLanguageModelChatResponse(model: vscode.LanguageModelChatInformation, messages: Array<vscode.LanguageModelChatMessage | vscode.LanguageModelChatMessage2>, options: vscode.ProvideLanguageModelChatResponseOptions, progress: vscode.Progress<vscode.LanguageModelResponsePart2>, token: vscode.CancellationToken): Promise<void> {
180
throw new Error('Method not implemented.');
181
}
182
async provideTokenCount(model: vscode.LanguageModelChatInformation, text: string | vscode.LanguageModelChatMessage | vscode.LanguageModelChatMessage2, token: vscode.CancellationToken): Promise<number> {
183
return 0;
184
}
185
});
186
187
const instaService = accessor.get(IInstantiationService);
188
const conversationFeature = instaService.createInstance(ConversationFeature);
189
try {
190
conversationFeature.activated = true;
191
192
// Create and open a new file
193
const document = await vscode.workspace.openTextDocument({ language: 'javascript' });
194
await vscode.window.showTextDocument(document);
195
196
// Wait for a document change event or 10 seconds whatever comes first then assert the text
197
const textPromise = new Promise<string>((resolve, reject) => {
198
const listener = vscode.workspace.onDidChangeTextDocument(async (e) => {
199
if (e.document.uri.scheme !== 'untitled') {
200
return;
201
}
202
if (e.document.getText().length !== 0) {
203
listener.dispose();
204
resolve(e.document.getText());
205
}
206
});
207
});
208
209
await vscode.commands.executeCommand('vscode.editorChat.start', {
210
autoSend: true,
211
message: 'Write me a for loop in javascript',
212
position: new vscode.Position(0, 0),
213
initialSelection: new vscode.Selection(0, 0, 0, 0),
214
initialRange: new vscode.Range(0, 0, 0, 0),
215
});
216
const text = await textPromise;
217
assert.ok(text.length > 0);
218
} finally {
219
conversationFeature.activated = false;
220
r.dispose();
221
}
222
});
223
});
224
});
225
226