esm - remove AMD build scripts, tests and variables

This commit is contained in:
Benjamin Pasero
2024-09-27 08:04:00 +02:00
parent bdc5542f12
commit 332b89f198
43 changed files with 217 additions and 3335 deletions

View File

@@ -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);
});

View File

@@ -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>

View File

@@ -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);
});
}
});

View File

@@ -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>

View File

@@ -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 });
}
}

View File

@@ -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();