Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/e2e/newWorkspace.stest.ts
13388 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 * as fs from 'fs';
7
import * as path from 'path';
8
import { TextDecoder } from 'util';
9
import type { ChatResponseFileTree } from 'vscode';
10
import { INewWorkspacePreviewContentManager } from '../../src/extension/intents/node/newIntent';
11
import { ITestingServicesAccessor } from '../../src/platform/test/node/services';
12
import { IQualifiedFile, getLanguageForFile } from '../../src/platform/test/node/simulationWorkspace';
13
import { looksLikeDirectory } from '../../src/util/common/fileSystem';
14
import { ChatResponseFileTreePart, Uri } from '../../src/vscodeTypes';
15
import { ssuite, stest } from '../base/stest';
16
import { validate } from '../base/validate';
17
import { compileTSWorkspace } from '../simulation/diagnosticProviders/tsc';
18
import { cleanTempDir, createTempDir } from '../simulation/stestUtil';
19
import { Scenario, fetchConversationScenarios } from './scenarioLoader';
20
import { generateScenarioTestRunner } from './scenarioTest';
21
22
(function () {
23
24
ssuite.skip({ title: 'newWorkspace', subtitle: 'e2e', location: 'panel' }, (inputPath) => {
25
26
const scenarioFolder = inputPath ?? path.join(__dirname, '..', 'test/scenarios/test-new-workspace');
27
const scenarios: Scenario[] = fetchConversationScenarios(scenarioFolder);
28
29
for (const scenario of scenarios) {
30
stest({ description: scenario[0].question.replace('/new ', '') },
31
generateScenarioTestRunner(
32
scenario,
33
async (accessor, question, answer, _rawResponse, _index, _turn, commands) => {
34
const files: IQualifiedFile[] = [];
35
for (const command of commands) {
36
if (command.command === 'github.copilot.createProject') {
37
// validate project structure and contents in files
38
const projectItems: ChatResponseFileTreePart = command.arguments?.[0];
39
if (!projectItems || projectItems.value?.length === 0) {
40
return Promise.resolve({ success: false, errorMessage: 'Invalid projectItems' });
41
}
42
if (scenario[0].json.keywords) {
43
const err = validate(_rawResponse, scenario[0].json.keywords);
44
if (err) {
45
return { success: false, errorMessage: err };
46
}
47
}
48
49
const contentManager = accessor.get(INewWorkspacePreviewContentManager);
50
51
async function traverseFileTree(parentPath: string, fileTree: ChatResponseFileTree, baseUri: Uri): Promise<void | { success: boolean; errorMessage: string }> {
52
const itemPath = path.posix.join(parentPath, fileTree.name);
53
if (fileTree.children?.length || !looksLikeDirectory(fileTree.name)) {
54
files.push({ kind: 'qualifiedFile', uri: Uri.joinPath(baseUri, itemPath), fileContents: 'DIR' });
55
for (const item of fileTree.children ?? []) {
56
await traverseFileTree(itemPath, item, baseUri);
57
}
58
} else {
59
const result = await contentManager.get(Uri.joinPath(baseUri, itemPath))?.content;
60
const decoder = new TextDecoder();
61
const decodedString = decoder.decode(result);
62
if (!decodedString) {
63
return { success: false, errorMessage: `Content not found for ${itemPath}` };
64
}
65
files.push({ kind: 'qualifiedFile', uri: Uri.joinPath(baseUri, itemPath), fileContents: decodedString });
66
}
67
return;
68
}
69
70
for (const projectItem of projectItems.value) {
71
await traverseFileTree('', projectItem, projectItems.baseUri);
72
}
73
74
const tempDirPath = await createTempDir();
75
const projectRoot = path.join(tempDirPath, projectItems.baseUri.path);
76
await createTempWorkspace(tempDirPath, files);
77
const result = await compileWorkspace(accessor, projectRoot, files);
78
await cleanTempDir(tempDirPath);
79
return result;
80
81
}
82
}
83
return Promise.resolve({ success: false, errorMessage: 'Failed to parse new response' });
84
}
85
));
86
}
87
});
88
})();
89
90
// TODO @aiday-mar add possibility to execute python files and find the diagnostics or errors upon execution
91
async function compileWorkspace(accessor: ITestingServicesAccessor, projectRoot: string, files: IQualifiedFile[]): Promise<{ success: boolean; errorMessage?: string }> {
92
for (const file of files) {
93
const language = getLanguageForFile(file);
94
switch (language.languageId) {
95
case 'typescript':
96
{
97
// compute diagnostics
98
const tsDiagnostics = await compileTSWorkspace(accessor, projectRoot);
99
if (tsDiagnostics.length === 0) {
100
return { success: true };
101
}
102
const errors = tsDiagnostics.map(diagnostic => diagnostic.file + ' ' + diagnostic.code + ' : ' + diagnostic.message).join('\n');
103
return { success: false, errorMessage: 'Typescript diagnostics errors: \n' + errors };
104
}
105
}
106
}
107
return { success: true };
108
}
109
110
async function createTempWorkspace(tempDirPath: string, files: IQualifiedFile[]): Promise<void> {
111
await fs.promises.rm(tempDirPath, { recursive: true, force: true });
112
await fs.promises.mkdir(tempDirPath, { recursive: true });
113
for (const file of files) {
114
const tmpPath = path.join(tempDirPath, file.uri.path);
115
if (file.fileContents !== 'DIR') {
116
await fs.promises.writeFile(tmpPath, file.fileContents);
117
}
118
else {
119
await fs.promises.mkdir(tmpPath, { recursive: true });
120
}
121
}
122
}
123
124