Skip to content

Commit 580b072

Browse files
committed
feat: parallel test running for node and the browser
1 parent e91441e commit 580b072

File tree

16 files changed

+716
-62
lines changed

16 files changed

+716
-62
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"files": [
3333
"bin/",
3434
"src/cli/",
35+
"src/workers/",
3536
"qunit/qunit.js",
3637
"qunit/qunit.css",
3738
"LICENSE.txt"

src/cli/run.js

Lines changed: 87 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const { findReporter } = require( "./find-reporter" );
1010

1111
const DEBOUNCE_WATCH_LENGTH = 60;
1212
const DEBOUNCE_RESTART_LENGTH = 200 - DEBOUNCE_WATCH_LENGTH;
13+
const os = require( "os" );
1314

1415
const changedPendingPurge = [];
1516

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

23-
const files = utils.getFilesFromArgs( args );
24-
2524
QUnit = requireQUnit();
2625

26+
let globalConfig = {};
27+
28+
// TODO: Enable mode where QUnit is not auto-injected, but other setup is
29+
// still done automatically.
30+
if ( global.QUnit && global.QUnit.config ) {
31+
globalConfig = global.QUnit.config;
32+
}
33+
global.QUnit = QUnit;
34+
35+
Object.keys( globalConfig ).forEach( function( key ) {
36+
QUnit.config[ key ] = globalConfig[ key ];
37+
} );
38+
2739
if ( options.filter ) {
2840
QUnit.config.filter = options.filter;
2941
}
3042

3143
const seed = options.seed;
44+
3245
if ( seed ) {
3346
if ( seed === true ) {
3447
QUnit.config.seed = Math.random().toString( 36 ).slice( 2 );
@@ -39,65 +52,80 @@ async function run( args, options ) {
3952
console.log( `Running tests with seed: ${QUnit.config.seed}` );
4053
}
4154

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

46-
options.requires.forEach( requireFromCWD );
59+
if ( !QUnit.config.isWorker ) {
60+
QUnit.config.maxThreads = os.cpus().length;
61+
QUnit.config.workerType = "NodeWorker";
62+
QUnit.config.files = args;
63+
64+
// eslint-disable-next-line node/no-unsupported-features/es-syntax
65+
const nodeWorkerModule = await import( "../workers/node-worker.mjs" );
66+
const NodeWorker = nodeWorkerModule.default;
67+
68+
QUnit.WorkerFactory.registerWorkerClass( NodeWorker );
69+
70+
} else {
71+
options.requires.forEach( requireFromCWD );
72+
73+
const files = utils.getFilesFromArgs( args );
74+
75+
for ( let i = 0; i < files.length; i++ ) {
76+
const filePath = path.resolve( process.cwd(), files[ i ] );
77+
delete require.cache[ filePath ];
78+
79+
// Node.js 12.0.0 has node_module_version=72
80+
// https://nodejs.org/en/download/releases/
81+
const nodeVint = process.config.variables.node_module_version;
4782

48-
findReporter( options.reporter, QUnit.reporters ).init( QUnit );
49-
50-
for ( let i = 0; i < files.length; i++ ) {
51-
const filePath = path.resolve( process.cwd(), files[ i ] );
52-
delete require.cache[ filePath ];
53-
54-
// Node.js 12.0.0 has node_module_version=72
55-
// https://nodejs.org/en/download/releases/
56-
const nodeVint = process.config.variables.node_module_version;
57-
58-
try {
59-
60-
// QUnit supports passing ESM files to the 'qunit' command when used on
61-
// Node.js 12 or later. The dynamic import() keyword supports both CommonJS files
62-
// (.js, .cjs) and ESM files (.mjs), so we could simply use that unconditionally on
63-
// newer Node versions, regardless of the given file path.
64-
//
65-
// But:
66-
// - Node.js 12 emits a confusing "ExperimentalWarning" when using import(),
67-
// even if just to load a non-ESM file. So we should try to avoid it on non-ESM.
68-
// - This Node.js feature is still considered experimental so to avoid unexpected
69-
// breakage we should continue using require(). Consider flipping once stable and/or
70-
// as part of QUnit 3.0.
71-
// - Plugins and CLI bootstrap scripts may be hooking into require.extensions to modify
72-
// or transform code as it gets loaded. For compatibility with that, we should
73-
// support that until at least QUnit 3.0.
74-
// - File extensions are not sufficient to differentiate between CJS and ESM.
75-
// Use of ".mjs" is optional, as a package may configure Node to default to ESM
76-
// and optionally use ".cjs" for CJS files.
77-
//
78-
// https://nodejs.org/docs/v12.7.0/api/modules.html#modules_addenda_the_mjs_extension
79-
// https://nodejs.org/docs/v12.7.0/api/esm.html#esm_code_import_code_expressions
80-
// https://github.com/qunitjs/qunit/issues/1465
8183
try {
82-
require( filePath );
83-
} catch ( e ) {
84-
if ( ( e.code === "ERR_REQUIRE_ESM" ||
85-
( e instanceof SyntaxError &&
86-
e.message === "Cannot use import statement outside a module" ) ) &&
87-
( !nodeVint || nodeVint >= 72 ) ) {
88-
89-
// filePath is an absolute file path here (per path.resolve above).
90-
// On Windows, Node.js enforces that absolute paths via ESM use valid URLs,
91-
// e.g. file-protocol) https://github.com/qunitjs/qunit/issues/1667
92-
await import( url.pathToFileURL( filePath ) ); // eslint-disable-line node/no-unsupported-features/es-syntax
93-
} else {
94-
throw e;
84+
85+
// QUnit supports passing ESM files to the 'qunit' command when used on
86+
// Node.js 12 or later. The dynamic import() keyword supports both CommonJS files
87+
// (.js, .cjs) and ESM files (.mjs), so we could simply use that unconditionally on
88+
// newer Node versions, regardless of the given file path.
89+
//
90+
// But:
91+
// - Node.js 12 emits a confusing "ExperimentalWarning" when using import(),
92+
// even if just to load a non-ESM file. So we should try to avoid it on non-ESM.
93+
// - This Node.js feature is still considered experimental so to avoid unexpected
94+
// breakage we should continue using require(). Consider flipping once stable and/or
95+
// as part of QUnit 3.0.
96+
// - Plugins and CLI bootstrap scripts may be hooking into require.extensions to modify
97+
// or transform code as it gets loaded. For compatibility with that, we should
98+
// support that until at least QUnit 3.0.
99+
// - File extensions are not sufficient to differentiate between CJS and ESM.
100+
// Use of ".mjs" is optional, as a package may configure Node to default to ESM
101+
// and optionally use ".cjs" for CJS files.
102+
//
103+
// https://nodejs.org/docs/v12.7.0/api/modules.html#modules_addenda_the_mjs_extension
104+
// https://nodejs.org/docs/v12.7.0/api/esm.html#esm_code_import_code_expressions
105+
// https://github.com/qunitjs/qunit/issues/1465
106+
try {
107+
require( filePath );
108+
} catch ( e ) {
109+
if ( ( e.code === "ERR_REQUIRE_ESM" ||
110+
( e instanceof SyntaxError &&
111+
e.message === "Cannot use import statement outside a module" ) ) &&
112+
( !nodeVint || nodeVint >= 72 ) ) {
113+
114+
// filePath is an absolute file path here (per path.resolve above).
115+
// On Windows, Node.js enforces that absolute paths via ESM use valid URLs,
116+
// e.g. file-protocol) https://github.com/qunitjs/qunit/issues/1667
117+
await import( url.pathToFileURL( filePath ) ); // eslint-disable-line node/no-unsupported-features/es-syntax
118+
} else {
119+
throw e;
120+
}
95121
}
122+
} catch ( e ) {
123+
const error = new Error(
124+
`Failed to load file ${files[ i ]}\n${e.name}: ${e.message}`
125+
);
126+
error.stack = e.stack;
127+
QUnit.onUncaughtException( error );
96128
}
97-
} catch ( e ) {
98-
const error = new Error( `Failed to load file ${files[ i ]}\n${e.name}: ${e.message}` );
99-
error.stack = e.stack;
100-
QUnit.onUncaughtException( error );
101129
}
102130
}
103131

@@ -141,7 +169,9 @@ async function run( args, options ) {
141169
}
142170
} );
143171

144-
QUnit.start();
172+
if ( !QUnit.config.isWorker ) {
173+
QUnit.start();
174+
}
145175
}
146176

147177
run.restart = function( args ) {

0 commit comments

Comments
 (0)