Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/simulation/outcomeValidators.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 assert from 'assert';
6
import { EXISTING_CODE_MARKER } from '../../src/extension/prompts/node/panel/codeBlockFormattingRules';
7
import { IAIEvaluationService } from '../../src/extension/testing/node/aiEvaluationService';
8
import { ITestingServicesAccessor } from '../../src/platform/test/node/services';
9
import { IFile, SimulationWorkspace } from '../../src/platform/test/node/simulationWorkspace';
10
import { CancellationToken } from '../../src/util/vs/base/common/cancellation';
11
import { basename } from '../../src/util/vs/base/common/resources';
12
import { splitLines } from '../../src/util/vs/base/common/strings';
13
import { URI } from '../../src/util/vs/base/common/uri';
14
import { getDiagnostics } from './diagnosticProviders';
15
import { DiagnosticsProvider, ITestDiagnostic } from './diagnosticProviders/diagnosticsProvider';
16
import { DiagnosticProviderId, IInlineEditOutcome, IOutcome, IWorkspaceEditOutcome, OutcomeAnnotation } from './types';
17
18
export function assertLooksLikeJSDoc(text: string): void {
19
text = text.trim();
20
assert(text.startsWith('/**') && text.endsWith('*/'), `expected jsdoc, but got:\n${text}`);
21
}
22
23
export function assertContainsAllSnippets(text: string, snippets: string[], dbgMsg: string = '') {
24
for (let i = 0, len = snippets.length; i < len; ++i) {
25
const snippet = snippets[i];
26
assert(text.indexOf(snippet) !== -1, `${dbgMsg} (contains snippet "${snippet}")`);
27
}
28
}
29
30
/**
31
* Searches for `marker1`, and then for `marker2` after `marker1`.
32
*/
33
export function findTextBetweenMarkersFromTop(text: string, marker1: string, marker2: string): string | null {
34
const index1 = text.indexOf(marker1);
35
if (index1 === -1) {
36
return null;
37
}
38
const index2 = text.indexOf(marker2, index1 + 1);
39
if (index2 === -1) {
40
return null;
41
}
42
return text.substring(index1 + marker1.length, index2);
43
}
44
45
46
/**
47
* Searches for `marker2`, and then for `marker1` before `marker2`.
48
*/
49
export function findTextBetweenMarkersFromBottom(text: string, marker1: string, marker2: string) {
50
const index2 = text.indexOf(marker2);
51
if (index2 === -1) {
52
return null;
53
}
54
const index1 = text.lastIndexOf(marker1, index2);
55
if (index1 === -1) {
56
return null;
57
}
58
return text.substring(index1 + marker1.length, index2);
59
}
60
61
62
/**
63
* This method validates the outcome by finding if after the edit, there remain errors
64
*/
65
export async function assertNoDiagnosticsAsync(accessor: ITestingServicesAccessor, outcome: IOutcome, workspace: SimulationWorkspace, method: DiagnosticProviderId | DiagnosticsProvider) {
66
assert.strictEqual(outcome.type, 'inlineEdit');
67
const diagnostics = await getWorkspaceDiagnostics(accessor, workspace, method);
68
if (diagnostics.length > 0) {
69
for (const diagnostic of diagnostics) {
70
if (diagnostic.message.indexOf('indent') !== -1) {
71
outcome.annotations.push({ label: 'indentation', message: diagnostic.message, severity: 'warning' });
72
}
73
}
74
}
75
assert.deepStrictEqual(diagnostics.length, 0, JSON.stringify(diagnostics, undefined, 2));
76
}
77
78
/**
79
* This method validates the outcome by finding if after the edit, there remain errors
80
*/
81
export async function assertNoSyntacticDiagnosticsAsync(accessor: ITestingServicesAccessor, outcome: IOutcome, workspace: SimulationWorkspace, method: DiagnosticProviderId | DiagnosticsProvider) {
82
assert.strictEqual(outcome.type, 'inlineEdit');
83
const diagnostics = await getWorkspaceDiagnostics(accessor, workspace, method);
84
const syntacticDiagnostics = diagnostics.filter(d => d.kind === 'syntactic');
85
86
assert.strictEqual(syntacticDiagnostics.length, 0, JSON.stringify(syntacticDiagnostics, undefined, 2));
87
}
88
89
/**
90
* This method validates the outcome by comparing the number of errors before and after
91
*/
92
export async function assertLessDiagnosticsAsync(accessor: ITestingServicesAccessor, outcome: IOutcome, workspace: SimulationWorkspace, method: DiagnosticProviderId) {
93
assert.strictEqual(outcome.type, 'inlineEdit');
94
const initialDiagnostics = outcome.initialDiagnostics;
95
assert.ok(initialDiagnostics);
96
let numberOfDiagnosticsInitially = 0;
97
for (const diagnostics of initialDiagnostics.values()) {
98
numberOfDiagnosticsInitially += diagnostics.length;
99
}
100
const diagnostics = await getWorkspaceDiagnostics(accessor, workspace, method);
101
const numberOfDiagnosticsAfter = diagnostics.length;
102
assert.ok(numberOfDiagnosticsAfter < numberOfDiagnosticsInitially);
103
}
104
105
/**
106
* Returns the diagnostics in all workspace files
107
*/
108
export async function getWorkspaceDiagnostics(accessor: ITestingServicesAccessor, workspace: SimulationWorkspace, method: DiagnosticProviderId | DiagnosticsProvider): Promise<ITestDiagnostic[]> {
109
const files = workspace.documents.map(doc => ({ fileName: workspace.getFilePath(doc.document.uri), fileContents: doc.document.getText() }));
110
if (typeof method === 'string') {
111
return await getDiagnostics(accessor, files, method);
112
} else {
113
return await method.getDiagnostics(accessor, files);
114
}
115
}
116
117
export function assertFileContent(files: IFile[] | Array<{ srcUri: string; post: string }>, fileName: string): string {
118
const existing = [];
119
for (const file of files) {
120
// Handle new format
121
if ('srcUri' in file && 'post' in file) {
122
// Convert string to URI if needed
123
const uri = typeof file.srcUri === 'string'
124
? URI.parse(file.srcUri)
125
: file.srcUri;
126
const name = basename(uri);
127
if (name === fileName) {
128
return file.post;
129
}
130
existing.push(name);
131
}
132
// Handle old format
133
else {
134
const name = file.kind === 'relativeFile' ? file.fileName : basename(file.uri);
135
if (name === fileName) {
136
return file.fileContents;
137
}
138
existing.push(name);
139
}
140
}
141
assert.fail(`Expected to find file ${fileName}. Files available: ${existing.join(', ')}`);
142
}
143
144
export function assertJSON(content: string): any {
145
try {
146
return JSON.parse(content);
147
} catch (e) {
148
assert.fail(`Expected JSON, but got: ${e.message}, ${content}`);
149
}
150
}
151
152
/**
153
* Helper function to get file content regardless of file format (old or new)
154
*/
155
export function getFileContent(file: IFile | { srcUri: string; post: string }): string {
156
if ('srcUri' in file && 'post' in file) {
157
// New format
158
return file.post;
159
} else if ('kind' in file && (file.kind === 'relativeFile' || file.kind === 'qualifiedFile')) {
160
// Old format
161
return file.fileContents;
162
} else {
163
throw new Error(`Unknown file format: ${JSON.stringify(file)}`);
164
}
165
}
166
167
export function assertNoElidedCodeComments(outcome: IInlineEditOutcome | IWorkspaceEditOutcome | string): void {
168
if (typeof outcome === 'string') {
169
assert.ok(outcome.indexOf(EXISTING_CODE_MARKER) === -1, 'Expected no elided code comments');
170
} else if (outcome.type === 'inlineEdit') {
171
assertNoElidedCodeComments(outcome.fileContents);
172
} else if (outcome.type === 'workspaceEdit') {
173
for (const file of outcome.files) {
174
// Use the helper function
175
assertNoElidedCodeComments(getFileContent(file));
176
}
177
}
178
}
179
180
export async function assertCriteriaMetAsync(accessor: ITestingServicesAccessor, response: string, criteria: string): Promise<void> {
181
const evaluationService = accessor.get(IAIEvaluationService);
182
const result = await evaluationService.evaluate(response, criteria, CancellationToken.None);
183
assert.ok(result.errorMessage === undefined, `Error: ${result.errorMessage}`);
184
}
185
186
export function validateConsistentIndentation(newText: string, insertSpaces: boolean, annotations: OutcomeAnnotation[]): void {
187
const indentationRegex = insertSpaces ? /^[ ]*[\S$]/ : /^\t*(\S|$|( \*))/; // special handling for Doc comments that start with ` *
188
const lines = splitLines(newText);
189
for (let i = 0; i < lines.length; i++) {
190
const line = lines[i];
191
if (line.length > 0 && !indentationRegex.test(line)) {
192
const message = `Expected line ${i} to start with ${insertSpaces ? 'spaces' : 'tabs'}: ${line}`;
193
annotations.push({ label: 'indentation', message, severity: 'warning' });
194
//assert.fail(message);
195
}
196
}
197
}
198
199