diff --git a/browser-entry.js b/browser-entry.js index cdcb56b6fb..dee3789231 100644 --- a/browser-entry.js +++ b/browser-entry.js @@ -210,8 +210,8 @@ Mocha.process = process; * Expose mocha. */ -mocha.Mocha = Mocha; -mocha.mocha = mocha; +global.Mocha = Mocha; +global.mocha = mocha; // this allows test/acceptance/required-tokens.js to pass; thus, // you can now do `const describe = require('mocha').describe` in a diff --git a/lib/hook.js b/lib/hook.js index 6c12c02bb8..8091e5d5da 100644 --- a/lib/hook.js +++ b/lib/hook.js @@ -1,7 +1,7 @@ 'use strict'; var Runnable = require('./runnable'); -var inherits = require('./utils').inherits; +const {inherits, constants} = require('./utils'); /** * Expose `Hook`. @@ -63,16 +63,21 @@ Hook.prototype.serialize = function serialize() { return { $$isPending: this.isPending(), $$titlePath: this.titlePath(), - ctx: { - currentTest: { - title: this.ctx && this.ctx.currentTest && this.ctx.currentTest.title - } - }, + ctx: + this.ctx && this.ctx.currentTest + ? { + currentTest: { + title: this.ctx.currentTest.title, + [constants.MOCHA_ID_PROP_NAME]: this.ctx.currentTest.id + } + } + : {}, parent: { - root: this.parent.root, - title: this.parent.title + [constants.MOCHA_ID_PROP_NAME]: this.parent.id }, title: this.title, - type: this.type + type: this.type, + [constants.MOCHA_ID_PROP_NAME]: this.id, + __mocha_partial__: true }; }; diff --git a/lib/mocha.js b/lib/mocha.js index dc18b585a5..2c2eefd91f 100644 --- a/lib/mocha.js +++ b/lib/mocha.js @@ -128,8 +128,8 @@ exports.Test = require('./test'); * suite with * @param {boolean} [options.isWorker] - Should be `true` if `Mocha` process is running in a worker process. */ -function Mocha(options) { - options = utils.assign({}, mocharc, options || {}); +function Mocha(options = {}) { + options = {...mocharc, ...options}; this.files = []; this.options = options; // root suite diff --git a/lib/nodejs/parallel-buffered-runner.js b/lib/nodejs/parallel-buffered-runner.js index ee8635ab98..65cc415ac1 100644 --- a/lib/nodejs/parallel-buffered-runner.js +++ b/lib/nodejs/parallel-buffered-runner.js @@ -12,7 +12,8 @@ const {EVENT_RUN_BEGIN, EVENT_RUN_END} = Runner.constants; const debug = require('debug')('mocha:parallel:parallel-buffered-runner'); const {BufferedWorkerPool} = require('./buffered-worker-pool'); const {setInterval, clearInterval} = global; -const {createMap} = require('../utils'); +const {createMap, constants} = require('../utils'); +const {MOCHA_ID_PROP_NAME} = constants; /** * Outputs a debug statement with worker stats @@ -76,6 +77,9 @@ class ParallelBufferedRunner extends Runner { } }); + this._linkPartialObjects = false; + this._linkedObjectMap = new Map(); + this.once(Runner.constants.EVENT_RUN_END, () => { this._state = COMPLETE; }); @@ -88,10 +92,60 @@ class ParallelBufferedRunner extends Runner { * @returns {FileRunner} Mapping function */ _createFileRunner(pool, options) { + /** + * Emits event and sets `BAILING` state, if necessary. + * @param {Object} event - Event having `eventName`, maybe `data` and maybe `error` + * @param {number} failureCount - Failure count + */ + const emitEvent = (event, failureCount) => { + this.emit(event.eventName, event.data, event.error); + if ( + this._state !== BAILING && + event.data && + event.data._bail && + (failureCount || event.error) + ) { + debug('run(): nonzero failure count & found bail flag'); + // we need to let the events complete for this file, as the worker + // should run any cleanup hooks + this._state = BAILING; + } + }; + + /** + * Given an event, recursively find any objects in its data that have ID's, and create object references to already-seen objects. + * @param {Object} event - Event having `eventName`, maybe `data` and maybe `error` + */ + const linkEvent = event => { + const stack = [{parent: event, prop: 'data'}]; + while (stack.length) { + const {parent, prop} = stack.pop(); + const obj = parent[prop]; + let newObj; + if (obj && typeof obj === 'object' && obj[MOCHA_ID_PROP_NAME]) { + const id = obj[MOCHA_ID_PROP_NAME]; + newObj = this._linkedObjectMap.has(id) + ? Object.assign(this._linkedObjectMap.get(id), obj) + : obj; + this._linkedObjectMap.set(id, newObj); + parent[prop] = newObj; + } else { + newObj = obj; + } + Object.keys(newObj).forEach(key => { + const value = obj[key]; + if (value && typeof value === 'object' && value[MOCHA_ID_PROP_NAME]) { + stack.push({obj: value, parent: newObj, prop: key}); + } + }); + } + }; + return async file => { debug('run(): enqueueing test file %s', file); try { const {failureCount, events} = await pool.run(file, options); + if (this._state === BAILED) { // short-circuit after a graceful bail. if this happens, // some other worker has bailed. @@ -107,20 +161,18 @@ class ParallelBufferedRunner extends Runner { ); this.failures += failureCount; // can this ever be non-numeric? let event = events.shift(); - while (event) { - this.emit(event.eventName, event.data, event.error); - if ( - this._state !== BAILING && - event.data && - event.data._bail && - (failureCount || event.error) - ) { - debug('run(): nonzero failure count & found bail flag'); - // we need to let the events complete for this file, as the worker - // should run any cleanup hooks - this._state = BAILING; + + if (this._linkPartialObjects) { + while (event) { + linkEvent(event); + emitEvent(event, failureCount); + event = events.shift(); + } + } else { + while (event) { + emitEvent(event, failureCount); + event = events.shift(); } - event = events.shift(); } if (this._state === BAILING) { debug('run(): terminating pool due to "bail" flag'); @@ -275,6 +327,32 @@ class ParallelBufferedRunner extends Runner { })(); return this; } + + /** + * Toggle partial object linking behavior; used for building object references from + * unique ID's. + * @param {boolean} [value] - If `true`, enable partial object linking, otherwise disable + * @returns {Runner} + * @chainable + * @public + * @example + * // this reporter needs proper object references when run in parallel mode + * class MyReporter() { + * constructor(runner) { + * this.runner.linkPartialObjects(true) + * .on(EVENT_SUITE_BEGIN, suite => { + // this Suite may be the same object... + * }) + * .on(EVENT_TEST_BEGIN, test => { + * // ...as the `test.parent` property + * }); + * } + * } + */ + linkPartialObjects(value) { + this._linkPartialObjects = Boolean(value); + return super.linkPartialObjects(value); + } } module.exports = ParallelBufferedRunner; diff --git a/lib/runnable.js b/lib/runnable.js index 023481dd69..e65b4c4638 100644 --- a/lib/runnable.js +++ b/lib/runnable.js @@ -38,6 +38,12 @@ function Runnable(title, fn) { this._timeout = 2000; this._slow = 75; this._retries = -1; + utils.assignNewMochaID(this); + Object.defineProperty(this, 'id', { + get() { + return utils.getMochaID(this); + } + }); this.reset(); } diff --git a/lib/runner.js b/lib/runner.js index cd91173e10..c8b5d24aa3 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -1043,6 +1043,33 @@ Runner.prototype.run = function(fn, opts) { return this; }; +/** + * Toggle partial object linking behavior; used for building object references from + * unique ID's. Does nothing in serial mode, because the object references already exist. + * Subclasses can implement this (e.g., `ParallelBufferedRunner`) + * @abstract + * @param {boolean} [value] - If `true`, enable partial object linking, otherwise disable + * @returns {Runner} + * @chainable + * @public + * @example + * // this reporter needs proper object references when run in parallel mode + * class MyReporter() { + * constructor(runner) { + * this.runner.linkPartialObjects(true) + * .on(EVENT_SUITE_BEGIN, suite => { + // this Suite may be the same object... + * }) + * .on(EVENT_TEST_BEGIN, test => { + * // ...as the `test.parent` property + * }); + * } + * } + */ +Runner.prototype.linkPartialObjects = function(value) { + return this; +}; + /** * Cleanly abort execution. * diff --git a/lib/suite.js b/lib/suite.js index e9d45d94c4..320582f611 100644 --- a/lib/suite.js +++ b/lib/suite.js @@ -13,6 +13,8 @@ var milliseconds = require('ms'); var errors = require('./errors'); var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError; +const {MOCHA_ID_PROP_NAME} = utils.constants; + /** * Expose `Suite`. */ @@ -74,6 +76,14 @@ function Suite(title, parentContext, isRoot) { this._bail = false; this._onlyTests = []; this._onlySuites = []; + utils.assignNewMochaID(this); + + Object.defineProperty(this, 'id', { + get() { + return utils.getMochaID(this); + } + }); + this.reset(); this.on('newListener', function(event) { @@ -579,7 +589,9 @@ Suite.prototype.serialize = function serialize() { $$fullTitle: this.fullTitle(), $$isPending: this.isPending(), root: this.root, - title: this.title + title: this.title, + id: this.id, + parent: this.parent ? {[MOCHA_ID_PROP_NAME]: this.parent.id} : null }; }; diff --git a/lib/test.js b/lib/test.js index 3fb3e57a4e..f2be51b4f4 100644 --- a/lib/test.js +++ b/lib/test.js @@ -5,6 +5,8 @@ var errors = require('./errors'); var createInvalidArgumentTypeError = errors.createInvalidArgumentTypeError; var isString = utils.isString; +const {MOCHA_ID_PROP_NAME} = utils.constants; + module.exports = Test; /** @@ -98,12 +100,14 @@ Test.prototype.serialize = function serialize() { duration: this.duration, err: this.err, parent: { - $$fullTitle: this.parent.fullTitle() + $$fullTitle: this.parent.fullTitle(), + [MOCHA_ID_PROP_NAME]: this.parent.id }, speed: this.speed, state: this.state, title: this.title, type: this.type, - file: this.file + file: this.file, + [MOCHA_ID_PROP_NAME]: this.id }; }; diff --git a/lib/utils.js b/lib/utils.js index 00d6fcc938..0fd7ca2c4d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -9,11 +9,12 @@ * Module dependencies. */ +const {nanoid} = require('nanoid/non-secure'); var path = require('path'); var util = require('util'); var he = require('he'); -var assign = (exports.assign = require('object.assign').getPolyfill()); +const MOCHA_ID_PROP_NAME = '__mocha_id__'; /** * Inherit the prototype methods from one constructor into another. @@ -575,7 +576,7 @@ exports.noop = function() {}; * @returns {Object} An object with no prototype, having `...obj` properties */ exports.createMap = function(obj) { - return assign.apply( + return Object.assign.apply( null, [Object.create(null)].concat(Array.prototype.slice.call(arguments)) ); @@ -645,3 +646,31 @@ exports.cwd = function cwd() { exports.isBrowser = function isBrowser() { return Boolean(process.browser); }; + +exports.constants = exports.defineConstants({ + MOCHA_ID_PROP_NAME +}); + +/** + * Creates a new unique identifier + * @returns {string} Unique identifier + */ +exports.uniqueID = () => nanoid(); + +exports.assignNewMochaID = obj => { + const id = exports.uniqueID(); + Object.defineProperty(obj, MOCHA_ID_PROP_NAME, { + get() { + return id; + } + }); + return obj; +}; + +/** + * Retrieves a Mocha ID from an object, if present. + * @param {*} [obj] - Object + * @returns {string|void} + */ +exports.getMochaID = obj => + obj && typeof obj === 'object' ? obj[MOCHA_ID_PROP_NAME] : undefined; diff --git a/package-lock.json b/package-lock.json index 328c2a81d2..971a4e1d35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3488,13 +3488,53 @@ } }, "@rollup/plugin-babel": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.0.4.tgz", - "integrity": "sha512-MBtNoi5gqBEbqy1gE9jZBfPsi10kbuK2CEu9bx53nk1Z3ATRvBOoZ/GsbhXOeVbS76xXi/DeYM+vYX6EGIDv9A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.2.0.tgz", + "integrity": "sha512-CPABsajaKjINgBQ3it+yMnfVO3ibsrMBxRzbUOUw2cL1hsZJ7aogU8mgglQm3S2hHJgjnAmxPz0Rq7DVdmHsTw==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.7.4", - "@rollup/pluginutils": "^3.0.8" + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "dependencies": { + "@babel/helper-module-imports": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.10.4.tgz", + "integrity": "sha512-nEQJHqYavI217oD9+s5MUBzk6x1IlvoS9WTPfgG43CbMEeStE0v+r+TucWdx8KFGowPGvyOkDT9+7DHedIDnVw==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/types": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.0.tgz", + "integrity": "sha512-O53yME4ZZI0jO1EVGtF1ePGl0LHirG4P1ibcD80XyzZcKhcMFeCXmh4Xb1ifGBIV233Qg12x4rBfQgA+tmOukA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + } } }, "@rollup/plugin-commonjs": { @@ -7367,6 +7407,12 @@ "xdg-basedir": "^3.0.0" }, "dependencies": { + "crypto-random-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", + "dev": true + }, "dot-prop": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", @@ -7381,6 +7427,15 @@ "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true + }, + "unique-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", + "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", + "dev": true, + "requires": { + "crypto-random-string": "^1.0.0" + } } } }, @@ -7675,12 +7730,6 @@ "randomfill": "^1.0.3" } }, - "crypto-random-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=", - "dev": true - }, "css-color-names": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", @@ -15237,10 +15286,9 @@ "dev": true }, "nanoid": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz", - "integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==", - "dev": true + "version": "3.1.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz", + "integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A==" }, "nanomatch": { "version": "1.2.13", @@ -18981,6 +19029,12 @@ "p-locate": "^4.1.0" } }, + "nanoid": { + "version": "2.1.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-2.1.11.tgz", + "integrity": "sha512-s/snB+WGm6uwi0WjsZdaVcuf3KJXlfGl2LcxgwkEwJF0D/BWzVWAZW/XY4bFaiR7s0Jk3FPvlnepg1H1b1UwlA==", + "dev": true + }, "p-locate": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", @@ -21656,15 +21710,6 @@ "integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=", "dev": true }, - "unique-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-1.0.0.tgz", - "integrity": "sha1-nhBXzKhRq7kzmPizOuGHuZyuwRo=", - "dev": true, - "requires": { - "crypto-random-string": "^1.0.0" - } - }, "unist-util-is": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", @@ -21944,9 +21989,9 @@ "dev": true }, "uuid": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz", - "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ==", "dev": true }, "v8-compile-cache": { diff --git a/package.json b/package.json index 6f88bd7033..08901adc92 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "log-symbols": "3.0.0", "minimatch": "3.0.4", "ms": "2.1.2", - "object.assign": "4.1.0", + "nanoid": "3.1.12", "promise.allsettled": "1.0.2", "serialize-javascript": "4.0.0", "strip-json-comments": "3.0.1", diff --git a/rollup.config.js b/rollup.config.js index e90bb5203e..4b4f3494e4 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -17,8 +17,7 @@ const config = { file: './mocha.js', format: 'umd', sourcemap: true, - name: 'mocha', - footer: 'window.Mocha = window.mocha.Mocha;' + name: 'mocha' }, plugins: [ json(), @@ -38,7 +37,6 @@ const config = { '@babel/preset-env', { modules: false, - spec: true, useBuiltIns: 'usage', forceAllTransforms: true, corejs: { diff --git a/test/node-unit/buffered-runner.spec.js b/test/node-unit/parallel-buffered-runner.spec.js similarity index 83% rename from test/node-unit/buffered-runner.spec.js rename to test/node-unit/parallel-buffered-runner.spec.js index ef308f0905..225388dea4 100644 --- a/test/node-unit/buffered-runner.spec.js +++ b/test/node-unit/parallel-buffered-runner.spec.js @@ -8,19 +8,18 @@ const { EVENT_SUITE_BEGIN } = require('../../lib/runner').constants; const rewiremock = require('rewiremock/node'); -const BUFFERED_RUNNER_PATH = require.resolve( - '../../lib/nodejs/parallel-buffered-runner.js' -); const Suite = require('../../lib/suite'); const Runner = require('../../lib/runner'); const sinon = require('sinon'); +const {constants} = require('../../lib/utils'); +const {MOCHA_ID_PROP_NAME} = constants; -describe('buffered-runner', function() { - describe('BufferedRunner', function() { +describe('parallel-buffered-runner', function() { + describe('ParallelBufferedRunner', function() { let run; let BufferedWorkerPool; let terminate; - let BufferedRunner; + let ParallelBufferedRunner; let suite; let warn; let cpuCount; @@ -40,20 +39,31 @@ describe('buffered-runner', function() { stats: sinon.stub().returns({}) }) }; - BufferedRunner = rewiremock.proxy(BUFFERED_RUNNER_PATH, r => ({ - '../../lib/nodejs/buffered-worker-pool': { - BufferedWorkerPool - }, - os: { - cpus: sinon.stub().callsFake(() => new Array(cpuCount)) - }, - '../../lib/utils': r.with({warn}).callThrough() - })); + /** + * @type {ParallelBufferedRunner} + */ + ParallelBufferedRunner = rewiremock.proxy( + () => require('../../lib/nodejs/parallel-buffered-runner'), + r => ({ + '../../lib/nodejs/buffered-worker-pool': { + BufferedWorkerPool + }, + os: { + cpus: sinon.stub().callsFake(() => new Array(cpuCount)) + }, + '../../lib/utils': r.with({warn}).callThrough() + }) + ); }); describe('constructor', function() { it('should start in "IDLE" state', function() { - expect(new BufferedRunner(suite), 'to have property', '_state', 'IDLE'); + expect( + new ParallelBufferedRunner(suite), + 'to have property', + '_state', + 'IDLE' + ); }); }); @@ -61,7 +71,7 @@ describe('buffered-runner', function() { let runner; beforeEach(function() { - runner = new BufferedRunner(suite); + runner = new ParallelBufferedRunner(suite); }); describe('_state', function() { @@ -81,7 +91,7 @@ describe('buffered-runner', function() { let runner; beforeEach(function() { - runner = new BufferedRunner(suite); + runner = new ParallelBufferedRunner(suite); }); describe('EVENT_RUN_END', function() { @@ -94,11 +104,11 @@ describe('buffered-runner', function() { }); describe('instance method', function() { - describe('run', function() { + describe('run()', function() { let runner; beforeEach(function() { - runner = new BufferedRunner(suite); + runner = new ParallelBufferedRunner(suite); }); // the purpose of this is to ensure that--despite using `Promise`s @@ -119,6 +129,64 @@ describe('buffered-runner', function() { ); }); + describe('when instructed to link objects', function() { + beforeEach(function() { + runner.linkPartialObjects(true); + }); + + it('should create object references', function() { + const options = {}; + const someSuite = { + title: 'some suite', + [MOCHA_ID_PROP_NAME]: 'bar' + }; + + run.withArgs('some-file.js', options).resolves({ + failureCount: 0, + events: [ + { + eventName: EVENT_SUITE_END, + data: someSuite + }, + { + eventName: EVENT_TEST_PASS, + data: { + title: 'some test', + [MOCHA_ID_PROP_NAME]: 'foo', + parent: { + // this stub object points to someSuite with id 'bar' + [MOCHA_ID_PROP_NAME]: 'bar' + } + } + }, + { + eventName: EVENT_SUITE_END, + // ensure we are not passing the _same_ someSuite, + // because we won't get the same one from the subprocess + data: {...someSuite} + } + ] + }); + + return expect( + () => + new Promise(resolve => { + runner.run(resolve, {files: ['some-file.js'], options: {}}); + }), + 'to emit from', + runner, + EVENT_TEST_PASS, + { + title: 'some test', + [MOCHA_ID_PROP_NAME]: 'foo', + parent: expect + .it('to be', someSuite) + .and('to have property', 'title', 'some suite') + } + ); + }); + }); + describe('when a worker fails', function() { it('should recover', function(done) { const options = {}; @@ -546,6 +614,20 @@ describe('buffered-runner', function() { }); }); }); + + describe('linkPartialObjects()', function() { + let runner; + + beforeEach(function() { + runner = new ParallelBufferedRunner(suite); + }); + + it('should return the runner', function() { + expect(runner.linkPartialObjects(), 'to be', runner); + }); + + // avoid testing implementation details; don't check _linkPartialObjects + }); }); }); }); diff --git a/test/unit/mocha.spec.js b/test/unit/mocha.spec.js index 75be0341c9..98d6082948 100644 --- a/test/unit/mocha.spec.js +++ b/test/unit/mocha.spec.js @@ -51,7 +51,8 @@ describe('Mocha', function() { sinon.stub(Mocha.reporters, 'base').returns({}); sinon.stub(Mocha.reporters, 'spec').returns({}); - runner = utils.assign(sinon.createStubInstance(EventEmitter), { + runner = { + ...sinon.createStubInstance(EventEmitter), run: sinon .stub() .callsArgAsync(0) @@ -59,18 +60,19 @@ describe('Mocha', function() { globals: sinon.stub(), grep: sinon.stub(), dispose: sinon.stub() - }); + }; Runner = sinon.stub(Mocha, 'Runner').returns(runner); // the Runner constructor is the main export, and constants is a static prop. // we don't need the constants themselves, but the object cannot be undefined Runner.constants = {}; - suite = utils.assign(sinon.createStubInstance(EventEmitter), { + suite = { + ...sinon.createStubInstance(EventEmitter), slow: sinon.stub(), timeout: sinon.stub(), bail: sinon.stub(), dispose: sinon.stub(), reset: sinon.stub() - }); + }; Suite = sinon.stub(Mocha, 'Suite').returns(suite); Suite.constants = {}; diff --git a/test/unit/runner.spec.js b/test/unit/runner.spec.js index 730c40003d..a8d0f975ff 100644 --- a/test/unit/runner.spec.js +++ b/test/unit/runner.spec.js @@ -1158,4 +1158,10 @@ describe('Runner', function() { }); }); }); + + describe('linkPartialObjects', function() { + it('should return the Runner', function() { + expect(runner.linkPartialObjects(), 'to be', runner); + }); + }); }); diff --git a/test/unit/utils.spec.js b/test/unit/utils.spec.js index 9fa6cc74e4..efd82f9ce4 100644 --- a/test/unit/utils.spec.js +++ b/test/unit/utils.spec.js @@ -742,4 +742,10 @@ describe('lib/utils', function() { expect(utils.slug('poppies & fritz'), 'to be', 'poppies-fritz'); }); }); + + describe('uniqueID()', function() { + it('should return a non-empty string', function() { + expect(utils.uniqueID(), 'to be a string').and('not to be empty'); + }); + }); });