Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/simulation/nesCoffeTests.ts
13389 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
import * as fs from 'fs';
6
import * as path from 'path';
7
import { IRecordingInformation } from '../../src/extension/inlineEdits/common/observableWorkspaceRecordingReplayer';
8
import { LogEntry, serializeEdit } from '../../src/platform/workspaceRecorder/common/workspaceLog';
9
import { assert } from '../../src/util/vs/base/common/assert';
10
import { assertDefined } from '../../src/util/vs/base/common/types';
11
import { StringEdit, StringReplacement } from '../../src/util/vs/editor/common/core/edits/stringEdit';
12
import { OffsetRange } from '../../src/util/vs/editor/common/core/ranges/offsetRange';
13
import { SimulationOptions } from '../base/simulationOptions';
14
import { Configuration, SimulationSuite, SimulationTest } from '../base/stest';
15
import { InlineEditTester } from './inlineEdit/inlineEditTester';
16
import { CompletionStests } from './nesCoffeTestsTypes';
17
import { nesOptionsToConfigurations } from './nesOptionsToConfigurations';
18
19
const TEST_FILE_SUFFIX = '.completion.yml';
20
const RESULT_FILE_SUFFIX = '.response.json';
21
22
export async function discoverCoffeTests(rootFolder: string, options: SimulationOptions) {
23
const rootFolderContents = await fs.promises.readdir(rootFolder, { withFileTypes: true });
24
25
const recordingFiles = rootFolderContents.filter(fileEntry => fileEntry.isFile() && fileEntry.name.endsWith(TEST_FILE_SUFFIX));
26
27
const tester = new InlineEditTester();
28
29
const configurations = nesOptionsToConfigurations(options);
30
31
const rootSuite = new SimulationSuite({ title: 'NES', location: 'external' });
32
33
let tests = recordingFiles.map((file) => generateExternalStestFromRecording(file, rootSuite, tester, configurations));
34
35
tests = tests.sort((a, b) => a.fullName.localeCompare(b.fullName));
36
37
rootSuite.tests.push(...tests);
38
39
return rootSuite;
40
}
41
42
function generateExternalStestFromRecording(file: fs.Dirent<string>, containingSuite: SimulationSuite, tester: InlineEditTester, configurations: Configuration<unknown>[]): SimulationTest {
43
44
const basename = file.name;
45
const testName = basename.slice(0, -TEST_FILE_SUFFIX.length); // strip suffix
46
const filePath = path.join(file.parentPath, basename);
47
48
49
const stest = new SimulationTest({ description: testName, configurations }, {}, containingSuite, async (collection) => {
50
const accessor = collection.createTestingAccessor();
51
52
const fileContents = fs.readFileSync(filePath, 'utf8');
53
const testInput = CompletionStests.parseTestInput(fileContents);
54
55
const recordingLog: LogEntry[] = [
56
{
57
documentType: '[email protected]',
58
kind: 'header',
59
repoRootUri: 'file:///Users/john/myProject/',
60
time: Date.now(),
61
uuid: 'random-uuid-1234',
62
},
63
];
64
65
const filesWithoutTargetFile = testInput.state.openFiles.filter(f => f.uri !== testInput.completion.uri);
66
67
const targetFile = testInput.state.openFiles.find(f => f.uri === testInput.completion.uri);
68
assertDefined(targetFile, `Target file ${testInput.completion.uri} not found in open files.`);
69
70
const targetFileId = filesWithoutTargetFile.length; // careful: needs to be in sync with loop logic
71
72
const { targetFileBeforeEdit, edit } = computeTargetFileBeforeEditAndEdit(targetFile);
73
74
let id = 0;
75
for (const openFile of [...filesWithoutTargetFile, targetFile]) {
76
const currentFileId = id++;
77
const date = Date.now();
78
recordingLog.push({
79
kind: 'documentEncountered',
80
id: currentFileId,
81
relativePath: openFile.uri,
82
time: date,
83
});
84
85
recordingLog.push({
86
kind: 'setContent',
87
id: currentFileId,
88
v: 1,
89
content: openFile === targetFile ? targetFileBeforeEdit : openFile.text,
90
time: date,
91
});
92
93
recordingLog.push({
94
kind: 'opened',
95
id: currentFileId,
96
time: date,
97
});
98
}
99
100
recordingLog.push({
101
kind: 'changed',
102
id: targetFileId,
103
time: Date.now(),
104
v: 2,
105
edit: serializeEdit(edit)
106
});
107
108
const recording: IRecordingInformation = {
109
log: recordingLog,
110
};
111
112
const r = await tester.runTestFromRecording(accessor, recording);
113
114
const completions: CompletionStests.TestCompletion[] = [];
115
116
if (r.aiRootedEdit && r.aiRootedEdit.edit.replacements.length > 0) {
117
const rootedEdit = r.aiRootedEdit;
118
const singleEdit = rootedEdit.edit.replacements[0];
119
120
const baseTrans = rootedEdit.base.getTransformer();
121
const start = baseTrans.getPosition(singleEdit.replaceRange.start);
122
const end = baseTrans.getPosition(singleEdit.replaceRange.endExclusive);
123
124
const trimmedEdit = rootedEdit.edit.removeCommonSuffixAndPrefix(rootedEdit.base.value);
125
126
completions.push({
127
insertText: singleEdit.newText,
128
displayText: trimmedEdit.replacements.at(0)?.newText ?? '<edits disappeared during trimming>',
129
range: {
130
start: {
131
line: start.lineNumber - 1,
132
character: start.column - 1,
133
},
134
end: {
135
line: end.lineNumber - 1,
136
character: end.column - 1
137
}
138
},
139
});
140
}
141
142
const completionsOutput: CompletionStests.TestOutput = {
143
completions,
144
};
145
146
const resultFilePath = path.join(file.parentPath, `${testName}${RESULT_FILE_SUFFIX}`);
147
148
await fs.promises.writeFile(resultFilePath, JSON.stringify(completionsOutput, null, 2));
149
});
150
151
return stest;
152
}
153
154
function computeTargetFileBeforeEditAndEdit(targetFile: CompletionStests.TestDocument): { targetFileBeforeEdit: string; edit: StringEdit } {
155
const cursorOffset = targetFile.text.indexOf('⮑');
156
assert(cursorOffset !== -1, 'Cursor marker ⮑ not found in target file text.');
157
158
const targetFileWithoutCursor = targetFile.text.replace('⮑', '');
159
let wordAtCursorStartOffset = cursorOffset - 1;
160
while (wordAtCursorStartOffset > 0 && /(\w|\.)/.test(targetFileWithoutCursor[wordAtCursorStartOffset - 1])) {
161
wordAtCursorStartOffset--;
162
}
163
const editToRemoveWordAtCursor = StringEdit.create([StringReplacement.delete(new OffsetRange(wordAtCursorStartOffset, cursorOffset))]);
164
const editToInsertWordAtCursor = editToRemoveWordAtCursor.inverse(targetFileWithoutCursor);
165
const targetFileBeforeEdit = editToRemoveWordAtCursor.apply(targetFileWithoutCursor);
166
return { targetFileBeforeEdit, edit: editToInsertWordAtCursor };
167
}
168
169