codeceptjs
Version:
Modern Era Acceptance Testing Framework for NodeJS
212 lines (177 loc) • 5.49 kB
JavaScript
const { satisfyNodeVersion, getConfig, getTestRoot } = require('./utils');
// For Node version >=10.5.0, have to use experimental flag
const ms = require('ms');
const path = require('path');
const Codecept = require('../codecept');
const Container = require('../container');
const { tryOrDefault } = require('../utils');
const output = require('../output');
const event = require('../event');
const runHook = require('../hooks');
const { Suite, Test, reporters: { Base } } = require('mocha');
const stats = {
suites: 0,
passes: 0,
failures: 0,
tests: 0,
};
let numberOfWorkersClosed = 0;
const hasFailures = () => stats.failures || errors.length;
const pathToWorker = path.join(__dirname, 'workers', 'runTests.js');
const finishedTests = [];
const errors = [];
module.exports = function (workers, options) {
satisfyNodeVersion(
'>=11.7.0',
'Required minimum Node version of 11.7.0 to work with "run-workers"',
);
process.profile = options.profile;
const { Worker } = require('worker_threads');
const { config: configPath, override = '' } = options;
const overrideConfigs = tryOrDefault(() => JSON.parse(override), {});
const numberOfWorkers = parseInt(workers, 10);
const config = {
...getConfig(configPath),
...overrideConfigs,
};
const testRoot = getTestRoot(configPath);
const codecept = new Codecept(config, options);
codecept.init(testRoot);
codecept.loadTests();
const groups = createGroupsOfTests(codecept, numberOfWorkers, options.suites);
stats.start = new Date();
output.print(`CodeceptJS v${require('../codecept').version()}`);
output.print(`Running tests in ${output.styles.bold(workers)} workers...`);
output.print();
// run bootstrap all
runHook(config.bootstrapAll, () => {
const workerList = createWorkers(groups, options, testRoot);
workerList.forEach(worker => assignWorkerMethods(worker, groups.length));
}, 'bootstrapAll');
function createWorkers(groups, options, testRoot) {
const workers = groups.map((tests, workerIndex) => {
workerIndex++;
return new Worker(pathToWorker, {
workerData: {
options: simplifyObject(options),
tests,
testRoot,
workerIndex,
},
});
});
return workers;
}
function assignWorkerMethods(worker, totalWorkers) {
worker.on('message', (message) => {
output.process(message.workerIndex);
switch (message.event) {
case event.test.failed:
finishedTests.push(repackTest(message.data));
output.test.failed(repackTest(message.data));
break;
case event.test.passed: output.test.passed(repackTest(message.data)); break;
case event.suite.before: output.suite.started(message.data); break;
case event.all.after: appendStats(message.data); break;
}
output.process(null);
});
worker.on('error', (err) => {
errors.push(err);
console.error(err);
});
worker.on('exit', () => {
numberOfWorkersClosed++;
if (numberOfWorkersClosed >= totalWorkers) {
printResults();
runHook(config.teardownAll, () => {
if (hasFailures()) process.exit(1);
process.exit(0);
});
}
});
}
};
function printResults() {
stats.end = new Date();
stats.duration = stats.end - stats.start;
output.print();
if (stats.tests === 0 || (stats.passes && !errors.length)) {
output.result(stats.passes, stats.failures, 0, `${stats.duration / 1000}s`);
}
if (stats.failures) {
output.print();
output.print('-- FAILURES:');
Base.list(finishedTests.filter(t => t.err));
}
}
function createGroupsOfTests(codecept, numberOfGroups, preserveSuites) {
const files = codecept.testFiles;
const mocha = Container.mocha();
mocha.files = files;
mocha.loadFiles();
const groups = [];
for (let i = 0; i < numberOfGroups; i++) {
groups[i] = [];
}
if (preserveSuites) {
return groupSuites(mocha.suite, groups);
}
return groupTests(mocha.suite, groups);
}
function groupTests(suite, groups) {
let groupCounter = 0;
suite.eachTest((test) => {
const i = groupCounter % groups.length;
if (test) {
const { id } = test;
groups[i].push(id);
groupCounter++;
}
});
return groups;
}
function groupSuites(suite, groups) {
suite.suites.forEach((suite) => {
const i = indexOfSmallestElement(groups);
suite.tests.forEach((test) => {
if (test) {
const { id } = test;
groups[i].push(id);
}
});
});
return groups;
}
function indexOfSmallestElement(groups) {
let i = 0;
for (let j = 1; j < groups.length; j++) {
if (groups[j - 1].length > groups[j].length) {
i = j;
}
}
return i;
}
function appendStats(newStats) {
stats.passes += newStats.passes;
stats.failures += newStats.failures;
stats.tests += newStats.tests;
}
function repackTest(test) {
test = Object.assign(new Test(test.title || '', () => { }), test);
test.parent = Object.assign(new Suite(test.parent.title), test.parent);
return test;
}
function repackSuite(suite) {
return Object.assign(new Suite(suite.title), suite);
}
function simplifyObject(object) {
return Object.keys(object)
.filter(k => k.indexOf('_') !== 0)
.filter(k => typeof object[k] !== 'function')
.filter(k => typeof object[k] !== 'object')
.reduce((obj, key) => {
obj[key] = object[key];
return obj;
}, {});
}