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