Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/test/smoke/src/main.ts
5240 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 { gracefulify } from 'graceful-fs';
8
import * as cp from 'child_process';
9
import * as path from 'path';
10
import * as os from 'os';
11
import minimist from 'minimist';
12
import * as vscodetest from '@vscode/test-electron';
13
import fetch from 'node-fetch';
14
import { Quality, MultiLogger, Logger, ConsoleLogger, FileLogger, measureAndLog, getDevElectronPath, getBuildElectronPath, getBuildVersion, ApplicationOptions } from '../../automation';
15
import { retry } from './utils';
16
17
import { setup as setupDataLossTests } from './areas/workbench/data-loss.test';
18
import { setup as setupPreferencesTests } from './areas/preferences/preferences.test';
19
import { setup as setupSearchTests } from './areas/search/search.test';
20
import { setup as setupNotebookTests } from './areas/notebook/notebook.test';
21
import { setup as setupLanguagesTests } from './areas/languages/languages.test';
22
import { setup as setupStatusbarTests } from './areas/statusbar/statusbar.test';
23
import { setup as setupExtensionTests } from './areas/extensions/extensions.test';
24
import { setup as setupExtensionHostRestartTests } from './areas/extensions/extension-host-restart.test';
25
import { setup as setupMultirootTests } from './areas/multiroot/multiroot.test';
26
import { setup as setupLocalizationTests } from './areas/workbench/localization.test';
27
import { setup as setupLaunchTests } from './areas/workbench/launch.test';
28
import { setup as setupTerminalTests } from './areas/terminal/terminal.test';
29
import { setup as setupTaskTests } from './areas/task/task.test';
30
import { setup as setupChatTests } from './areas/chat/chatDisabled.test';
31
import { setup as setupChatAnonymousTests } from './areas/chat/chatAnonymous.test';
32
import { setup as setupAccessibilityTests } from './areas/accessibility/accessibility.test';
33
34
const rootPath = path.join(__dirname, '..', '..', '..');
35
36
const [, , ...args] = process.argv;
37
const opts = minimist(args, {
38
string: [
39
'browser',
40
'build',
41
'stable-build',
42
'wait-time',
43
'test-repo',
44
'electronArgs'
45
],
46
boolean: [
47
'verbose',
48
'remote',
49
'web',
50
'headless',
51
'tracing'
52
],
53
default: {
54
verbose: false
55
}
56
}) as {
57
verbose?: boolean;
58
remote?: boolean;
59
headless?: boolean;
60
web?: boolean;
61
tracing?: boolean;
62
build?: string;
63
'stable-build'?: string;
64
browser?: 'chromium' | 'webkit' | 'firefox' | 'chromium-msedge' | 'chromium-chrome';
65
electronArgs?: string;
66
};
67
68
const logsRootPath = (() => {
69
const logsParentPath = path.join(rootPath, '.build', 'logs');
70
71
let logsName: string;
72
if (opts.web) {
73
logsName = 'smoke-tests-browser';
74
} else if (opts.remote) {
75
logsName = 'smoke-tests-remote';
76
} else {
77
logsName = 'smoke-tests-electron';
78
}
79
80
return path.join(logsParentPath, logsName);
81
})();
82
83
const crashesRootPath = (() => {
84
const crashesParentPath = path.join(rootPath, '.build', 'crashes');
85
86
let crashesName: string;
87
if (opts.web) {
88
crashesName = 'smoke-tests-browser';
89
} else if (opts.remote) {
90
crashesName = 'smoke-tests-remote';
91
} else {
92
crashesName = 'smoke-tests-electron';
93
}
94
95
return path.join(crashesParentPath, crashesName);
96
})();
97
98
const logger = createLogger();
99
100
function createLogger(): Logger {
101
const loggers: Logger[] = [];
102
103
// Log to console if verbose
104
if (opts.verbose) {
105
loggers.push(new ConsoleLogger());
106
}
107
108
// Prepare logs rot path
109
fs.rmSync(logsRootPath, { recursive: true, force: true, maxRetries: 10, retryDelay: 1000 });
110
fs.mkdirSync(logsRootPath, { recursive: true });
111
112
// Always log to log file
113
loggers.push(new FileLogger(path.join(logsRootPath, 'smoke-test-runner.log')));
114
115
return new MultiLogger(loggers);
116
}
117
118
try {
119
gracefulify(fs);
120
} catch (error) {
121
logger.log(`Error enabling graceful-fs: ${error}`);
122
}
123
124
function getTestTypeSuffix(): string {
125
if (opts.web) {
126
return 'browser';
127
} else if (opts.remote) {
128
return 'remote';
129
} else {
130
return 'electron';
131
}
132
}
133
134
const testDataPath = path.join(os.tmpdir(), `vscsmoke-${getTestTypeSuffix()}`);
135
if (fs.existsSync(testDataPath)) {
136
fs.rmSync(testDataPath, { recursive: true, force: true, maxRetries: 10, retryDelay: 1000 });
137
}
138
fs.mkdirSync(testDataPath, { recursive: true });
139
process.once('exit', () => {
140
try {
141
fs.rmSync(testDataPath, { recursive: true, force: true, maxRetries: 10, retryDelay: 1000 });
142
} catch {
143
// noop
144
}
145
});
146
147
const testRepoUrl = 'https://github.com/microsoft/vscode-smoketest-express';
148
const workspacePath = path.join(testDataPath, `vscode-smoketest-express`);
149
const extensionsPath = path.join(testDataPath, 'extensions-dir');
150
fs.mkdirSync(extensionsPath, { recursive: true });
151
152
function fail(errorMessage): void {
153
logger.log(errorMessage);
154
if (!opts.verbose) {
155
console.error(errorMessage);
156
}
157
process.exit(1);
158
}
159
160
let quality: Quality;
161
let version: string | undefined;
162
163
function parseVersion(version: string): { major: number; minor: number; patch: number } {
164
const [, major, minor, patch] = /^(\d+)\.(\d+)\.(\d+)/.exec(version)!;
165
return { major: parseInt(major), minor: parseInt(minor), patch: parseInt(patch) };
166
}
167
168
function parseQuality(): Quality {
169
if (process.env.VSCODE_DEV === '1') {
170
return Quality.Dev;
171
}
172
173
const quality = process.env.VSCODE_QUALITY ?? '';
174
175
switch (quality) {
176
case 'stable':
177
return Quality.Stable;
178
case 'insider':
179
return Quality.Insiders;
180
case 'exploration':
181
return Quality.Exploration;
182
case 'oss':
183
return Quality.OSS;
184
default:
185
return Quality.Dev;
186
}
187
}
188
189
//
190
// #### Electron Smoke Tests ####
191
//
192
if (!opts.web) {
193
let testCodePath = opts.build;
194
let electronPath: string | undefined;
195
196
if (testCodePath) {
197
electronPath = getBuildElectronPath(testCodePath);
198
version = getBuildVersion(testCodePath);
199
} else {
200
testCodePath = getDevElectronPath();
201
electronPath = testCodePath;
202
process.env.VSCODE_REPOSITORY = rootPath;
203
process.env.VSCODE_DEV = '1';
204
process.env.VSCODE_CLI = '1';
205
}
206
207
if (!fs.existsSync(electronPath || '')) {
208
fail(`Cannot find VSCode at ${electronPath}. Please run VSCode once first (scripts/code.sh, scripts\\code.bat) and try again.`);
209
}
210
211
quality = parseQuality();
212
213
if (opts.remote) {
214
logger.log(`Running desktop remote smoke tests against ${electronPath}`);
215
} else {
216
logger.log(`Running desktop smoke tests against ${electronPath}`);
217
}
218
}
219
220
//
221
// #### Web Smoke Tests ####
222
//
223
else {
224
const testCodeServerPath = opts.build || process.env.VSCODE_REMOTE_SERVER_PATH;
225
226
if (typeof testCodeServerPath === 'string') {
227
if (!fs.existsSync(testCodeServerPath)) {
228
fail(`Cannot find Code server at ${testCodeServerPath}.`);
229
} else {
230
logger.log(`Running web smoke tests against ${testCodeServerPath}`);
231
}
232
}
233
234
if (!testCodeServerPath) {
235
process.env.VSCODE_REPOSITORY = rootPath;
236
process.env.VSCODE_DEV = '1';
237
process.env.VSCODE_CLI = '1';
238
239
logger.log(`Running web smoke out of sources`);
240
}
241
242
quality = parseQuality();
243
}
244
245
logger.log(`VS Code product quality: ${quality}.`);
246
247
const userDataDir = path.join(testDataPath, 'd');
248
249
async function setupRepository(): Promise<void> {
250
if (opts['test-repo']) {
251
logger.log('Copying test project repository:', opts['test-repo']);
252
fs.rmSync(workspacePath, { recursive: true, force: true, maxRetries: 10, retryDelay: 1000 });
253
// not platform friendly
254
if (process.platform === 'win32') {
255
cp.execSync(`xcopy /E "${opts['test-repo']}" "${workspacePath}"\\*`);
256
} else {
257
cp.execSync(`cp -R "${opts['test-repo']}" "${workspacePath}"`);
258
}
259
} else {
260
if (!fs.existsSync(workspacePath)) {
261
logger.log('Cloning test project repository...');
262
const res = cp.spawnSync('git', ['clone', testRepoUrl, workspacePath], { stdio: 'inherit' });
263
if (!fs.existsSync(workspacePath)) {
264
throw new Error(`Clone operation failed: ${res.stderr.toString()}`);
265
}
266
} else {
267
logger.log('Cleaning test project repository...');
268
cp.spawnSync('git', ['fetch'], { cwd: workspacePath, stdio: 'inherit' });
269
cp.spawnSync('git', ['reset', '--hard', 'FETCH_HEAD'], { cwd: workspacePath, stdio: 'inherit' });
270
cp.spawnSync('git', ['clean', '-xdf'], { cwd: workspacePath, stdio: 'inherit' });
271
}
272
}
273
}
274
275
async function ensureStableCode(): Promise<void> {
276
let stableCodePath = opts['stable-build'];
277
if (!stableCodePath) {
278
const current = parseVersion(version!);
279
const versionsReq = await retry(() => measureAndLog(() => fetch('https://update.code.visualstudio.com/api/releases/stable'), 'versionReq', logger), 1000, 20);
280
281
if (!versionsReq.ok) {
282
throw new Error('Could not fetch releases from update server');
283
}
284
285
const versions: string[] = await measureAndLog(() => versionsReq.json(), 'versionReq.json()', logger);
286
const stableVersion = versions.find(raw => {
287
const version = parseVersion(raw);
288
return version.major < current.major || (version.major === current.major && version.minor < current.minor);
289
});
290
291
if (!stableVersion) {
292
throw new Error(`Could not find suitable stable version for ${version}`);
293
}
294
295
logger.log(`Found VS Code v${version}, downloading previous VS Code version ${stableVersion}...`);
296
297
let lastProgressMessage: string | undefined = undefined;
298
let lastProgressReportedAt = 0;
299
const stableCodeDestination = path.join(testDataPath, 's');
300
const stableCodeExecutable = await retry(() => measureAndLog(() => vscodetest.download({
301
cachePath: stableCodeDestination,
302
version: stableVersion,
303
extractSync: true,
304
reporter: {
305
report: report => {
306
let progressMessage = `download stable code progress: ${report.stage}`;
307
const now = Date.now();
308
if (progressMessage !== lastProgressMessage || now - lastProgressReportedAt > 10000) {
309
lastProgressMessage = progressMessage;
310
lastProgressReportedAt = now;
311
312
if (report.stage === 'downloading') {
313
progressMessage += ` (${report.bytesSoFar}/${report.totalBytes})`;
314
}
315
316
logger.log(progressMessage);
317
}
318
},
319
error: error => logger.log(`download stable code error: ${error}`)
320
}
321
}), 'download stable code', logger), 1000, 3, async () => {
322
fs.rmSync(stableCodeDestination, { recursive: true, force: true, maxRetries: 10, retryDelay: 1000 });
323
});
324
325
if (process.platform === 'darwin') {
326
// Visual Studio Code.app/Contents/MacOS/Code
327
stableCodePath = path.dirname(path.dirname(path.dirname(stableCodeExecutable)));
328
} else {
329
// VSCode/Code.exe (Windows) | VSCode/code (Linux)
330
stableCodePath = path.dirname(stableCodeExecutable);
331
}
332
333
opts['stable-version'] = parseVersion(stableVersion);
334
}
335
336
if (!fs.existsSync(stableCodePath)) {
337
throw new Error(`Cannot find Stable VSCode at ${stableCodePath}.`);
338
}
339
340
logger.log(`Using stable build ${stableCodePath} for migration tests`);
341
342
opts['stable-build'] = stableCodePath;
343
}
344
345
async function setup(): Promise<void> {
346
logger.log('Test data path:', testDataPath);
347
logger.log('Preparing smoketest setup...');
348
349
if (!opts.web && !opts.remote && opts.build) {
350
// only enabled when running with --build and not in web or remote
351
await measureAndLog(() => ensureStableCode(), 'ensureStableCode', logger);
352
}
353
await measureAndLog(() => setupRepository(), 'setupRepository', logger);
354
355
// Copy smoke test extension for extension host restart test
356
if (!opts.web && !opts.remote) {
357
const smokeExtPath = path.join(rootPath, 'test', 'smoke', 'extensions', 'vscode-smoketest-ext-host');
358
const dest = path.join(extensionsPath, 'vscode-smoketest-ext-host');
359
if (fs.existsSync(dest)) {
360
fs.rmSync(dest, { recursive: true, force: true });
361
}
362
fs.cpSync(smokeExtPath, dest, { recursive: true });
363
}
364
365
logger.log('Smoketest setup done!\n');
366
}
367
368
// Before all tests run setup
369
before(async function () {
370
this.timeout(5 * 60 * 1000); // increase since we download VSCode
371
372
const options: ApplicationOptions = {
373
quality,
374
version: parseVersion(version ?? '0.0.0'),
375
codePath: opts.build,
376
workspacePath,
377
userDataDir,
378
useInMemorySecretStorage: true,
379
extensionsPath,
380
logger,
381
logsPath: path.join(logsRootPath, 'suite_unknown'),
382
crashesPath: path.join(crashesRootPath, 'suite_unknown'),
383
verbose: opts.verbose,
384
remote: opts.remote,
385
web: opts.web,
386
tracing: opts.tracing || !!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY || !!process.env.GITHUB_WORKSPACE,
387
headless: opts.headless,
388
browser: opts.browser,
389
extraArgs: (opts.electronArgs || '').split(' ').map(arg => arg.trim()).filter(arg => !!arg)
390
};
391
this.defaultOptions = options;
392
393
await setup();
394
});
395
396
// After main suite (after all tests)
397
after(async function () {
398
try {
399
await measureAndLog(async () => {
400
fs.rmSync(testDataPath, { recursive: true, force: true, maxRetries: 10, retryDelay: 1000 });
401
}, 'rimraf(testDataPath)', logger);
402
} catch (error) {
403
logger.log(`Unable to delete smoke test workspace: ${error}. This indicates some process is locking the workspace folder.`);
404
}
405
});
406
407
describe(`VSCode Smoke Tests (${opts.web ? 'Web' : 'Electron'})`, () => {
408
if (!opts.web) { setupDataLossTests(() => { return { stableCodePath: opts['stable-build'], stableCodeVersion: opts['stable-version'] } /* Do not change, deferred for a reason! */; }, logger); }
409
setupPreferencesTests(logger);
410
setupSearchTests(logger);
411
if (!opts.web) { setupNotebookTests(logger); }
412
setupLanguagesTests(logger);
413
setupTerminalTests(logger);
414
setupTaskTests(logger);
415
setupStatusbarTests(logger);
416
if (quality !== Quality.Dev && quality !== Quality.OSS) { setupExtensionTests(logger); }
417
if (!opts.web && !opts.remote) { setupExtensionHostRestartTests(logger); }
418
setupMultirootTests(logger);
419
if (!opts.web && !opts.remote && quality !== Quality.Dev && quality !== Quality.OSS) { setupLocalizationTests(logger); }
420
if (!opts.web && !opts.remote) { setupLaunchTests(logger); }
421
if (!opts.web) { setupChatTests(logger); }
422
if (!opts.web && quality === Quality.Insiders) { setupChatAnonymousTests(logger); }
423
setupAccessibilityTests(logger, opts, quality);
424
});
425
426