Skip to content

Commit

Permalink
more implementation, tests
Browse files Browse the repository at this point in the history
  • Loading branch information
boneskull committed Jul 7, 2020
1 parent 9acfabb commit 8b38307
Show file tree
Hide file tree
Showing 9 changed files with 398 additions and 135 deletions.
2 changes: 1 addition & 1 deletion lib/cli/run-helpers.js
Expand Up @@ -85,7 +85,7 @@ exports.list = str =>
*/
exports.handleRequires = async (requires = []) => {
const pluginLoader = PluginLoader.create();
for (const mod of requires) {
for await (const mod of requires) {
let modpath = mod;
// this is relative to cwd
if (fs.existsSync(mod) || fs.existsSync(`${mod}.js`)) {
Expand Down
5 changes: 5 additions & 0 deletions lib/nodejs/parallel-buffered-runner.js
Expand Up @@ -235,6 +235,8 @@ class ParallelBufferedRunner extends Runner {

this.emit(EVENT_RUN_BEGIN);

await this.runGlobalSetup();

const results = await allSettled(
files.map(this._createFileRunner(pool, options))
);
Expand All @@ -257,6 +259,9 @@ class ParallelBufferedRunner extends Runner {
if (this._state === ABORTING) {
return;
}

await this.runGlobalTeardown();

this.emit(EVENT_RUN_END);
debug('run(): completing with failure count %d', this.failures);
callback(this.failures);
Expand Down
4 changes: 2 additions & 2 deletions lib/nodejs/worker.js
Expand Up @@ -12,7 +12,7 @@ const {
} = require('../errors');
const workerpool = require('workerpool');
const Mocha = require('../mocha');
const {handleRequires, validatePlugin} = require('../cli/run-helpers');
const {handleRequires, validateLegacyPlugin} = require('../cli/run-helpers');
const d = require('debug');
const debug = d.debug(`mocha:parallel:worker:${process.pid}`);
const isDebugEnabled = d.enabled(`mocha:parallel:worker:${process.pid}`);
Expand Down Expand Up @@ -42,7 +42,7 @@ if (workerpool.isMainThread) {
*/
let bootstrap = async argv => {
const plugins = await handleRequires(argv.require);
validatePlugin(argv, 'ui', Mocha.interfaces);
validateLegacyPlugin(argv, 'ui', Mocha.interfaces);

// globalSetup and globalTeardown do not run in workers
argv.rootHooks = plugins.rootHooks;
Expand Down
79 changes: 49 additions & 30 deletions lib/plugin.js
Expand Up @@ -8,31 +8,45 @@ const constants = (exports.constants = defineConstants({
PLUGIN_GLOBAL_SETUP: 'mochaGlobalSetup',
PLUGIN_GLOBAL_TEARDOWN: 'mochaGlobalTeardown'
}));
const {
PLUGIN_ROOT_HOOKS,
PLUGIN_GLOBAL_SETUP,
PLUGIN_GLOBAL_TEARDOWN
} = constants;
const PLUGIN_NAMES = new Set(Object.values(constants));

exports.PluginLoader = class PluginLoader {
constructor() {
this.pluginMap = new Map([
[constants.PLUGIN_ROOT_HOOKS, []],
[constants.PLUGIN_GLOBAL_SETUP, []],
[constants.PLUGIN_GLOBAL_TEARDOWN, []]
]);
constructor({
pluginNames = PLUGIN_NAMES,
pluginValidators = PluginValidators
} = {}) {
this.pluginNames = Array.from(pluginNames);
this.pluginMap = new Map(
this.pluginNames.map(pluginName => [pluginName, []])
);
this.pluginValidators = pluginValidators;
}

load(requiredModule) {
// we should explicitly NOT fail if other stuff is exported.
// we only care about the plugins we know about.
if (requiredModule && typeof requiredModule === 'object') {
PLUGIN_TYPES.forEach(pluginType => {
const plugin = requiredModule[pluginType];
return this.pluginNames.reduce((isFound, pluginName) => {
const plugin = requiredModule[pluginName];
if (plugin) {
PluginValidators[pluginType](plugin);
this.pluginMap.set(pluginType, [
...this.pluginMap.get(pluginType),
if (this.pluginValidators[pluginName]) {
this.pluginValidators[pluginName](plugin);
}
this.pluginMap.set(pluginName, [
...this.pluginMap.get(pluginName),
...castArray(plugin)
]);
return true;
}
});
return isFound;
}, false);
}
return false;
}

async finalize() {
Expand All @@ -55,18 +69,11 @@ exports.PluginLoader = class PluginLoader {
return finalizedPlugins;
}

static create() {
return new PluginLoader();
static create(...args) {
return new PluginLoader(...args);
}
};

const PLUGIN_TYPES = new Set(Object.values(constants));
const {
PLUGIN_ROOT_HOOKS,
PLUGIN_GLOBAL_SETUP,
PLUGIN_GLOBAL_TEARDOWN
} = constants;

const createFunctionArrayValidator = pluginType => value => {
let isValid = true;
if (Array.isArray(value)) {
Expand Down Expand Up @@ -102,22 +109,34 @@ const PluginValidators = {
* Loads root hooks as exported via `mochaHooks` from required files.
* These can be sync/async functions returning objects, or just objects.
* Flattens to a single object.
* @param {Array<MochaRootHookObject|MochaRootHookFunction>} rootHooks - Array of root hooks
* @param {MochaRootHookObject[]|MochaRootHookFunction[]} rootHooks - Array of root hooks
* @private
* @returns {MochaRootHookObject}
*/
const aggregateRootHooks = async rootHooks => {
const aggregateRootHooks = (exports.aggregateRootHooks = async (
rootHooks = []
) => {
const rootHookObjects = await Promise.all(
rootHooks.map(async hook => (typeof hook === 'function' ? hook() : hook))
);

console.dir(rootHookObjects);
return rootHookObjects.reduce(
(acc, hook) => ({
beforeAll: [...acc.beforeAll, ...(hook.beforeAll || [])],
beforeEach: [...acc.beforeEach, ...(hook.beforeEach || [])],
afterAll: [...acc.afterAll, ...(hook.afterAll || [])],
afterEach: [...acc.afterEach, ...(hook.afterEach || [])]
}),
(acc, hook) => {
hook = {
beforeAll: [],
beforeEach: [],
afterAll: [],
afterEach: [],
...hook
};
return {
beforeAll: [...acc.beforeAll, ...castArray(hook.beforeAll)],
beforeEach: [...acc.beforeEach, ...castArray(hook.beforeEach)],
afterAll: [...acc.afterAll, ...castArray(hook.afterAll)],
afterEach: [...acc.afterEach, ...castArray(hook.afterEach)]
};
},
{beforeAll: [], beforeEach: [], afterAll: [], afterEach: []}
);
};
});
73 changes: 42 additions & 31 deletions lib/runner.js
Expand Up @@ -982,18 +982,48 @@ Runner.prototype._uncaught = function(err) {
}
};

Runner.prototype.runGlobal = function runGlobal(globalFns, events) {
var context = (this._globalContext = this._globalContext || {});
var self = this;
return globalFns.reduce(async (promise, globalFn) => {
Runner.prototype.runGlobalSetup = async function runGlobalSetup() {
this.emit(constants.EVENT_PRE_GLOBAL_SETUP);
const {globalSetup} = this._opts;
if (globalSetup) {
debug('run(): global setup starting');
await this.runGlobalFixtures(globalSetup, {
begin: constants.EVENT_GLOBAL_SETUP_BEGIN,
end: constants.EVENT_GLOBAL_SETUP_END
});
}
debug('run(): global setup complete');
this.emit(constants.EVENT_POST_GLOBAL_SETUP);
};

Runner.prototype.runGlobalTeardown = async function runGlobalTeardown() {
this.emit(constants.EVENT_PRE_GLOBAL_TEARDOWN);
const {globalTeardown} = this._opts;
if (globalTeardown) {
debug('run(): global teardown starting');
await this.runGlobalFixtures(globalTeardown, {
begin: constants.EVENT_GLOBAL_TEARDOWN_BEGIN,
end: constants.EVENT_GLOBAL_TEARDOWN_END
});
}
debug('run(): global teardown complete');
this.emit(constants.EVENT_POST_GLOBAL_TEARDOWN);
};

Runner.prototype.runGlobalFixtures = function runGlobalFixtures(
fixtureFns,
events
) {
const context = (this._globalContext = this._globalContext || {});
return fixtureFns.reduce(async (promise, fixtureFn) => {
await promise;
self.emit(events.begin, {
value: globalFn,
this.emit(events.begin, {
value: fixtureFn,
context: context
});
const retVal = await globalFn.call(context);
self.emit(events.end, {
value: globalFn,
const retVal = await fixtureFn.call(context);
this.emit(events.end, {
value: fixtureFn,
context: context,
fulfilled: retVal
});
Expand All @@ -1010,7 +1040,7 @@ Runner.prototype.runGlobal = function runGlobal(globalFns, events) {
* @param {{files: string[], options: Options}} [opts] - For subclasses
* @return {Runner} Runner instance.
*/
Runner.prototype.run = function(fn, opts) {
Runner.prototype.run = function(fn, opts = {}) {
var rootSuite = this.suite;
var options = opts.options || {};

Expand All @@ -1029,17 +1059,7 @@ Runner.prototype.run = function(fn, opts) {
debug('run(): emitted %s', constants.EVENT_RUN_BEGIN);

this.runSuite(rootSuite, async () => {
if (options.globalTeardown) {
debug('run(): global teardown starting');
this.emit(constants.EVENT_PRE_GLOBAL_TEARDOWN);
await this.runGlobal(options.globalTeardown, {
begin: constants.EVENT_GLOBAL_TEARDOWN_BEGIN,
end: constants.EVENT_GLOBAL_TEARDOWN_END
});

debug('run(): global teardown complete');
this.emit(constants.EVENT_POST_GLOBAL_TEARDOWN);
}
await this.runGlobalTeardown();
end();
});
};
Expand All @@ -1057,17 +1077,8 @@ Runner.prototype.run = function(fn, opts) {
debug('run(): "delay" ended');
}

if (options.globalSetup) {
this.emit(constants.EVENT_PRE_GLOBAL_SETUP);
debug('run(): global setup starting');
await this.runGlobal(options.globalSetup, {
begin: constants.EVENT_GLOBAL_SETUP_BEGIN,
end: constants.EVENT_GLOBAL_SETUP_END
});
await this.runGlobalSetup();

this.emit(constants.EVENT_POST_GLOBAL_SETUP);
debug('run(): global setup complete');
}
return begin();
};

Expand Down
75 changes: 8 additions & 67 deletions test/node-unit/cli/run-helpers.spec.js
@@ -1,76 +1,17 @@
'use strict';

const {
validatePlugin,
validateLegacyPlugin,
list,
aggregateRootHooks
} = require('../../../lib/cli/run-helpers');

describe('helpers', function() {
describe('aggregateRootHooks()', function() {
describe('when passed nothing', function() {
it('should reject', async function() {
return expect(aggregateRootHooks(), 'to be rejected');
});
});

describe('when passed empty array of hooks', function() {
it('should return an empty MochaRootHooks object', async function() {
return expect(aggregateRootHooks([]), 'to be fulfilled with', {
beforeAll: [],
beforeEach: [],
afterAll: [],
afterEach: []
});
});
});

describe('when passed an array containing hook objects and sync functions and async functions', function() {
it('should flatten them into a single object', async function() {
function a() {}
function b() {}
function d() {}
function g() {}
async function f() {}
function c() {
return {
beforeAll: d,
beforeEach: g
};
}
async function e() {
return {
afterEach: f
};
}
return expect(
aggregateRootHooks([
{
beforeEach: a
},
{
afterAll: b
},
c,
e
]),
'to be fulfilled with',
{
beforeAll: [d],
beforeEach: [a, g],
afterAll: [b],
afterEach: [f]
}
);
});
});
});

describe('validatePlugin()', function() {
describe('validateLegacyPlugin()', function() {
describe('when used with "reporter" key', function() {
it('should disallow an array of names', function() {
expect(
() => validatePlugin({reporter: ['bar']}, 'reporter'),
() => validateLegacyPlugin({reporter: ['bar']}, 'reporter'),
'to throw',
{
code: 'ERR_MOCHA_INVALID_REPORTER',
Expand All @@ -81,7 +22,7 @@ describe('helpers', function() {

it('should fail to recognize an unknown reporter', function() {
expect(
() => validatePlugin({reporter: 'bar'}, 'reporter'),
() => validateLegacyPlugin({reporter: 'bar'}, 'reporter'),
'to throw',
{code: 'ERR_MOCHA_INVALID_REPORTER', message: /cannot find module/i}
);
Expand All @@ -91,7 +32,7 @@ describe('helpers', function() {
describe('when used with an "interfaces" key', function() {
it('should disallow an array of names', function() {
expect(
() => validatePlugin({interface: ['bar']}, 'interface'),
() => validateLegacyPlugin({interface: ['bar']}, 'interface'),
'to throw',
{
code: 'ERR_MOCHA_INVALID_INTERFACE',
Expand All @@ -102,7 +43,7 @@ describe('helpers', function() {

it('should fail to recognize an unknown interface', function() {
expect(
() => validatePlugin({interface: 'bar'}, 'interface'),
() => validateLegacyPlugin({interface: 'bar'}, 'interface'),
'to throw',
{code: 'ERR_MOCHA_INVALID_INTERFACE', message: /cannot find module/i}
);
Expand All @@ -112,7 +53,7 @@ describe('helpers', function() {
describe('when used with an unknown plugin type', function() {
it('should fail', function() {
expect(
() => validatePlugin({frog: 'bar'}, 'frog'),
() => validateLegacyPlugin({frog: 'bar'}, 'frog'),
'to throw',
/unknown plugin/i
);
Expand All @@ -123,7 +64,7 @@ describe('helpers', function() {
it('should fail and report the original error', function() {
expect(
() =>
validatePlugin(
validateLegacyPlugin(
{
reporter: require.resolve('./fixtures/bad-module.fixture.js')
},
Expand Down

0 comments on commit 8b38307

Please sign in to comment.