Skip to content

Commit

Permalink
feat: parallel test running for node and the browser
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonocasey committed Mar 26, 2022
1 parent e91441e commit 580b072
Show file tree
Hide file tree
Showing 16 changed files with 716 additions and 62 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"files": [
"bin/",
"src/cli/",
"src/workers/",
"qunit/qunit.js",
"qunit/qunit.css",
"LICENSE.txt"
Expand Down
144 changes: 87 additions & 57 deletions src/cli/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const { findReporter } = require( "./find-reporter" );

const DEBOUNCE_WATCH_LENGTH = 60;
const DEBOUNCE_RESTART_LENGTH = 200 - DEBOUNCE_WATCH_LENGTH;
const os = require( "os" );

const changedPendingPurge = [];

Expand All @@ -20,15 +21,27 @@ async function run( args, options ) {
// Default to non-zero exit code to avoid false positives
process.exitCode = 1;

const files = utils.getFilesFromArgs( args );

QUnit = requireQUnit();

let globalConfig = {};

// TODO: Enable mode where QUnit is not auto-injected, but other setup is
// still done automatically.
if ( global.QUnit && global.QUnit.config ) {
globalConfig = global.QUnit.config;
}
global.QUnit = QUnit;

Object.keys( globalConfig ).forEach( function( key ) {
QUnit.config[ key ] = globalConfig[ key ];
} );

if ( options.filter ) {
QUnit.config.filter = options.filter;
}

const seed = options.seed;

if ( seed ) {
if ( seed === true ) {
QUnit.config.seed = Math.random().toString( 36 ).slice( 2 );
Expand All @@ -39,65 +52,80 @@ async function run( args, options ) {
console.log( `Running tests with seed: ${QUnit.config.seed}` );
}

// TODO: Enable mode where QUnit is not auto-injected, but other setup is
// still done automatically.
global.QUnit = QUnit;
if ( !options.noReporter ) {
findReporter( options.reporter, QUnit.reporters ).init( QUnit );
}

options.requires.forEach( requireFromCWD );
if ( !QUnit.config.isWorker ) {
QUnit.config.maxThreads = os.cpus().length;
QUnit.config.workerType = "NodeWorker";
QUnit.config.files = args;

// eslint-disable-next-line node/no-unsupported-features/es-syntax
const nodeWorkerModule = await import( "../workers/node-worker.mjs" );
const NodeWorker = nodeWorkerModule.default;

QUnit.WorkerFactory.registerWorkerClass( NodeWorker );

} else {
options.requires.forEach( requireFromCWD );

const files = utils.getFilesFromArgs( args );

for ( let i = 0; i < files.length; i++ ) {
const filePath = path.resolve( process.cwd(), files[ i ] );
delete require.cache[ filePath ];

// Node.js 12.0.0 has node_module_version=72
// https://nodejs.org/en/download/releases/
const nodeVint = process.config.variables.node_module_version;

findReporter( options.reporter, QUnit.reporters ).init( QUnit );

for ( let i = 0; i < files.length; i++ ) {
const filePath = path.resolve( process.cwd(), files[ i ] );
delete require.cache[ filePath ];

// Node.js 12.0.0 has node_module_version=72
// https://nodejs.org/en/download/releases/
const nodeVint = process.config.variables.node_module_version;

try {

// QUnit supports passing ESM files to the 'qunit' command when used on
// Node.js 12 or later. The dynamic import() keyword supports both CommonJS files
// (.js, .cjs) and ESM files (.mjs), so we could simply use that unconditionally on
// newer Node versions, regardless of the given file path.
//
// But:
// - Node.js 12 emits a confusing "ExperimentalWarning" when using import(),
// even if just to load a non-ESM file. So we should try to avoid it on non-ESM.
// - This Node.js feature is still considered experimental so to avoid unexpected
// breakage we should continue using require(). Consider flipping once stable and/or
// as part of QUnit 3.0.
// - Plugins and CLI bootstrap scripts may be hooking into require.extensions to modify
// or transform code as it gets loaded. For compatibility with that, we should
// support that until at least QUnit 3.0.
// - File extensions are not sufficient to differentiate between CJS and ESM.
// Use of ".mjs" is optional, as a package may configure Node to default to ESM
// and optionally use ".cjs" for CJS files.
//
// https://nodejs.org/docs/v12.7.0/api/modules.html#modules_addenda_the_mjs_extension
// https://nodejs.org/docs/v12.7.0/api/esm.html#esm_code_import_code_expressions
// https://github.com/qunitjs/qunit/issues/1465
try {
require( filePath );
} catch ( e ) {
if ( ( e.code === "ERR_REQUIRE_ESM" ||
( e instanceof SyntaxError &&
e.message === "Cannot use import statement outside a module" ) ) &&
( !nodeVint || nodeVint >= 72 ) ) {

// filePath is an absolute file path here (per path.resolve above).
// On Windows, Node.js enforces that absolute paths via ESM use valid URLs,
// e.g. file-protocol) https://github.com/qunitjs/qunit/issues/1667
await import( url.pathToFileURL( filePath ) ); // eslint-disable-line node/no-unsupported-features/es-syntax
} else {
throw e;

// QUnit supports passing ESM files to the 'qunit' command when used on
// Node.js 12 or later. The dynamic import() keyword supports both CommonJS files
// (.js, .cjs) and ESM files (.mjs), so we could simply use that unconditionally on
// newer Node versions, regardless of the given file path.
//
// But:
// - Node.js 12 emits a confusing "ExperimentalWarning" when using import(),
// even if just to load a non-ESM file. So we should try to avoid it on non-ESM.
// - This Node.js feature is still considered experimental so to avoid unexpected
// breakage we should continue using require(). Consider flipping once stable and/or
// as part of QUnit 3.0.
// - Plugins and CLI bootstrap scripts may be hooking into require.extensions to modify
// or transform code as it gets loaded. For compatibility with that, we should
// support that until at least QUnit 3.0.
// - File extensions are not sufficient to differentiate between CJS and ESM.
// Use of ".mjs" is optional, as a package may configure Node to default to ESM
// and optionally use ".cjs" for CJS files.
//
// https://nodejs.org/docs/v12.7.0/api/modules.html#modules_addenda_the_mjs_extension
// https://nodejs.org/docs/v12.7.0/api/esm.html#esm_code_import_code_expressions
// https://github.com/qunitjs/qunit/issues/1465
try {
require( filePath );
} catch ( e ) {
if ( ( e.code === "ERR_REQUIRE_ESM" ||
( e instanceof SyntaxError &&
e.message === "Cannot use import statement outside a module" ) ) &&
( !nodeVint || nodeVint >= 72 ) ) {

// filePath is an absolute file path here (per path.resolve above).
// On Windows, Node.js enforces that absolute paths via ESM use valid URLs,
// e.g. file-protocol) https://github.com/qunitjs/qunit/issues/1667
await import( url.pathToFileURL( filePath ) ); // eslint-disable-line node/no-unsupported-features/es-syntax
} else {
throw e;
}
}
} catch ( e ) {
const error = new Error(
`Failed to load file ${files[ i ]}\n${e.name}: ${e.message}`
);
error.stack = e.stack;
QUnit.onUncaughtException( error );
}
} catch ( e ) {
const error = new Error( `Failed to load file ${files[ i ]}\n${e.name}: ${e.message}` );
error.stack = e.stack;
QUnit.onUncaughtException( error );
}
}

Expand Down Expand Up @@ -141,7 +169,9 @@ async function run( args, options ) {
}
} );

QUnit.start();
if ( !QUnit.config.isWorker ) {
QUnit.start();
}
}

run.restart = function( args ) {
Expand Down
Loading

0 comments on commit 580b072

Please sign in to comment.