Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompt/node/test/testFiles.spec.ts
13405 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 { suite, test } from 'vitest';
8
import type * as vscode from 'vscode';
9
import { TextDocumentSnapshot } from '../../../../platform/editing/common/textDocumentSnapshot';
10
import { AbstractSearchService } from '../../../../platform/search/common/searchService';
11
import { ITabsAndEditorsService, TabChangeEvent, TabInfo } from '../../../../platform/tabs/common/tabsAndEditorsService';
12
import * as glob from '../../../../util/common/glob';
13
import { createTextDocumentData } from '../../../../util/common/test/shims/textDocument';
14
import { CancellationToken } from '../../../../util/vs/base/common/cancellation';
15
import { Event } from '../../../../util/vs/base/common/event';
16
import { normalize } from '../../../../util/vs/base/common/path';
17
import { basename } from '../../../../util/vs/base/common/resources';
18
import { URI } from '../../../../util/vs/base/common/uri';
19
import { TestFileFinder, isTestFile, suffix2Language } from '../testFiles';
20
21
suite.skipIf(process.platform === 'win32')('TestFileFinder', function () {
22
23
class TestSearchService extends AbstractSearchService {
24
override async findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, progress: vscode.Progress<vscode.TextSearchResult>, token: vscode.CancellationToken): Promise<vscode.TextSearchComplete> {
25
return {};
26
}
27
28
override findTextInFiles2(query: vscode.TextSearchQuery2, options?: vscode.FindTextInFilesOptions2, token?: vscode.CancellationToken): vscode.FindTextInFilesResponse {
29
return {} as vscode.FindTextInFilesResponse;
30
}
31
32
override async findFiles(filePattern: vscode.GlobPattern, options?: vscode.FindFiles2Options | undefined, token?: vscode.CancellationToken | undefined): Promise<vscode.Uri[]> {
33
return [];
34
}
35
}
36
37
class TestTabsService implements ITabsAndEditorsService {
38
declare _serviceBrand: undefined;
39
onDidChangeActiveTextEditor: vscode.Event<vscode.TextEditor | undefined> = Event.None;
40
onDidChangeTabs: vscode.Event<TabChangeEvent> = Event.None;
41
activeTextEditor: vscode.TextEditor | undefined = undefined;
42
visibleTextEditors: readonly vscode.TextEditor[] = [];
43
activeNotebookEditor: vscode.NotebookEditor | undefined = undefined;
44
visibleNotebookEditors: readonly vscode.NotebookEditor[] = [];
45
tabs: TabInfo[] = [];
46
}
47
48
test('returns undefined when no test file exists', async function () {
49
const testFileFinder = new TestFileFinder(new TestSearchService(), new TestTabsService());
50
const sourceFile = URI.file('/path/to/source/file.ts');
51
52
const result = await testFileFinder.findTestFileForSourceFile(createTextDocument(sourceFile), CancellationToken.None);
53
54
assert.deepStrictEqual(result, undefined);
55
});
56
57
test('uses tab info', async function () {
58
59
const sourceFile = URI.file('/path/to/source/file.ts');
60
const testFileFinder = new TestFileFinder(new TestSearchService(), new class extends TestTabsService {
61
override tabs: TabInfo[] = [{
62
uri: URI.file('/path/to/source/file.spec.ts'),
63
tab: null!
64
}];
65
});
66
67
const result = await testFileFinder.findTestFileForSourceFile(createTextDocument(sourceFile), CancellationToken.None);
68
assert.deepStrictEqual(result?.toString(), 'file:///path/to/source/file.spec.ts');
69
});
70
71
test('returns test file URI when it exists', async function () {
72
const sourceFile = '/path/to/source/file.ts';
73
const testFile = '/path/to/source/file.test.ts';
74
await assertTestFileFoundAsync(sourceFile, testFile);
75
});
76
77
const possibleTestNames = ['file.test.xx', 'file_test.xx', 'file.spec.xx', 'fileSpec.xx', 'test_file.xx'];
78
for (const testName of possibleTestNames) {
79
test(`for unknown languages, returns test file URI if it exists (${testName})`, async function () {
80
await assertTestFileFoundAsync('/path/file.xx', '/path/file.test.xx');
81
});
82
}
83
84
test('returns a test for Go', async function () {
85
const sourceFile = '/path/to/source/foo.go';
86
const testFile = '/path/to/source/foo_test.go';
87
await assertTestFileFoundAsync(sourceFile, testFile);
88
});
89
90
test('returns impl for Go', async function () {
91
const sourceFile = '/path/to/source/foo.go';
92
const testFile = '/path/to/source/foo_test.go';
93
await assertImplFileFoundAsync(testFile, sourceFile);
94
});
95
96
test('returns same folder with prefix as fallback', async function () {
97
const sourceFile = '/path/to/source/foo.go';
98
const existingTestFile = '/path/to/source/foo_test.go';
99
await assertTestFileFoundAsync(sourceFile, existingTestFile, '/path/to/source');
100
});
101
102
103
104
test('returns a test for Java for maven layout', async function () {
105
const sourceFile = '/src/main/java/p/Foo.java';
106
const testFile = '/src/test/java/p/FooTest.java';
107
await assertTestFileFoundAsync(sourceFile, testFile);
108
});
109
110
test('returns a impl for Java for maven layout', async function () {
111
const testFile = '/src/test/java/p/FooTest.java';
112
const sourceFile = '/src/main/java/p/Foo.java';
113
await assertImplFileFoundAsync(testFile, sourceFile);
114
});
115
116
test('returns a test for PHP', async function () {
117
const sourceFile = '/src/Foo.php';
118
const testFile = '/tests/FooTest.php';
119
await assertTestFileFoundAsync(sourceFile, testFile);
120
});
121
122
test('returns a test for Dart', async function () {
123
const sourceFile = '/project/Foo.dart';
124
const testFile = '/tests/Foo_test.dart';
125
await assertTestFileFoundAsync(sourceFile, testFile);
126
});
127
128
test('returns a test for Python', async function () {
129
const sourceFile = '/project/foo.py';
130
const testFile = '/tests/test_foo.py';
131
await assertTestFileFoundAsync(sourceFile, testFile);
132
});
133
134
test('returns a test for C#', async function () {
135
const sourceFile = 'src/project/Foo.cs';
136
await assertTestFileFoundAsync(sourceFile, '/src/tests/project/FooTest.cs');
137
// assertTestFileFound(sourceFile, '/unit-tests/project/FooTest.cs');
138
// assertTestFileFound(sourceFile, '/unittests/project/FooTest.cs');
139
});
140
141
test('returns a test for Ruby', async function () {
142
const sourceFile = 'app/api/foo.rb';
143
await assertTestFileFoundAsync(sourceFile, '/test/app/api/foo_test.rb');
144
});
145
146
test('determine java test file with absolute path', async () => {
147
await assertTestFileFoundAsync(
148
'/Users/copilot/git/commons-io/src/main/java/org/apache/commons/io/EndianUtils.java',
149
'/Users/copilot/git/commons-io/src/test/java/org/apache/commons/io/EndianUtilsTest.java',
150
'file:///Users/copilot/git/commons-io'
151
);
152
});
153
154
test('determine rb test file with absolute path', async () => {
155
await assertTestFileFoundAsync(
156
'/Users/copilot/git/github/foo/util.rb',
157
'/Users/copilot/git/github/test/foo/util_test.rb',
158
'file:///Users/copilot/git/github'
159
);
160
});
161
162
test('determine php test file with absolute path', async () => {
163
await assertTestFileFoundAsync(
164
'/Users/copilot/git/github/foo/util.php',
165
'/Users/copilot/git/github/tests/utilTest.php',
166
'file:///Users/copilot/git/github'
167
);
168
});
169
170
test('determine ps1 test file with absolute path', async () => {
171
await assertTestFileFoundAsync(
172
'/Users/copilot/git/github/foo/util.ps1',
173
'/Users/copilot/git/github/Tests/util.Tests.ps1',
174
'file:///Users/copilot/git/github'
175
);
176
});
177
178
type TestSample = { filename: string; isTestFile: boolean };
179
const testSamples: TestSample[] = [
180
{ filename: 'foo.js', isTestFile: false },
181
{ filename: 'foo.test.js', isTestFile: true },
182
{ filename: 'foo.spec.js', isTestFile: true },
183
{ filename: 'foo.ts', isTestFile: false },
184
{ filename: 'foo.test.ts', isTestFile: true },
185
{ filename: 'foo.spec.ts', isTestFile: true },
186
{ filename: 'foo.py', isTestFile: false },
187
{ filename: 'test_foo.py', isTestFile: true },
188
{ filename: 'foo_test.py', isTestFile: true },
189
{ filename: 'foo.rb', isTestFile: false },
190
{ filename: 'foo_test.rb', isTestFile: true },
191
{ filename: 'foo.go', isTestFile: false },
192
{ filename: 'foo_test.go', isTestFile: true },
193
{ filename: 'foo.php', isTestFile: false },
194
{ filename: 'fooTest.php', isTestFile: true },
195
{ filename: 'Foo.java', isTestFile: false },
196
{ filename: 'FooTest.java', isTestFile: true },
197
{ filename: 'Foo.cs', isTestFile: false },
198
{ filename: 'FooTest.cs', isTestFile: true },
199
{ filename: 'foo.xx', isTestFile: false },
200
{ filename: 'foo~Test.xx', isTestFile: true },
201
{ filename: 'foo.spec.xx', isTestFile: true },
202
{ filename: 'fooTest.xx', isTestFile: true },
203
{ filename: 'test_foo.xx', isTestFile: true },
204
{ filename: 'foo.Tests.ps1', isTestFile: true },
205
];
206
// test for each sample
207
for (const sample of testSamples) {
208
test(`is ${sample.filename} a test file?`, () => {
209
const isTest = isTestFile(URI.file(sample.filename));
210
assert.strictEqual(isTest, sample.isTestFile);
211
});
212
}
213
214
function createTextDocument(uri: URI) {
215
const sourceDocumentData = createTextDocumentData(uri, '', suffix2Language[basename(uri).substring(1)] ?? '');
216
return TextDocumentSnapshot.create(sourceDocumentData.document);
217
}
218
219
async function assertTestFileFoundAsync(sourceFilePath: string, expectedTestFilePath: string, workspaceUri?: string) {
220
221
const sourceFile = URI.file(sourceFilePath);
222
const expectedTestFile = URI.file(expectedTestFilePath);
223
224
const testFileFinder = new TestFileFinder(new class extends AbstractSearchService {
225
override async findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, progress: vscode.Progress<vscode.TextSearchResult>, token: vscode.CancellationToken): Promise<vscode.TextSearchComplete> {
226
if (glob.shouldInclude(expectedTestFile, { include: options.include ? [options.include] : undefined, exclude: options.exclude ? [options.exclude] : undefined })) {
227
progress.report({ uri: expectedTestFile, ranges: [], preview: { matches: [], text: '' } });
228
}
229
return {};
230
}
231
override findTextInFiles2(query: vscode.TextSearchQuery2, options?: vscode.FindTextInFilesOptions2, token?: vscode.CancellationToken): vscode.FindTextInFilesResponse {
232
throw new Error('not implemented');
233
}
234
override async findFiles(filePattern: vscode.GlobPattern, options?: vscode.FindFiles2Options | undefined, token?: vscode.CancellationToken | undefined): Promise<vscode.Uri[]> {
235
if (glob.shouldInclude(expectedTestFile, { include: [filePattern], exclude: options?.exclude ? options.exclude : undefined })) {
236
return [expectedTestFile];
237
}
238
return [];
239
}
240
}, new TestTabsService());
241
242
const sourceDocument = createTextDocument(sourceFile);
243
const result = await testFileFinder.findTestFileForSourceFile(sourceDocument, CancellationToken.None);
244
245
assert.ok(result);
246
assert.strictEqual(normalize(result!.path), normalize(expectedTestFilePath.toString()));
247
}
248
249
async function assertImplFileFoundAsync(testFilePath: string, expectedImplFilePath: string) {
250
251
const testFile = URI.file(testFilePath);
252
const expectedImplFile = URI.file(expectedImplFilePath);
253
254
const testFileFinder = new TestFileFinder(new class extends AbstractSearchService {
255
override async findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, progress: vscode.Progress<vscode.TextSearchResult>, token: vscode.CancellationToken): Promise<vscode.TextSearchComplete> {
256
if (glob.isMatch(expectedImplFile, options.include!) && (!options.exclude || !glob.isMatch(expectedImplFile, options.exclude))) {
257
progress.report({
258
uri: expectedImplFile,
259
ranges: [],
260
preview: { text: '', matches: [] }
261
});
262
}
263
return {};
264
}
265
override findTextInFiles2(query: vscode.TextSearchQuery2, options?: vscode.FindTextInFilesOptions2, token?: vscode.CancellationToken): vscode.FindTextInFilesResponse {
266
throw new Error('not implemented');
267
}
268
override async findFiles(filePattern: vscode.GlobPattern, options?: vscode.FindFiles2Options | undefined, token?: vscode.CancellationToken | undefined): Promise<vscode.Uri[]> {
269
if (glob.isMatch(expectedImplFile, filePattern) && (!options?.exclude || !options.exclude.some(e => glob.isMatch(expectedImplFile, e)))) {
270
return [expectedImplFile];
271
}
272
return [];
273
}
274
}, new TestTabsService());
275
276
const testFileDocument = createTextDocument(testFile);
277
const result = await testFileFinder.findFileForTestFile(testFileDocument, CancellationToken.None);
278
279
assert.notStrictEqual(result, undefined);
280
assert.strictEqual(normalize(result!.path), normalize(expectedImplFilePath.toString()));
281
}
282
});
283
284