mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-29 13:36:27 -05:00
esm - remove AMD build scripts, tests and variables
This commit is contained in:
@@ -1,395 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const events = require('events');
|
||||
const mocha = require('mocha');
|
||||
const createStatsCollector = require('mocha/lib/stats-collector');
|
||||
const MochaJUnitReporter = require('mocha-junit-reporter');
|
||||
const url = require('url');
|
||||
const minimatch = require('minimatch');
|
||||
const fs = require('fs');
|
||||
const playwright = require('@playwright/test');
|
||||
const { applyReporter } = require('../reporter');
|
||||
const yaserver = require('yaserver');
|
||||
const http = require('http');
|
||||
const { randomBytes } = require('crypto');
|
||||
const minimist = require('minimist');
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* run: string;
|
||||
* grep: string;
|
||||
* runGlob: string;
|
||||
* browser: string;
|
||||
* reporter: string;
|
||||
* 'reporter-options': string;
|
||||
* tfs: string;
|
||||
* build: boolean;
|
||||
* debug: boolean;
|
||||
* sequential: boolean;
|
||||
* help: boolean;
|
||||
* }}
|
||||
*/
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
boolean: ['build', 'debug', 'sequential', 'help'],
|
||||
string: ['run', 'grep', 'runGlob', 'browser', 'reporter', 'reporter-options', 'tfs'],
|
||||
default: {
|
||||
build: false,
|
||||
browser: ['chromium', 'firefox', 'webkit'],
|
||||
reporter: process.platform === 'win32' ? 'list' : 'spec',
|
||||
'reporter-options': ''
|
||||
},
|
||||
alias: {
|
||||
grep: ['g', 'f'],
|
||||
runGlob: ['glob', 'runGrep'],
|
||||
debug: ['debug-browser'],
|
||||
help: 'h'
|
||||
},
|
||||
describe: {
|
||||
build: 'run with build output (out-build)',
|
||||
run: 'only run tests matching <relative_file_path>',
|
||||
grep: 'only run tests matching <pattern>',
|
||||
debug: 'do not run browsers headless',
|
||||
sequential: 'only run suites for a single browser at a time',
|
||||
browser: 'browsers in which tests should run',
|
||||
reporter: 'the mocha reporter',
|
||||
'reporter-options': 'the mocha reporter options',
|
||||
tfs: 'tfs',
|
||||
help: 'show the help'
|
||||
}
|
||||
});
|
||||
|
||||
if (args.help) {
|
||||
console.log(`Usage: node ${process.argv[1]} [options]
|
||||
|
||||
Options:
|
||||
--build run with build output (out-build)
|
||||
--run <relative_file_path> only run tests matching <relative_file_path>
|
||||
--grep, -g, -f <pattern> only run tests matching <pattern>
|
||||
--debug, --debug-browser do not run browsers headless
|
||||
--sequential only run suites for a single browser at a time
|
||||
--browser <browser> browsers in which tests should run
|
||||
--reporter <reporter> the mocha reporter
|
||||
--reporter-options <reporter-options> the mocha reporter options
|
||||
--tfs <tfs> tfs
|
||||
--help, -h show the help`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const withReporter = (function () {
|
||||
if (args.tfs) {
|
||||
{
|
||||
return (browserType, runner) => {
|
||||
new mocha.reporters.Spec(runner);
|
||||
new MochaJUnitReporter(runner, {
|
||||
reporterOptions: {
|
||||
testsuitesTitle: `${args.tfs} ${process.platform}`,
|
||||
mochaFile: process.env.BUILD_ARTIFACTSTAGINGDIRECTORY ? path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${browserType}-${args.tfs.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) : undefined
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return (_, runner) => applyReporter(runner, args);
|
||||
}
|
||||
})();
|
||||
|
||||
const outdir = args.build ? 'out-build' : 'out';
|
||||
const rootDir = path.resolve(__dirname, '..', '..', '..');
|
||||
const out = path.join(rootDir, `${outdir}`);
|
||||
|
||||
function ensureIsArray(a) {
|
||||
return Array.isArray(a) ? a : [a];
|
||||
}
|
||||
|
||||
const testModules = (async function () {
|
||||
|
||||
const excludeGlob = '**/{node,electron-sandbox,electron-main,electron-utility}/**/*.test.js';
|
||||
let isDefaultModules = true;
|
||||
let promise;
|
||||
|
||||
if (args.run) {
|
||||
// use file list (--run)
|
||||
isDefaultModules = false;
|
||||
promise = Promise.resolve(ensureIsArray(args.run).map(file => {
|
||||
file = file.replace(/^src/, 'out');
|
||||
file = file.replace(/\.ts$/, '.js');
|
||||
return path.relative(out, file);
|
||||
}));
|
||||
|
||||
} else {
|
||||
// glob patterns (--glob)
|
||||
const defaultGlob = '**/*.test.js';
|
||||
const pattern = args.runGlob || defaultGlob;
|
||||
isDefaultModules = pattern === defaultGlob;
|
||||
|
||||
promise = new Promise((resolve, reject) => {
|
||||
glob(pattern, { cwd: out }, (err, files) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(files);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return promise.then(files => {
|
||||
const modules = [];
|
||||
for (const file of files) {
|
||||
if (!minimatch(file, excludeGlob)) {
|
||||
modules.push(file.replace(/\.js$/, ''));
|
||||
|
||||
} else if (!isDefaultModules) {
|
||||
console.warn(`DROPPONG ${file} because it cannot be run inside a browser`);
|
||||
}
|
||||
}
|
||||
return modules;
|
||||
});
|
||||
})();
|
||||
|
||||
function consoleLogFn(msg) {
|
||||
const type = msg.type();
|
||||
const candidate = console[type];
|
||||
if (candidate) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
if (type === 'warning') {
|
||||
return console.warn;
|
||||
}
|
||||
|
||||
return console.log;
|
||||
}
|
||||
|
||||
async function createServer() {
|
||||
// Demand a prefix to avoid issues with other services on the
|
||||
// machine being able to access the test server.
|
||||
const prefix = '/' + randomBytes(16).toString('hex');
|
||||
const serveStatic = await yaserver.createServer({ rootDir });
|
||||
|
||||
/** Handles a request for a remote method call, invoking `fn` and returning the result */
|
||||
const remoteMethod = async (req, response, fn) => {
|
||||
const params = await new Promise((resolve, reject) => {
|
||||
const body = [];
|
||||
req.on('data', chunk => body.push(chunk));
|
||||
req.on('end', () => resolve(JSON.parse(Buffer.concat(body).toString())));
|
||||
req.on('error', reject);
|
||||
});
|
||||
|
||||
const result = await fn(...params);
|
||||
response.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
response.end(JSON.stringify(result));
|
||||
};
|
||||
|
||||
const server = http.createServer((request, response) => {
|
||||
if (!request.url?.startsWith(prefix)) {
|
||||
return response.writeHead(404).end();
|
||||
}
|
||||
|
||||
// rewrite the URL so the static server can handle the request correctly
|
||||
request.url = request.url.slice(prefix.length);
|
||||
|
||||
switch (request.url) {
|
||||
case '/remoteMethod/__readFileInTests':
|
||||
return remoteMethod(request, response, p => fs.promises.readFile(p, 'utf-8'));
|
||||
case '/remoteMethod/__writeFileInTests':
|
||||
return remoteMethod(request, response, (p, contents) => fs.promises.writeFile(p, contents));
|
||||
case '/remoteMethod/__readDirInTests':
|
||||
return remoteMethod(request, response, p => fs.promises.readdir(p));
|
||||
case '/remoteMethod/__unlinkInTests':
|
||||
return remoteMethod(request, response, p => fs.promises.unlink(p));
|
||||
case '/remoteMethod/__mkdirPInTests':
|
||||
return remoteMethod(request, response, p => fs.promises.mkdir(p, { recursive: true }));
|
||||
default:
|
||||
return serveStatic.handle(request, response);
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
server.listen(0, 'localhost', () => {
|
||||
resolve({
|
||||
dispose: () => server.close(),
|
||||
// @ts-ignore
|
||||
url: `http://localhost:${server.address().port}${prefix}`
|
||||
});
|
||||
});
|
||||
server.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function runTestsInBrowser(testModules, browserType) {
|
||||
const server = await createServer();
|
||||
const browser = await playwright[browserType].launch({ headless: !Boolean(args.debug), devtools: Boolean(args.debug) });
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
const target = new URL(server.url + '/test/unit/browser/renderer.amd.html');
|
||||
target.searchParams.set('baseUrl', url.pathToFileURL(path.join(rootDir, 'src2')).toString());
|
||||
if (args.build) {
|
||||
target.searchParams.set('build', 'true');
|
||||
}
|
||||
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
|
||||
target.searchParams.set('ci', 'true');
|
||||
}
|
||||
|
||||
const emitter = new events.EventEmitter();
|
||||
await page.exposeFunction('mocha_report', (type, data1, data2) => {
|
||||
emitter.emit(type, data1, data2);
|
||||
});
|
||||
|
||||
await page.goto(target.href);
|
||||
|
||||
if (args.build) {
|
||||
const nlsMessages = await fs.promises.readFile(path.join(out, 'nls.messages.json'), 'utf8');
|
||||
await page.evaluate(value => {
|
||||
// when running from `out-build`, ensure to load the default
|
||||
// messages file, because all `nls.localize` calls have their
|
||||
// english values removed and replaced by an index.
|
||||
// @ts-ignore
|
||||
globalThis._VSCODE_NLS_MESSAGES = JSON.parse(value);
|
||||
}, nlsMessages);
|
||||
}
|
||||
|
||||
page.on('console', async msg => {
|
||||
consoleLogFn(msg)(msg.text(), await Promise.all(msg.args().map(async arg => await arg.jsonValue())));
|
||||
});
|
||||
|
||||
withReporter(browserType, new EchoRunner(emitter, browserType.toUpperCase()));
|
||||
|
||||
// collection failures for console printing
|
||||
const failingModuleIds = [];
|
||||
const failingTests = [];
|
||||
emitter.on('fail', (test, err) => {
|
||||
failingTests.push({ title: test.fullTitle, message: err.message });
|
||||
|
||||
if (err.stack) {
|
||||
const regex = /(vs\/.*\.test)\.js/;
|
||||
for (const line of String(err.stack).split('\n')) {
|
||||
const match = regex.exec(line);
|
||||
if (match) {
|
||||
failingModuleIds.push(match[1]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
// @ts-expect-error
|
||||
await page.evaluate(opts => loadAndRun(opts), {
|
||||
modules: testModules,
|
||||
grep: args.grep,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
server.dispose();
|
||||
await browser.close();
|
||||
|
||||
if (failingTests.length > 0) {
|
||||
let res = `The followings tests are failing:\n - ${failingTests.map(({ title, message }) => `${title} (reason: ${message})`).join('\n - ')}`;
|
||||
|
||||
if (failingModuleIds.length > 0) {
|
||||
res += `\n\nTo DEBUG, open ${browserType.toUpperCase()} and navigate to ${target.href}?${failingModuleIds.map(module => `m=${module}`).join('&')}`;
|
||||
}
|
||||
|
||||
return `${res}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
class EchoRunner extends events.EventEmitter {
|
||||
|
||||
constructor(event, title = '') {
|
||||
super();
|
||||
createStatsCollector(this);
|
||||
event.on('start', () => this.emit('start'));
|
||||
event.on('end', () => this.emit('end'));
|
||||
event.on('suite', (suite) => this.emit('suite', EchoRunner.deserializeSuite(suite, title)));
|
||||
event.on('suite end', (suite) => this.emit('suite end', EchoRunner.deserializeSuite(suite, title)));
|
||||
event.on('test', (test) => this.emit('test', EchoRunner.deserializeRunnable(test)));
|
||||
event.on('test end', (test) => this.emit('test end', EchoRunner.deserializeRunnable(test)));
|
||||
event.on('hook', (hook) => this.emit('hook', EchoRunner.deserializeRunnable(hook)));
|
||||
event.on('hook end', (hook) => this.emit('hook end', EchoRunner.deserializeRunnable(hook)));
|
||||
event.on('pass', (test) => this.emit('pass', EchoRunner.deserializeRunnable(test)));
|
||||
event.on('fail', (test, err) => this.emit('fail', EchoRunner.deserializeRunnable(test, title), EchoRunner.deserializeError(err)));
|
||||
event.on('pending', (test) => this.emit('pending', EchoRunner.deserializeRunnable(test)));
|
||||
}
|
||||
|
||||
static deserializeSuite(suite, titleExtra) {
|
||||
return {
|
||||
root: suite.root,
|
||||
suites: suite.suites,
|
||||
tests: suite.tests,
|
||||
title: titleExtra && suite.title ? `${suite.title} - /${titleExtra}/` : suite.title,
|
||||
titlePath: () => suite.titlePath,
|
||||
fullTitle: () => suite.fullTitle,
|
||||
timeout: () => suite.timeout,
|
||||
retries: () => suite.retries,
|
||||
slow: () => suite.slow,
|
||||
bail: () => suite.bail
|
||||
};
|
||||
}
|
||||
|
||||
static deserializeRunnable(runnable, titleExtra) {
|
||||
return {
|
||||
title: runnable.title,
|
||||
fullTitle: () => titleExtra && runnable.fullTitle ? `${runnable.fullTitle} - /${titleExtra}/` : runnable.fullTitle,
|
||||
titlePath: () => runnable.titlePath,
|
||||
async: runnable.async,
|
||||
slow: () => runnable.slow,
|
||||
speed: runnable.speed,
|
||||
duration: runnable.duration,
|
||||
currentRetry: () => runnable.currentRetry,
|
||||
};
|
||||
}
|
||||
|
||||
static deserializeError(err) {
|
||||
const inspect = err.inspect;
|
||||
err.inspect = () => inspect;
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
testModules.then(async modules => {
|
||||
|
||||
// run tests in selected browsers
|
||||
const browserTypes = Array.isArray(args.browser)
|
||||
? args.browser : [args.browser];
|
||||
|
||||
let messages = [];
|
||||
let didFail = false;
|
||||
|
||||
try {
|
||||
if (args.sequential) {
|
||||
for (const browserType of browserTypes) {
|
||||
messages.push(await runTestsInBrowser(modules, browserType));
|
||||
}
|
||||
} else {
|
||||
messages = await Promise.all(browserTypes.map(async browserType => {
|
||||
return await runTestsInBrowser(modules, browserType);
|
||||
}));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// aftermath
|
||||
for (const msg of messages) {
|
||||
if (msg) {
|
||||
didFail = true;
|
||||
console.log(msg);
|
||||
}
|
||||
}
|
||||
process.exit(didFail ? 1 : 0);
|
||||
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
@@ -1,187 +0,0 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>VSCode Tests</title>
|
||||
<link href="../../../node_modules/mocha/mocha.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<script src="../../../node_modules/mocha/mocha.js"></script>
|
||||
<script>
|
||||
// !!! DO NOT CHANGE !!!
|
||||
// Our unit tests may run in environments without
|
||||
// display (e.g. from builds) and tests may by
|
||||
// accident bring up native dialogs or even open
|
||||
// windows. This we cannot allow as it may crash
|
||||
// the test run.
|
||||
// !!! DO NOT CHANGE !!!
|
||||
window.open = function () { throw new Error('window.open() is not supported in tests!'); };
|
||||
window.alert = function () { throw new Error('window.alert() is not supported in tests!'); }
|
||||
window.confirm = function () { throw new Error('window.confirm() is not supported in tests!'); }
|
||||
|
||||
// Ignore uncaught cancelled promise errors
|
||||
window.addEventListener('unhandledrejection', e => {
|
||||
const name = e && e.reason && e.reason.name;
|
||||
|
||||
if (name === 'Canceled') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
});
|
||||
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const isCI = urlParams.get('ci');
|
||||
|
||||
mocha.setup({
|
||||
ui: 'tdd',
|
||||
timeout: isCI ? 30000 : 5000
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Depending on --build or not, load loader from known locations -->
|
||||
<script src="../../../out/vs/loader.js"></script>
|
||||
<script src="../../../out-build/vs/loader.js"></script>
|
||||
|
||||
<script>
|
||||
const isBuild = urlParams.get('build');
|
||||
|
||||
// configure loader
|
||||
const baseUrl = window.location.href;
|
||||
require.config({
|
||||
catchError: true,
|
||||
baseUrl: urlParams.get('baseUrl'),
|
||||
paths: {
|
||||
vs: new URL(`../../../${!!isBuild ? 'out-build' : 'out'}/vs`, baseUrl).href,
|
||||
assert: new URL('../assert.js', baseUrl).href,
|
||||
sinon: new URL('../../../node_modules/sinon/pkg/sinon.js', baseUrl).href,
|
||||
'sinon-test': new URL('../../../node_modules/sinon-test/dist/sinon-test.js', baseUrl).href,
|
||||
'@xterm/xterm': new URL('../../../node_modules/@xterm/xterm/lib/xterm.js', baseUrl).href,
|
||||
'@vscode/iconv-lite-umd': new URL('../../../node_modules/@vscode/iconv-lite-umd/lib/iconv-lite-umd.js', baseUrl).href,
|
||||
'@vscode/tree-sitter-wasm': new URL('../../../node_modules/@vscode/tree-sitter-wasm/wasm/tree-sitter.js', baseUrl).href,
|
||||
jschardet: new URL('../../../node_modules/jschardet/dist/jschardet.min.js', baseUrl).href
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script>
|
||||
function serializeSuite(suite) {
|
||||
return {
|
||||
root: suite.root,
|
||||
suites: suite.suites.map(serializeSuite),
|
||||
tests: suite.tests.map(serializeRunnable),
|
||||
title: suite.title,
|
||||
fullTitle: suite.fullTitle(),
|
||||
titlePath: suite.titlePath(),
|
||||
timeout: suite.timeout(),
|
||||
retries: suite.retries(),
|
||||
slow: suite.slow(),
|
||||
bail: suite.bail()
|
||||
};
|
||||
}
|
||||
function serializeRunnable(runnable) {
|
||||
return {
|
||||
title: runnable.title,
|
||||
titlePath: runnable.titlePath(),
|
||||
fullTitle: runnable.fullTitle(),
|
||||
async: runnable.async,
|
||||
slow: runnable.slow(),
|
||||
speed: runnable.speed,
|
||||
duration: runnable.duration,
|
||||
currentRetry: runnable.currentRetry(),
|
||||
};
|
||||
}
|
||||
function serializeError(err) {
|
||||
return {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
actual: err.actual,
|
||||
expected: err.expected,
|
||||
uncaught: err.uncaught,
|
||||
showDiff: err.showDiff,
|
||||
inspect: typeof err.inspect === 'function' ? err.inspect() : ''
|
||||
};
|
||||
}
|
||||
function PlaywrightReporter(runner) {
|
||||
runner.on('start', () => window.mocha_report('start'));
|
||||
runner.on('end', () => window.mocha_report('end'));
|
||||
runner.on('suite', suite => window.mocha_report('suite', serializeSuite(suite)));
|
||||
runner.on('suite end', suite => window.mocha_report('suite end', serializeSuite(suite)));
|
||||
runner.on('test', test => window.mocha_report('test', serializeRunnable(test)));
|
||||
runner.on('test end', test => window.mocha_report('test end', serializeRunnable(test)));
|
||||
runner.on('hook', hook => window.mocha_report('hook', serializeRunnable(hook)));
|
||||
runner.on('hook end', hook => window.mocha_report('hook end', serializeRunnable(hook)));
|
||||
runner.on('pass', test => window.mocha_report('pass', serializeRunnable(test)));
|
||||
runner.on('fail', (test, err) => window.mocha_report('fail', serializeRunnable(test), serializeError(err)));
|
||||
runner.on('pending', test => window.mocha_report('pending', serializeRunnable(test)));
|
||||
};
|
||||
|
||||
const remoteMethods = [
|
||||
'__readFileInTests',
|
||||
'__writeFileInTests',
|
||||
'__readDirInTests',
|
||||
'__unlinkInTests',
|
||||
'__mkdirPInTests',
|
||||
];
|
||||
|
||||
for (const method of remoteMethods) {
|
||||
const prefix = window.location.pathname.split('/')[1];
|
||||
globalThis[method] = async (...args) => {
|
||||
const res = await fetch(`/${prefix}/remoteMethod/${method}`, {
|
||||
body: JSON.stringify(args),
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
});
|
||||
|
||||
return res.json();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async function loadModules(modules) {
|
||||
for (const file of modules) {
|
||||
mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_PRE_REQUIRE, globalThis, file, mocha);
|
||||
const m = await new Promise((resolve, reject) => require([file], resolve, err => {
|
||||
console.log("BAD " + file + JSON.stringify(err, undefined, '\t'));
|
||||
resolve({});
|
||||
}));
|
||||
mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_REQUIRE, m, file, mocha);
|
||||
mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_POST_REQUIRE, globalThis, file, mocha);
|
||||
}
|
||||
}
|
||||
|
||||
window.loadAndRun = async function loadAndRun({ modules, grep }, manual = false) {
|
||||
// load
|
||||
await loadModules(modules);
|
||||
// await new Promise((resolve, reject) => {
|
||||
// require(modules, resolve, err => {
|
||||
// console.log(err);
|
||||
// reject(err);
|
||||
// });
|
||||
// });
|
||||
|
||||
// run
|
||||
return new Promise((resolve, reject) => {
|
||||
if (grep) {
|
||||
mocha.grep(grep);
|
||||
}
|
||||
|
||||
if (!manual) {
|
||||
mocha.reporter(PlaywrightReporter);
|
||||
}
|
||||
mocha.run(failCount => resolve(failCount === 0));
|
||||
});
|
||||
}
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
const modules = url.searchParams.getAll('m');
|
||||
if (Array.isArray(modules) && modules.length > 0) {
|
||||
console.log('MANUALLY running tests', modules);
|
||||
|
||||
loadAndRun({modules}, true).then(() => console.log('done'), err => console.log(err));
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,350 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
// mocha disables running through electron by default. Note that this must
|
||||
// come before any mocha imports.
|
||||
process.env.MOCHA_COLORS = '1';
|
||||
|
||||
const { app, BrowserWindow, ipcMain, crashReporter } = require('electron');
|
||||
const product = require('../../../product.json');
|
||||
const { tmpdir } = require('os');
|
||||
const { existsSync, mkdirSync } = require('fs');
|
||||
const path = require('path');
|
||||
const mocha = require('mocha');
|
||||
const events = require('events');
|
||||
const MochaJUnitReporter = require('mocha-junit-reporter');
|
||||
const url = require('url');
|
||||
const net = require('net');
|
||||
const createStatsCollector = require('mocha/lib/stats-collector');
|
||||
const { applyReporter, importMochaReporter } = require('../reporter');
|
||||
|
||||
const minimist = require('minimist');
|
||||
|
||||
/**
|
||||
* @type {{
|
||||
* grep: string;
|
||||
* run: string;
|
||||
* runGlob: string;
|
||||
* dev: boolean;
|
||||
* reporter: string;
|
||||
* 'reporter-options': string;
|
||||
* 'waitServer': string;
|
||||
* timeout: string;
|
||||
* 'crash-reporter-directory': string;
|
||||
* tfs: string;
|
||||
* build: boolean;
|
||||
* coverage: boolean;
|
||||
* coveragePath: string;
|
||||
* coverageFormats: string | string[];
|
||||
* 'per-test-coverage': boolean;
|
||||
* help: boolean;
|
||||
* }}
|
||||
*/
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
string: ['grep', 'run', 'runGlob', 'reporter', 'reporter-options', 'waitServer', 'timeout', 'crash-reporter-directory', 'tfs', 'coveragePath', 'coverageFormats'],
|
||||
boolean: ['build', 'coverage', 'help', 'dev', 'per-test-coverage'],
|
||||
alias: {
|
||||
'grep': ['g', 'f'],
|
||||
'runGlob': ['glob', 'runGrep'],
|
||||
'dev': ['dev-tools', 'devTools'],
|
||||
'help': 'h'
|
||||
},
|
||||
default: {
|
||||
'reporter': 'spec',
|
||||
'reporter-options': ''
|
||||
}
|
||||
});
|
||||
|
||||
if (args.help) {
|
||||
console.log(`Usage: node ${process.argv[1]} [options]
|
||||
|
||||
Options:
|
||||
--grep, -g, -f <pattern> only run tests matching <pattern>
|
||||
--run <file> only run tests from <file>
|
||||
--runGlob, --glob, --runGrep <file_pattern> only run tests matching <file_pattern>
|
||||
--build run with build output (out-build)
|
||||
--coverage generate coverage report
|
||||
--per-test-coverage generate a per-test V8 coverage report, only valid with the full-json-stream reporter
|
||||
--dev, --dev-tools, --devTools <window> open dev tools, keep window open, reuse app data
|
||||
--reporter <reporter> the mocha reporter (default: "spec")
|
||||
--reporter-options <options> the mocha reporter options (default: "")
|
||||
--waitServer <port> port to connect to and wait before running tests
|
||||
--timeout <ms> timeout for tests
|
||||
--crash-reporter-directory <path> crash reporter directory
|
||||
--tfs <url> TFS server URL
|
||||
--help, -h show the help`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
let crashReporterDirectory = args['crash-reporter-directory'];
|
||||
if (crashReporterDirectory) {
|
||||
crashReporterDirectory = path.normalize(crashReporterDirectory);
|
||||
|
||||
if (!path.isAbsolute(crashReporterDirectory)) {
|
||||
console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory must be absolute.`);
|
||||
app.exit(1);
|
||||
}
|
||||
|
||||
if (!existsSync(crashReporterDirectory)) {
|
||||
try {
|
||||
mkdirSync(crashReporterDirectory);
|
||||
} catch (error) {
|
||||
console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory does not seem to exist or cannot be created.`);
|
||||
app.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Crashes are stored in the crashDumps directory by default, so we
|
||||
// need to change that directory to the provided one
|
||||
console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`);
|
||||
app.setPath('crashDumps', crashReporterDirectory);
|
||||
|
||||
crashReporter.start({
|
||||
companyName: 'Microsoft',
|
||||
productName: process.env['VSCODE_DEV'] ? `${product.nameShort} Dev` : product.nameShort,
|
||||
uploadToServer: false,
|
||||
compress: true
|
||||
});
|
||||
}
|
||||
|
||||
if (!args.dev) {
|
||||
app.setPath('userData', path.join(tmpdir(), `vscode-tests-${Date.now()}`));
|
||||
}
|
||||
|
||||
function deserializeSuite(suite) {
|
||||
return {
|
||||
root: suite.root,
|
||||
suites: suite.suites,
|
||||
tests: suite.tests,
|
||||
title: suite.title,
|
||||
titlePath: () => suite.titlePath,
|
||||
fullTitle: () => suite.fullTitle,
|
||||
timeout: () => suite.timeout,
|
||||
retries: () => suite.retries,
|
||||
slow: () => suite.slow,
|
||||
bail: () => suite.bail
|
||||
};
|
||||
}
|
||||
|
||||
function deserializeRunnable(runnable) {
|
||||
return {
|
||||
title: runnable.title,
|
||||
titlePath: () => runnable.titlePath,
|
||||
fullTitle: () => runnable.fullTitle,
|
||||
async: runnable.async,
|
||||
slow: () => runnable.slow,
|
||||
speed: runnable.speed,
|
||||
duration: runnable.duration,
|
||||
currentRetry: () => runnable.currentRetry
|
||||
};
|
||||
}
|
||||
|
||||
function deserializeError(err) {
|
||||
const inspect = err.inspect;
|
||||
err.inspect = () => inspect;
|
||||
// Unfortunately, mocha rewrites and formats err.actual/err.expected.
|
||||
// This formatting is hard to reverse, so err.*JSON includes the unformatted value.
|
||||
if (err.actual) {
|
||||
err.actual = JSON.parse(err.actual).value;
|
||||
err.actualJSON = err.actual;
|
||||
}
|
||||
if (err.expected) {
|
||||
err.expected = JSON.parse(err.expected).value;
|
||||
err.expectedJSON = err.expected;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
class IPCRunner extends events.EventEmitter {
|
||||
|
||||
constructor(win) {
|
||||
super();
|
||||
|
||||
this.didFail = false;
|
||||
this.didEnd = false;
|
||||
|
||||
ipcMain.on('start', () => this.emit('start'));
|
||||
ipcMain.on('end', () => {
|
||||
this.didEnd = true;
|
||||
this.emit('end');
|
||||
});
|
||||
ipcMain.on('suite', (e, suite) => this.emit('suite', deserializeSuite(suite)));
|
||||
ipcMain.on('suite end', (e, suite) => this.emit('suite end', deserializeSuite(suite)));
|
||||
ipcMain.on('test', (e, test) => this.emit('test', deserializeRunnable(test)));
|
||||
ipcMain.on('test end', (e, test) => this.emit('test end', deserializeRunnable(test)));
|
||||
ipcMain.on('hook', (e, hook) => this.emit('hook', deserializeRunnable(hook)));
|
||||
ipcMain.on('hook end', (e, hook) => this.emit('hook end', deserializeRunnable(hook)));
|
||||
ipcMain.on('pass', (e, test) => this.emit('pass', deserializeRunnable(test)));
|
||||
ipcMain.on('fail', (e, test, err) => {
|
||||
this.didFail = true;
|
||||
this.emit('fail', deserializeRunnable(test), deserializeError(err));
|
||||
});
|
||||
ipcMain.on('pending', (e, test) => this.emit('pending', deserializeRunnable(test)));
|
||||
|
||||
ipcMain.handle('startCoverage', async () => {
|
||||
win.webContents.debugger.attach();
|
||||
await win.webContents.debugger.sendCommand('Debugger.enable');
|
||||
await win.webContents.debugger.sendCommand('Profiler.enable');
|
||||
await win.webContents.debugger.sendCommand('Profiler.startPreciseCoverage', {
|
||||
detailed: true,
|
||||
allowTriggeredUpdates: false,
|
||||
});
|
||||
});
|
||||
|
||||
const coverageScriptsReported = new Set();
|
||||
ipcMain.handle('snapshotCoverage', async (_, test) => {
|
||||
const coverage = await win.webContents.debugger.sendCommand('Profiler.takePreciseCoverage');
|
||||
await Promise.all(coverage.result.map(async (r) => {
|
||||
if (!coverageScriptsReported.has(r.scriptId)) {
|
||||
coverageScriptsReported.add(r.scriptId);
|
||||
const src = await win.webContents.debugger.sendCommand('Debugger.getScriptSource', { scriptId: r.scriptId });
|
||||
r.source = src.scriptSource;
|
||||
}
|
||||
}));
|
||||
|
||||
if (!test) {
|
||||
this.emit('coverage init', coverage);
|
||||
} else {
|
||||
this.emit('coverage increment', test, coverage);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
app.on('ready', () => {
|
||||
|
||||
ipcMain.on('error', (_, err) => {
|
||||
if (!args.dev) {
|
||||
console.error(err);
|
||||
app.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// We need to provide a basic `ISandboxConfiguration`
|
||||
// for our preload script to function properly because
|
||||
// some of our types depend on it (e.g. product.ts).
|
||||
ipcMain.handle('vscode:test-vscode-window-config', async () => {
|
||||
return {
|
||||
product: {
|
||||
version: '1.x.y',
|
||||
nameShort: 'Code - OSS Dev',
|
||||
nameLong: 'Code - OSS Dev',
|
||||
applicationName: 'code-oss',
|
||||
dataFolderName: '.vscode-oss',
|
||||
urlProtocol: 'code-oss',
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// No-op since invoke the IPC as part of IIFE in the preload.
|
||||
ipcMain.handle('vscode:fetchShellEnv', event => { });
|
||||
|
||||
const win = new BrowserWindow({
|
||||
height: 600,
|
||||
width: 800,
|
||||
show: false,
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'), // ensure similar environment as VSCode as tests may depend on this
|
||||
additionalArguments: [`--vscode-window-config=vscode:test-vscode-window-config`],
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
enableWebSQL: false,
|
||||
spellcheck: false
|
||||
}
|
||||
});
|
||||
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
if (args.dev) {
|
||||
win.show();
|
||||
win.webContents.openDevTools();
|
||||
}
|
||||
|
||||
if (args.waitServer) {
|
||||
waitForServer(Number(args.waitServer)).then(sendRun);
|
||||
} else {
|
||||
sendRun();
|
||||
}
|
||||
});
|
||||
|
||||
async function waitForServer(port) {
|
||||
let timeout;
|
||||
let socket;
|
||||
|
||||
return new Promise(resolve => {
|
||||
socket = net.connect(port, '127.0.0.1');
|
||||
socket.on('error', e => {
|
||||
console.error('error connecting to waitServer', e);
|
||||
resolve(undefined);
|
||||
});
|
||||
|
||||
socket.on('close', () => {
|
||||
resolve(undefined);
|
||||
});
|
||||
|
||||
timeout = setTimeout(() => {
|
||||
console.error('timed out waiting for before starting tests debugger');
|
||||
resolve(undefined);
|
||||
}, 15000);
|
||||
}).finally(() => {
|
||||
if (socket) {
|
||||
socket.end();
|
||||
}
|
||||
clearTimeout(timeout);
|
||||
});
|
||||
}
|
||||
|
||||
function sendRun() {
|
||||
win.webContents.send('run', args);
|
||||
}
|
||||
|
||||
win.loadURL(url.format({ pathname: path.join(__dirname, 'renderer.amd.html'), protocol: 'file:', slashes: true }));
|
||||
|
||||
const runner = new IPCRunner(win);
|
||||
createStatsCollector(runner);
|
||||
|
||||
// Handle renderer crashes, #117068
|
||||
win.webContents.on('render-process-gone', (evt, details) => {
|
||||
if (!runner.didEnd) {
|
||||
console.error(`Renderer process crashed with: ${JSON.stringify(details)}`);
|
||||
app.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
const reporters = [];
|
||||
|
||||
if (args.tfs) {
|
||||
reporters.push(
|
||||
new mocha.reporters.Spec(runner),
|
||||
new MochaJUnitReporter(runner, {
|
||||
reporterOptions: {
|
||||
testsuitesTitle: `${args.tfs} ${process.platform}`,
|
||||
mochaFile: process.env.BUILD_ARTIFACTSTAGINGDIRECTORY ? path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${process.arch}-${args.tfs.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`) : undefined
|
||||
}
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
// mocha patches symbols to use windows escape codes, but it seems like
|
||||
// Electron mangles these in its output.
|
||||
if (process.platform === 'win32') {
|
||||
Object.assign(importMochaReporter('base').symbols, {
|
||||
ok: '+',
|
||||
err: 'X',
|
||||
dot: '.',
|
||||
});
|
||||
}
|
||||
|
||||
reporters.push(applyReporter(runner, args));
|
||||
}
|
||||
|
||||
if (!args.dev) {
|
||||
ipcMain.on('all done', async () => {
|
||||
await Promise.all(reporters.map(r => r.drain?.()));
|
||||
app.exit(runner.didFail ? 1 : 0);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -1,35 +0,0 @@
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>VSCode Tests</title>
|
||||
<link href="../../../node_modules/mocha/mocha.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="mocha"></div>
|
||||
<script src="../../../node_modules/mocha/mocha.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
// !!! DO NOT CHANGE !!!
|
||||
// Our unit tests may run in environments without
|
||||
// display (e.g. from builds) and tests may by
|
||||
// accident bring up native dialogs or even open
|
||||
// windows. This we cannot allow as it may crash
|
||||
// the test run.
|
||||
// !!! DO NOT CHANGE !!!
|
||||
window.open = function () { throw new Error('window.open() is not supported in tests!'); };
|
||||
window.alert = function () { throw new Error('window.alert() is not supported in tests!'); }
|
||||
window.confirm = function () { throw new Error('window.confirm() is not supported in tests!'); }
|
||||
|
||||
mocha.setup({
|
||||
ui: 'tdd',
|
||||
timeout: typeof process.env['BUILD_ARTIFACTSTAGINGDIRECTORY'] === 'string' ? 30000 : 5000,
|
||||
forbidOnly: typeof process.env['BUILD_ARTIFACTSTAGINGDIRECTORY'] === 'string' // disallow .only() when running on build machine
|
||||
});
|
||||
require('./renderer.amd');
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,475 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/*eslint-env mocha*/
|
||||
|
||||
const fs = require('fs');
|
||||
const inspector = require('inspector');
|
||||
|
||||
(function () {
|
||||
const originals = {};
|
||||
let logging = false;
|
||||
let withStacks = false;
|
||||
|
||||
self.beginLoggingFS = (_withStacks) => {
|
||||
logging = true;
|
||||
withStacks = _withStacks || false;
|
||||
};
|
||||
self.endLoggingFS = () => {
|
||||
logging = false;
|
||||
withStacks = false;
|
||||
};
|
||||
|
||||
function createSpy(element, cnt) {
|
||||
return function (...args) {
|
||||
if (logging) {
|
||||
console.log(`calling ${element}: ` + args.slice(0, cnt).join(',') + (withStacks ? (`\n` + new Error().stack.split('\n').slice(2).join('\n')) : ''));
|
||||
}
|
||||
return originals[element].call(this, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
function intercept(element, cnt) {
|
||||
originals[element] = fs[element];
|
||||
fs[element] = createSpy(element, cnt);
|
||||
}
|
||||
|
||||
[
|
||||
['realpathSync', 1],
|
||||
['readFileSync', 1],
|
||||
['openSync', 3],
|
||||
['readSync', 1],
|
||||
['closeSync', 1],
|
||||
['readFile', 2],
|
||||
['mkdir', 1],
|
||||
['lstat', 1],
|
||||
['stat', 1],
|
||||
['watch', 1],
|
||||
['readdir', 1],
|
||||
['access', 2],
|
||||
['open', 2],
|
||||
['write', 1],
|
||||
['fdatasync', 1],
|
||||
['close', 1],
|
||||
['read', 1],
|
||||
['unlink', 1],
|
||||
['rmdir', 1],
|
||||
].forEach((element) => {
|
||||
intercept(element[0], element[1]);
|
||||
});
|
||||
})();
|
||||
|
||||
const { ipcRenderer } = require('electron');
|
||||
const assert = require('assert');
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const util = require('util');
|
||||
const coverage = require('../coverage');
|
||||
const { takeSnapshotAndCountClasses } = require('../analyzeSnapshot');
|
||||
|
||||
// Disabled custom inspect. See #38847
|
||||
if (util.inspect && util.inspect['defaultOptions']) {
|
||||
util.inspect['defaultOptions'].customInspect = false;
|
||||
}
|
||||
|
||||
// VSCODE_GLOBALS: package/product.json
|
||||
globalThis._VSCODE_PRODUCT_JSON = (require.__$__nodeRequire ?? require)('../../../product.json');
|
||||
globalThis._VSCODE_PACKAGE_JSON = (require.__$__nodeRequire ?? require)('../../../package.json');
|
||||
|
||||
// Test file operations that are common across platforms. Used for test infra, namely snapshot tests
|
||||
Object.assign(globalThis, {
|
||||
__analyzeSnapshotInTests: takeSnapshotAndCountClasses,
|
||||
__readFileInTests: path => fs.promises.readFile(path, 'utf-8'),
|
||||
__writeFileInTests: (path, contents) => fs.promises.writeFile(path, contents),
|
||||
__readDirInTests: path => fs.promises.readdir(path),
|
||||
__unlinkInTests: path => fs.promises.unlink(path),
|
||||
__mkdirPInTests: path => fs.promises.mkdir(path, { recursive: true }),
|
||||
});
|
||||
|
||||
const IS_CI = !!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY;
|
||||
const _tests_glob = '**/test/**/*.test.js';
|
||||
let loader;
|
||||
let _out;
|
||||
|
||||
function initNls(opts) {
|
||||
if (opts.build) {
|
||||
// when running from `out-build`, ensure to load the default
|
||||
// messages file, because all `nls.localize` calls have their
|
||||
// english values removed and replaced by an index.
|
||||
globalThis._VSCODE_NLS_MESSAGES = (require.__$__nodeRequire ?? require)(`../../../out-build/nls.messages.json`);
|
||||
}
|
||||
}
|
||||
|
||||
function initLoader(opts) {
|
||||
const outdir = opts.build ? 'out-build' : 'out';
|
||||
_out = path.join(__dirname, `../../../${outdir}`);
|
||||
|
||||
const bootstrapNode = require(`../../../${outdir}/bootstrap-node`);
|
||||
|
||||
// setup loader
|
||||
loader = require(`${_out}/vs/loader`);
|
||||
const loaderConfig = {
|
||||
nodeRequire: require,
|
||||
catchError: true,
|
||||
baseUrl: bootstrapNode.fileUriFromPath(path.join(__dirname, '../../../src2'), { isWindows: process.platform === 'win32' }),
|
||||
paths: {
|
||||
'vs': `../${outdir}/vs`,
|
||||
'lib': `../${outdir}/lib`,
|
||||
'bootstrap-fork': `../${outdir}/bootstrap-fork`
|
||||
}
|
||||
};
|
||||
|
||||
if (opts.coverage) {
|
||||
// initialize coverage if requested
|
||||
coverage.initialize(loaderConfig);
|
||||
}
|
||||
|
||||
loader.require.config(loaderConfig);
|
||||
}
|
||||
|
||||
function createCoverageReport(opts) {
|
||||
if (opts.coverage) {
|
||||
return coverage.createReport(opts.run || opts.runGlob, opts.coveragePath, opts.coverageFormats);
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
function loadWorkbenchTestingUtilsModule() {
|
||||
return new Promise((resolve, reject) => {
|
||||
loader.require(['vs/workbench/test/common/utils'], resolve, reject);
|
||||
});
|
||||
}
|
||||
|
||||
async function loadModules(modules) {
|
||||
for (const file of modules) {
|
||||
mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_PRE_REQUIRE, globalThis, file, mocha);
|
||||
const m = await new Promise((resolve, reject) => loader.require([file], resolve, reject));
|
||||
mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_REQUIRE, m, file, mocha);
|
||||
mocha.suite.emit(Mocha.Suite.constants.EVENT_FILE_POST_REQUIRE, globalThis, file, mocha);
|
||||
}
|
||||
}
|
||||
|
||||
function loadTestModules(opts) {
|
||||
|
||||
if (opts.run) {
|
||||
const files = Array.isArray(opts.run) ? opts.run : [opts.run];
|
||||
const modules = files.map(file => {
|
||||
file = file.replace(/^src[\\/]/, '');
|
||||
return file.replace(/\.[jt]s$/, '');
|
||||
});
|
||||
return loadModules(modules);
|
||||
}
|
||||
|
||||
const pattern = opts.runGlob || _tests_glob;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
glob(pattern, { cwd: _out }, (err, files) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
const modules = files.map(file => file.replace(/\.js$/, ''));
|
||||
resolve(modules);
|
||||
});
|
||||
}).then(loadModules);
|
||||
}
|
||||
|
||||
/** @type Mocha.Test */
|
||||
let currentTest;
|
||||
|
||||
async function loadTests(opts) {
|
||||
|
||||
//#region Unexpected Output
|
||||
|
||||
const _allowedTestOutput = [
|
||||
/The vm module of Node\.js is deprecated in the renderer process and will be removed./,
|
||||
];
|
||||
|
||||
// allow snapshot mutation messages locally
|
||||
if (!IS_CI) {
|
||||
_allowedTestOutput.push(/Creating new snapshot in/);
|
||||
_allowedTestOutput.push(/Deleting [0-9]+ old snapshots/);
|
||||
}
|
||||
|
||||
const perTestCoverage = opts['per-test-coverage'] ? await PerTestCoverage.init() : undefined;
|
||||
|
||||
const _allowedTestsWithOutput = new Set([
|
||||
'creates a snapshot', // self-testing
|
||||
'validates a snapshot', // self-testing
|
||||
'cleans up old snapshots', // self-testing
|
||||
'issue #149412: VS Code hangs when bad semantic token data is received', // https://github.com/microsoft/vscode/issues/192440
|
||||
'issue #134973: invalid semantic tokens should be handled better', // https://github.com/microsoft/vscode/issues/192440
|
||||
'issue #148651: VSCode UI process can hang if a semantic token with negative values is returned by language service', // https://github.com/microsoft/vscode/issues/192440
|
||||
'issue #149130: vscode freezes because of Bracket Pair Colorization', // https://github.com/microsoft/vscode/issues/192440
|
||||
'property limits', // https://github.com/microsoft/vscode/issues/192443
|
||||
'Error events', // https://github.com/microsoft/vscode/issues/192443
|
||||
'fetch returns keybinding with user first if title and id matches', //
|
||||
'throw ListenerLeakError'
|
||||
]);
|
||||
|
||||
const _allowedSuitesWithOutput = new Set([
|
||||
'InteractiveChatController'
|
||||
]);
|
||||
|
||||
let _testsWithUnexpectedOutput = false;
|
||||
|
||||
for (const consoleFn of [console.log, console.error, console.info, console.warn, console.trace, console.debug]) {
|
||||
console[consoleFn.name] = function (msg) {
|
||||
if (!currentTest) {
|
||||
consoleFn.apply(console, arguments);
|
||||
} else if (!_allowedTestOutput.some(a => a.test(msg)) && !_allowedTestsWithOutput.has(currentTest.title) && !_allowedSuitesWithOutput.has(currentTest.parent?.title)) {
|
||||
_testsWithUnexpectedOutput = true;
|
||||
consoleFn.apply(console, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Unexpected / Loader Errors
|
||||
|
||||
const _unexpectedErrors = [];
|
||||
const _loaderErrors = [];
|
||||
|
||||
const _allowedTestsWithUnhandledRejections = new Set([
|
||||
// Lifecycle tests
|
||||
'onWillShutdown - join with error is handled',
|
||||
'onBeforeShutdown - veto with error is treated as veto',
|
||||
'onBeforeShutdown - final veto with error is treated as veto',
|
||||
// Search tests
|
||||
'Search Model: Search reports timed telemetry on search when error is called'
|
||||
]);
|
||||
|
||||
loader.require.config({
|
||||
onError(err) {
|
||||
_loaderErrors.push(err);
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
|
||||
loader.require(['vs/base/common/errors'], function (errors) {
|
||||
|
||||
const onUnexpectedError = function (err) {
|
||||
if (err.name === 'Canceled') {
|
||||
return; // ignore canceled errors that are common
|
||||
}
|
||||
|
||||
let stack = (err ? err.stack : null);
|
||||
if (!stack) {
|
||||
stack = new Error().stack;
|
||||
}
|
||||
|
||||
_unexpectedErrors.push((err && err.message ? err.message : err) + '\n' + stack);
|
||||
};
|
||||
|
||||
process.on('uncaughtException', error => onUnexpectedError(error));
|
||||
process.on('unhandledRejection', (reason, promise) => {
|
||||
onUnexpectedError(reason);
|
||||
promise.catch(() => { });
|
||||
});
|
||||
window.addEventListener('unhandledrejection', event => {
|
||||
event.preventDefault(); // Do not log to test output, we show an error later when test ends
|
||||
event.stopPropagation();
|
||||
|
||||
if (!_allowedTestsWithUnhandledRejections.has(currentTest.title)) {
|
||||
onUnexpectedError(event.reason);
|
||||
}
|
||||
});
|
||||
|
||||
errors.setUnexpectedErrorHandler(err => unexpectedErrorHandler(err));
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
return loadWorkbenchTestingUtilsModule().then((workbenchTestingModule) => {
|
||||
const assertCleanState = workbenchTestingModule.assertCleanState;
|
||||
|
||||
suite('Tests are using suiteSetup and setup correctly', () => {
|
||||
test('assertCleanState - check that registries are clean at the start of test running', () => {
|
||||
assertCleanState();
|
||||
});
|
||||
});
|
||||
|
||||
setup(async () => {
|
||||
await perTestCoverage?.startTest();
|
||||
});
|
||||
|
||||
teardown(async () => {
|
||||
await perTestCoverage?.finishTest(currentTest.file, currentTest.fullTitle());
|
||||
|
||||
// should not have unexpected output
|
||||
// if (_testsWithUnexpectedOutput && !opts.dev) {
|
||||
// assert.ok(false, 'Error: Unexpected console output in test run. Please ensure no console.[log|error|info|warn] usage in tests or runtime errors.');
|
||||
// }
|
||||
|
||||
// should not have unexpected errors
|
||||
const errors = _unexpectedErrors.concat(_loaderErrors);
|
||||
if (errors.length) {
|
||||
for (const error of errors) {
|
||||
console.error(`Error: Test run should not have unexpected errors:\n${error}`);
|
||||
}
|
||||
assert.ok(false, 'Error: Test run should not have unexpected errors.');
|
||||
}
|
||||
});
|
||||
|
||||
suiteTeardown(() => { // intentionally not in teardown because some tests only cleanup in suiteTeardown
|
||||
|
||||
// should have cleaned up in registries
|
||||
assertCleanState();
|
||||
});
|
||||
|
||||
return loadTestModules(opts);
|
||||
});
|
||||
}
|
||||
|
||||
function serializeSuite(suite) {
|
||||
return {
|
||||
root: suite.root,
|
||||
suites: suite.suites.map(serializeSuite),
|
||||
tests: suite.tests.map(serializeRunnable),
|
||||
title: suite.title,
|
||||
fullTitle: suite.fullTitle(),
|
||||
titlePath: suite.titlePath(),
|
||||
timeout: suite.timeout(),
|
||||
retries: suite.retries(),
|
||||
slow: suite.slow(),
|
||||
bail: suite.bail()
|
||||
};
|
||||
}
|
||||
|
||||
function serializeRunnable(runnable) {
|
||||
return {
|
||||
title: runnable.title,
|
||||
fullTitle: runnable.fullTitle(),
|
||||
titlePath: runnable.titlePath(),
|
||||
async: runnable.async,
|
||||
slow: runnable.slow(),
|
||||
speed: runnable.speed,
|
||||
duration: runnable.duration
|
||||
};
|
||||
}
|
||||
|
||||
function serializeError(err) {
|
||||
return {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
snapshotPath: err.snapshotPath,
|
||||
actual: safeStringify({ value: err.actual }),
|
||||
expected: safeStringify({ value: err.expected }),
|
||||
uncaught: err.uncaught,
|
||||
showDiff: err.showDiff,
|
||||
inspect: typeof err.inspect === 'function' ? err.inspect() : ''
|
||||
};
|
||||
}
|
||||
|
||||
function safeStringify(obj) {
|
||||
const seen = new Set();
|
||||
return JSON.stringify(obj, (key, value) => {
|
||||
if (value === undefined) {
|
||||
return '[undefined]';
|
||||
}
|
||||
|
||||
if (isObject(value) || Array.isArray(value)) {
|
||||
if (seen.has(value)) {
|
||||
return '[Circular]';
|
||||
} else {
|
||||
seen.add(value);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
}
|
||||
|
||||
function isObject(obj) {
|
||||
// The method can't do a type cast since there are type (like strings) which
|
||||
// are subclasses of any put not positvely matched by the function. Hence type
|
||||
// narrowing results in wrong results.
|
||||
return typeof obj === 'object'
|
||||
&& obj !== null
|
||||
&& !Array.isArray(obj)
|
||||
&& !(obj instanceof RegExp)
|
||||
&& !(obj instanceof Date);
|
||||
}
|
||||
|
||||
class IPCReporter {
|
||||
|
||||
constructor(runner) {
|
||||
runner.on('start', () => ipcRenderer.send('start'));
|
||||
runner.on('end', () => ipcRenderer.send('end'));
|
||||
runner.on('suite', suite => ipcRenderer.send('suite', serializeSuite(suite)));
|
||||
runner.on('suite end', suite => ipcRenderer.send('suite end', serializeSuite(suite)));
|
||||
runner.on('test', test => ipcRenderer.send('test', serializeRunnable(test)));
|
||||
runner.on('test end', test => ipcRenderer.send('test end', serializeRunnable(test)));
|
||||
runner.on('hook', hook => ipcRenderer.send('hook', serializeRunnable(hook)));
|
||||
runner.on('hook end', hook => ipcRenderer.send('hook end', serializeRunnable(hook)));
|
||||
runner.on('pass', test => ipcRenderer.send('pass', serializeRunnable(test)));
|
||||
runner.on('fail', (test, err) => ipcRenderer.send('fail', serializeRunnable(test), serializeError(err)));
|
||||
runner.on('pending', test => ipcRenderer.send('pending', serializeRunnable(test)));
|
||||
}
|
||||
}
|
||||
|
||||
function runTests(opts) {
|
||||
// this *must* come before loadTests, or it doesn't work.
|
||||
if (opts.timeout !== undefined) {
|
||||
mocha.timeout(opts.timeout);
|
||||
}
|
||||
|
||||
return loadTests(opts).then(() => {
|
||||
|
||||
if (opts.grep) {
|
||||
mocha.grep(opts.grep);
|
||||
}
|
||||
|
||||
if (!opts.dev) {
|
||||
mocha.reporter(IPCReporter);
|
||||
}
|
||||
|
||||
const runner = mocha.run(() => {
|
||||
createCoverageReport(opts).then(() => {
|
||||
ipcRenderer.send('all done');
|
||||
});
|
||||
});
|
||||
|
||||
runner.on('test', test => currentTest = test);
|
||||
|
||||
if (opts.dev) {
|
||||
runner.on('fail', (test, err) => {
|
||||
console.error(test.fullTitle());
|
||||
console.error(err.stack);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ipcRenderer.on('run', (e, opts) => {
|
||||
initNls(opts);
|
||||
initLoader(opts);
|
||||
runTests(opts).catch(err => {
|
||||
if (typeof err !== 'string') {
|
||||
err = JSON.stringify(err);
|
||||
}
|
||||
|
||||
console.error(err);
|
||||
ipcRenderer.send('error', err);
|
||||
});
|
||||
});
|
||||
|
||||
class PerTestCoverage {
|
||||
static async init() {
|
||||
await ipcRenderer.invoke('startCoverage');
|
||||
return new PerTestCoverage();
|
||||
}
|
||||
|
||||
async startTest() {
|
||||
if (!this.didInit) {
|
||||
this.didInit = true;
|
||||
await ipcRenderer.invoke('snapshotCoverage');
|
||||
}
|
||||
}
|
||||
|
||||
async finishTest(file, fullTitle) {
|
||||
await ipcRenderer.invoke('snapshotCoverage', { file, fullTitle });
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
//@ts-check
|
||||
'use strict';
|
||||
|
||||
process.env.MOCHA_COLORS = '1'; // Force colors (note that this must come before any mocha imports)
|
||||
|
||||
const assert = require('assert');
|
||||
const Mocha = require('mocha');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const glob = require('glob');
|
||||
const minimatch = require('minimatch');
|
||||
const coverage = require('../coverage');
|
||||
const minimist = require('minimist');
|
||||
const { takeSnapshotAndCountClasses } = require('../analyzeSnapshot');
|
||||
|
||||
/**
|
||||
* @type {{ build: boolean; run: string; runGlob: string; coverage: boolean; help: boolean; coverageFormats: string | string[]; coveragePath: string; }}
|
||||
*/
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
boolean: ['build', 'coverage', 'help'],
|
||||
string: ['run', 'coveragePath', 'coverageFormats'],
|
||||
alias: {
|
||||
h: 'help'
|
||||
},
|
||||
default: {
|
||||
build: false,
|
||||
coverage: false,
|
||||
help: false
|
||||
},
|
||||
description: {
|
||||
build: 'Run from out-build',
|
||||
run: 'Run a single file',
|
||||
coverage: 'Generate a coverage report',
|
||||
coveragePath: 'Path to coverage report to generate',
|
||||
coverageFormats: 'Coverage formats to generate',
|
||||
help: 'Show help'
|
||||
}
|
||||
});
|
||||
|
||||
if (args.help) {
|
||||
console.log(`Usage: node test/unit/node/index [options]
|
||||
|
||||
Options:
|
||||
--build Run from out-build
|
||||
--run <file> Run a single file
|
||||
--coverage Generate a coverage report
|
||||
--help Show help`);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const TEST_GLOB = '**/test/**/*.test.js';
|
||||
|
||||
const excludeGlobs = [
|
||||
'**/{browser,electron-sandbox,electron-main,electron-utility}/**/*.test.js',
|
||||
'**/vs/platform/environment/test/node/nativeModules.test.js', // native modules are compiled against Electron and this test would fail with node.js
|
||||
'**/vs/base/parts/storage/test/node/storage.test.js', // same as above, due to direct dependency to sqlite native module
|
||||
'**/vs/workbench/contrib/testing/test/**' // flaky (https://github.com/microsoft/vscode/issues/137853)
|
||||
];
|
||||
|
||||
const REPO_ROOT = path.join(__dirname, '../../../');
|
||||
const out = args.build ? 'out-build' : 'out';
|
||||
const loader = require(`../../../${out}/vs/loader`);
|
||||
const src = path.join(REPO_ROOT, out);
|
||||
|
||||
//@ts-ignore
|
||||
const majorRequiredNodeVersion = `v${/^target="(.*)"$/m.exec(fs.readFileSync(path.join(REPO_ROOT, 'remote', '.npmrc'), 'utf8'))[1]}`.substring(0, 3);
|
||||
const currentMajorNodeVersion = process.version.substring(0, 3);
|
||||
if (majorRequiredNodeVersion !== currentMajorNodeVersion) {
|
||||
console.error(`node.js unit tests require a major node.js version of ${majorRequiredNodeVersion} (your version is: ${currentMajorNodeVersion})`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function main() {
|
||||
|
||||
// VSCODE_GLOBALS: package/product.json
|
||||
globalThis._VSCODE_PRODUCT_JSON = require(`${REPO_ROOT}/product.json`);
|
||||
globalThis._VSCODE_PACKAGE_JSON = require(`${REPO_ROOT}/package.json`);
|
||||
|
||||
if (args.build) {
|
||||
// when running from `out-build`, ensure to load the default
|
||||
// messages file, because all `nls.localize` calls have their
|
||||
// english values removed and replaced by an index.
|
||||
globalThis._VSCODE_NLS_MESSAGES = require(`../../../${out}/nls.messages.json`);
|
||||
}
|
||||
|
||||
// Test file operations that are common across platforms. Used for test infra, namely snapshot tests
|
||||
Object.assign(globalThis, {
|
||||
__analyzeSnapshotInTests: takeSnapshotAndCountClasses,
|
||||
__readFileInTests: (/** @type {string} */ path) => fs.promises.readFile(path, 'utf-8'),
|
||||
__writeFileInTests: (/** @type {string} */ path, /** @type {BufferEncoding} */ contents) => fs.promises.writeFile(path, contents),
|
||||
__readDirInTests: (/** @type {string} */ path) => fs.promises.readdir(path),
|
||||
__unlinkInTests: (/** @type {string} */ path) => fs.promises.unlink(path),
|
||||
__mkdirPInTests: (/** @type {string} */ path) => fs.promises.mkdir(path, { recursive: true }),
|
||||
});
|
||||
|
||||
process.on('uncaughtException', function (e) {
|
||||
console.error(e.stack || e);
|
||||
});
|
||||
|
||||
const bootstrapNode = require(`../../../${out}/bootstrap-node`);
|
||||
|
||||
const loaderConfig = {
|
||||
nodeRequire: require,
|
||||
baseUrl: bootstrapNode.fileUriFromPath(src, { isWindows: process.platform === 'win32' }),
|
||||
catchError: true
|
||||
};
|
||||
|
||||
if (args.coverage) {
|
||||
coverage.initialize(loaderConfig);
|
||||
|
||||
process.on('exit', function (code) {
|
||||
if (code !== 0) {
|
||||
return;
|
||||
}
|
||||
coverage.createReport(args.run || args.runGlob, args.coveragePath, args.coverageFormats);
|
||||
});
|
||||
}
|
||||
|
||||
loader.config(loaderConfig);
|
||||
|
||||
let didErr = false;
|
||||
const write = process.stderr.write;
|
||||
process.stderr.write = function (...args) {
|
||||
didErr = didErr || !!args[0];
|
||||
return write.apply(process.stderr, args);
|
||||
};
|
||||
|
||||
|
||||
const runner = new Mocha({
|
||||
ui: 'tdd'
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string[]} modules
|
||||
*/
|
||||
async function loadModules(modules) {
|
||||
for (const file of modules) {
|
||||
runner.suite.emit(Mocha.Suite.constants.EVENT_FILE_PRE_REQUIRE, globalThis, file, runner);
|
||||
const m = await new Promise((resolve, reject) => loader([file], resolve, reject));
|
||||
runner.suite.emit(Mocha.Suite.constants.EVENT_FILE_REQUIRE, m, file, runner);
|
||||
runner.suite.emit(Mocha.Suite.constants.EVENT_FILE_POST_REQUIRE, globalThis, file, runner);
|
||||
}
|
||||
}
|
||||
|
||||
/** @type { null|((callback:(err:any)=>void)=>void) } */
|
||||
let loadFunc = null;
|
||||
|
||||
if (args.runGlob) {
|
||||
loadFunc = (cb) => {
|
||||
const doRun = /** @param {string[]} tests */(tests) => {
|
||||
const modulesToLoad = tests.map(test => {
|
||||
if (path.isAbsolute(test)) {
|
||||
test = path.relative(src, path.resolve(test));
|
||||
}
|
||||
|
||||
return test.replace(/(\.js)|(\.d\.ts)|(\.js\.map)$/, '');
|
||||
});
|
||||
loadModules(modulesToLoad).then(() => cb(null), cb);
|
||||
};
|
||||
|
||||
glob(args.runGlob, { cwd: src }, function (err, files) { doRun(files); });
|
||||
};
|
||||
} else if (args.run) {
|
||||
const tests = (typeof args.run === 'string') ? [args.run] : args.run;
|
||||
const modulesToLoad = tests.map(function (test) {
|
||||
test = test.replace(/^src/, 'out');
|
||||
test = test.replace(/\.ts$/, '.js');
|
||||
return path.relative(src, path.resolve(test)).replace(/(\.js)|(\.js\.map)$/, '').replace(/\\/g, '/');
|
||||
});
|
||||
loadFunc = (cb) => {
|
||||
loadModules(modulesToLoad).then(() => cb(null), cb);
|
||||
};
|
||||
} else {
|
||||
loadFunc = (cb) => {
|
||||
glob(TEST_GLOB, { cwd: src }, function (err, files) {
|
||||
/** @type {string[]} */
|
||||
const modules = [];
|
||||
for (const file of files) {
|
||||
if (!excludeGlobs.some(excludeGlob => minimatch(file, excludeGlob))) {
|
||||
modules.push(file.replace(/\.js$/, ''));
|
||||
}
|
||||
}
|
||||
loadModules(modules).then(() => cb(null), cb);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
loadFunc(function (err) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
return process.exit(1);
|
||||
}
|
||||
|
||||
process.stderr.write = write;
|
||||
|
||||
if (!args.run && !args.runGlob) {
|
||||
// set up last test
|
||||
Mocha.suite('Loader', function () {
|
||||
test('should not explode while loading', function () {
|
||||
assert.ok(!didErr, `should not explode while loading: ${didErr}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// report failing test for every unexpected error during any of the tests
|
||||
const unexpectedErrors = [];
|
||||
Mocha.suite('Errors', function () {
|
||||
test('should not have unexpected errors in tests', function () {
|
||||
if (unexpectedErrors.length) {
|
||||
unexpectedErrors.forEach(function (stack) {
|
||||
console.error('');
|
||||
console.error(stack);
|
||||
});
|
||||
|
||||
assert.ok(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// replace the default unexpected error handler to be useful during tests
|
||||
loader(['vs/base/common/errors'], function (errors) {
|
||||
errors.setUnexpectedErrorHandler(function (err) {
|
||||
const stack = (err && err.stack) || (new Error().stack);
|
||||
unexpectedErrors.push((err && err.message ? err.message : err) + '\n' + stack);
|
||||
});
|
||||
|
||||
// fire up mocha
|
||||
runner.run(failures => process.exit(failures ? 1 : 0));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user