From 17f6bcb7d2d89a03c53319f93b1e677053612d96 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Fri, 6 Dec 2024 18:53:29 +0100 Subject: [PATCH] chore - modernise test runner (#235511) async/wait, remove AMD/ESM split, cleanup --- test/unit/electron/renderer.js | 236 ++++++++++++++++----------------- 1 file changed, 114 insertions(+), 122 deletions(-) diff --git a/test/unit/electron/renderer.js b/test/unit/electron/renderer.js index 7c28a98930c..b93d91a78e9 100644 --- a/test/unit/electron/renderer.js +++ b/test/unit/electron/renderer.js @@ -5,6 +5,8 @@ /*eslint-env mocha*/ +// @ts-check + const fs = require('fs'); (function () { @@ -24,7 +26,7 @@ const fs = require('fs'); 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')) : '')); + 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); }; @@ -88,9 +90,18 @@ Object.assign(globalThis, { const IS_CI = !!process.env.BUILD_ARTIFACTSTAGINGDIRECTORY; const _tests_glob = '**/test/**/*.test.js'; -let loader; + + +/** + * Loads one or N modules. + * @type {{ + * (module: string|string[]): Promise|Promise; + * _out: string; + * }} + */ +let loadFn; + const _loaderErrors = []; -let _out; function initNls(opts) { if (opts.build) { @@ -101,20 +112,17 @@ function initNls(opts) { } } -function initLoader(opts) { +function initLoadFn(opts) { const outdir = opts.build ? 'out-build' : 'out'; - _out = path.join(__dirname, `../../../${outdir}`); + const out = path.join(__dirname, `../../../${outdir}`); const baseUrl = pathToFileURL(path.join(__dirname, `../../../${outdir}/`)); globalThis._VSCODE_FILE_ROOT = baseUrl.href; // set loader - /** - * @param {string[]} modules - * @param {(...args:any[]) => void} callback - */ - function esmRequire(modules, callback, errorback) { - const tasks = modules.map(mod => { + function importModules(modules) { + const moduleArray = Array.isArray(modules) ? modules : [modules]; + const tasks = moduleArray.map(mod => { const url = new URL(`./${mod}.js`, baseUrl).href; return import(url).catch(err => { console.log(mod, url); @@ -124,35 +132,33 @@ function initLoader(opts) { }); }); - Promise.all(tasks).then(modules => callback(...modules)).catch(errorback); + return Array.isArray(modules) + ? Promise.all(tasks) + : tasks[0]; } - - loader = { require: esmRequire }; + importModules._out = out; + loadFn = importModules; } -function createCoverageReport(opts) { - if (opts.coverage) { - return coverage.createReport(opts.run || opts.runGlob); +async function createCoverageReport(opts) { + if (!opts.coverage) { + return undefined; } - return Promise.resolve(undefined); -} - -function loadWorkbenchTestingUtilsModule() { - return new Promise((resolve, reject) => { - loader.require(['vs/workbench/test/common/utils'], resolve, reject); - }); + return coverage.createReport(opts.run || opts.runGlob); } 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)); + const m = await loadFn(file); 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) { +const globAsync = util.promisify(glob); + +async function loadTestModules(opts) { if (opts.run) { const files = Array.isArray(opts.run) ? opts.run : [opts.run]; @@ -164,17 +170,9 @@ function loadTestModules(opts) { } 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); + const files = await globAsync(pattern, { cwd: loadFn._out }); + const modules = files.map(file => file.replace(/\.js$/, '')); + return loadModules(modules); } /** @type Mocha.Test */ @@ -220,7 +218,7 @@ async function loadTests(opts) { 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)) { + } else if (!_allowedTestOutput.some(a => a.test(msg)) && !_allowedTestsWithOutput.has(currentTest.title) && !_allowedSuitesWithOutput.has(currentTest.parent?.title ?? '')) { _testsWithUnexpectedOutput = true; consoleFn.apply(console, arguments); } @@ -242,79 +240,74 @@ async function loadTests(opts) { 'Search Model: Search reports timed telemetry on search when error is called' ]); - loader.require(['vs/base/common/errors'], function (errors) { + const errors = await loadFn('vs/base/common/errors'); + const onUnexpectedError = function (err) { + if (err.name === 'Canceled') { + return; // ignore canceled errors that are common + } - 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; + } - let stack = (err ? err.stack : null); - if (!stack) { - stack = new Error().stack; - } + _unexpectedErrors.push((err && err.message ? err.message : err) + '\n' + 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(); - 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(onUnexpectedError); + if (!_allowedTestsWithUnhandledRejections.has(currentTest.title)) { + onUnexpectedError(event.reason); + } }); + errors.setUnexpectedErrorHandler(onUnexpectedError); //#endregion - return loadWorkbenchTestingUtilsModule().then((workbenchTestingModule) => { - const assertCleanState = workbenchTestingModule.assertCleanState; + const { assertCleanState } = await loadFn('vs/workbench/test/common/utils'); - 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 + suite('Tests are using suiteSetup and setup correctly', () => { + test('assertCleanState - check that registries are clean at the start of test running', () => { assertCleanState(); }); - - return loadTestModules(opts); }); + + 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) { @@ -403,42 +396,41 @@ class IPCReporter { } } -function runTests(opts) { +async 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(() => { + await loadTests(opts); - if (opts.grep) { - mocha.grep(opts.grep); - } + if (opts.grep) { + mocha.grep(opts.grep); + } - if (!opts.dev) { - mocha.reporter(IPCReporter); - } + if (!opts.dev) { + // @ts-expect-error + 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); - }); - } + const runner = mocha.run(async () => { + await createCoverageReport(opts) + 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', async (_e, opts) => { initNls(opts); - initLoader(opts); + initLoadFn(opts); await Promise.resolve(globalThis._VSCODE_TEST_INIT);