blob: d278d90b4d89248645023ed0627982f2717554d8 [file] [log] [blame]
// Copyright 2013 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Various test helpers meant to be reused by other modules.
* @suppress {moduleLoad} closure compiler can't handle node_modules/.
*/
import {lib} from '../../libdot/index.js';
import {assert} from '../../node_modules/chai/chai.js';
/**
* Run the main test page.
*
* @param {{
* globals: (!Array<string>|undefined),
* base: string,
* files: !Array<string>,
* }=} options
* globals: Global variables that are exempt from leak checks.
* base: The prefix to load files from.
* files: Test files to import dynamically.
*/
export function main({globals, base = '', files = []} = {}) {
// Setup the mocha framework.
mocha.setup({ui: 'bdd', globals});
mocha.checkLeaks();
// Add a global shortcut to the assert API.
globalThis['assert'] = assert;
// Catch any random errors before the test runner runs.
let earlyError = null;
/**
* Catch any errors.
*
* @param {...*} args Whatever arguments are passed in.
*/
globalThis.onerror = function(...args) {
earlyError = Array.from(args);
};
/** Run the test framework once everything is finished. */
globalThis.onload = async function() {
// Serially fetch files to guarantee stability in execution order.
// Performance wise it's basically the same.
for (const file of files) {
suiteStart(file);
await import(`${base}/${file}`);
suiteEnd();
}
mocha.run();
};
describe('testRunner', () => {
/** Make sure no general framework errors happened (e.g. syntax errors). */
it('uncaught framework errors', () => {
if (earlyError !== null) {
assert.fail(`uncaught exception detected:\n${earlyError.join('\n')}`);
}
});
});
}
/**
* Mocha functions that we need to wrap between suite{Start,End}.
*
* See suiteStart for details.
*/
const wrappedFunctions = [
'after',
'afterEach',
'before',
'beforeEach',
'describe',
'it',
'xit',
];
let currentSuite = null;
const queuedCalls = [];
const backupMocha = [];
/**
* Capture calls to mocha APIs to delay until suiteEnd.
*
* See suiteStart for details.
*
* @param {string} fn The function we wrapped.
* @param {...*} args The arguments to the function.
*/
function captureCall(fn, ...args) {
queuedCalls.push([fn, args]);
}
/**
* Group a bunch of tests into a single suite.
*
* This is akin to using describe(), but without the nesting.
*
* describe('foo', () => {
* ...
* });
*
* suiteStart('foo');
* ...
* suiteEnd();
*
* @param {string} name The name to give to the suite.
*/
function suiteStart(name) {
// We only support one level of nesting atm.
assert.isNull(currentSuite);
currentSuite = name;
// Wrap all the mocha calls.
wrappedFunctions.forEach((x) => {
backupMocha[x] = globalThis[x];
globalThis[x] = captureCall.bind(null, x);
});
}
/**
* End the current suite.
*/
function suiteEnd() {
// Make sure suiteStart was called.
assert.isNotNull(currentSuite);
// Restore the mocha APIs.
wrappedFunctions.forEach((x) => {
globalThis[x] = backupMocha[x];
});
backupMocha.length = 0;
// Make all the delayed calls now inside a single describe().
describe(lib.notNull(currentSuite), function() {
queuedCalls.forEach(([fn, args]) => {
globalThis[fn](...args);
});
});
queuedCalls.length = 0;
currentSuite = null;
}