mocha 7.1.1 → 8.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,402 @@
1
+ /**
2
+ * Serialization/deserialization classes and functions for communication between a main Mocha process and worker processes.
3
+ * @module serializer
4
+ * @private
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const {type} = require('../utils');
10
+ const {createInvalidArgumentTypeError} = require('../errors');
11
+ // this is not named `mocha:parallel:serializer` because it's noisy and it's
12
+ // helpful to be able to write `DEBUG=mocha:parallel*` and get everything else.
13
+ const debug = require('debug')('mocha:serializer');
14
+
15
+ const SERIALIZABLE_RESULT_NAME = 'SerializableWorkerResult';
16
+ const SERIALIZABLE_TYPES = new Set(['object', 'array', 'function', 'error']);
17
+
18
+ /**
19
+ * The serializable result of a test file run from a worker.
20
+ * @private
21
+ */
22
+ class SerializableWorkerResult {
23
+ /**
24
+ * Creates instance props; of note, the `__type` prop.
25
+ *
26
+ * Note that the failure count is _redundant_ and could be derived from the
27
+ * list of events; but since we're already doing the work, might as well use
28
+ * it.
29
+ * @param {SerializableEvent[]} [events=[]] - Events to eventually serialize
30
+ * @param {number} [failureCount=0] - Failure count
31
+ */
32
+ constructor(events = [], failureCount = 0) {
33
+ /**
34
+ * The number of failures in this run
35
+ * @type {number}
36
+ */
37
+ this.failureCount = failureCount;
38
+ /**
39
+ * All relevant events emitted from the {@link Runner}.
40
+ * @type {SerializableEvent[]}
41
+ */
42
+ this.events = events;
43
+
44
+ /**
45
+ * Symbol-like value needed to distinguish when attempting to deserialize
46
+ * this object (once it's been received over IPC).
47
+ * @type {Readonly<"SerializableWorkerResult">}
48
+ */
49
+ Object.defineProperty(this, '__type', {
50
+ value: SERIALIZABLE_RESULT_NAME,
51
+ enumerable: true,
52
+ writable: false
53
+ });
54
+ }
55
+
56
+ /**
57
+ * Instantiates a new {@link SerializableWorkerResult}.
58
+ * @param {...any} args - Args to constructor
59
+ * @returns {SerilizableWorkerResult}
60
+ */
61
+ static create(...args) {
62
+ return new SerializableWorkerResult(...args);
63
+ }
64
+
65
+ /**
66
+ * Serializes each {@link SerializableEvent} in our `events` prop;
67
+ * makes this object read-only.
68
+ * @returns {Readonly<SerializableWorkerResult>}
69
+ */
70
+ serialize() {
71
+ this.events.forEach(event => {
72
+ event.serialize();
73
+ });
74
+ return Object.freeze(this);
75
+ }
76
+
77
+ /**
78
+ * Deserializes a {@link SerializedWorkerResult} into something reporters can
79
+ * use; calls {@link SerializableEvent.deserialize} on each item in its
80
+ * `events` prop.
81
+ * @param {SerializedWorkerResult} obj
82
+ * @returns {SerializedWorkerResult}
83
+ */
84
+ static deserialize(obj) {
85
+ obj.events.forEach(event => {
86
+ SerializableEvent.deserialize(event);
87
+ });
88
+ return obj;
89
+ }
90
+
91
+ /**
92
+ * Returns `true` if this is a {@link SerializedWorkerResult} or a
93
+ * {@link SerializableWorkerResult}.
94
+ * @param {*} value - A value to check
95
+ * @returns {boolean} If true, it's deserializable
96
+ */
97
+ static isSerializedWorkerResult(value) {
98
+ return (
99
+ value instanceof SerializableWorkerResult ||
100
+ (type(value) === 'object' && value.__type === SERIALIZABLE_RESULT_NAME)
101
+ );
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Represents an event, emitted by a {@link Runner}, which is to be transmitted
107
+ * over IPC.
108
+ *
109
+ * Due to the contents of the event data, it's not possible to send them
110
+ * verbatim. When received by the main process--and handled by reporters--these
111
+ * objects are expected to contain {@link Runnable} instances. This class
112
+ * provides facilities to perform the translation via serialization and
113
+ * deserialization.
114
+ * @private
115
+ */
116
+ class SerializableEvent {
117
+ /**
118
+ * Constructs a `SerializableEvent`, throwing if we receive unexpected data.
119
+ *
120
+ * Practically, events emitted from `Runner` have a minumum of zero (0)
121
+ * arguments-- (for example, {@link Runnable.constants.EVENT_RUN_BEGIN}) and a
122
+ * maximum of two (2) (for example,
123
+ * {@link Runnable.constants.EVENT_TEST_FAIL}, where the second argument is an
124
+ * `Error`). The first argument, if present, is a {@link Runnable}. This
125
+ * constructor's arguments adhere to this convention.
126
+ * @param {string} eventName - A non-empty event name.
127
+ * @param {any} [originalValue] - Some data. Corresponds to extra arguments
128
+ * passed to `EventEmitter#emit`.
129
+ * @param {Error} [originalError] - An error, if there's an error.
130
+ * @throws If `eventName` is empty, or `originalValue` is a non-object.
131
+ */
132
+ constructor(eventName, originalValue, originalError) {
133
+ if (!eventName) {
134
+ throw new Error('expected a non-empty `eventName` string argument');
135
+ }
136
+ /**
137
+ * The event name.
138
+ * @memberof SerializableEvent
139
+ */
140
+ this.eventName = eventName;
141
+ const originalValueType = type(originalValue);
142
+ if (originalValueType !== 'object' && originalValueType !== 'undefined') {
143
+ throw new Error(
144
+ `expected object, received [${originalValueType}]: ${originalValue}`
145
+ );
146
+ }
147
+ /**
148
+ * An error, if present.
149
+ * @memberof SerializableEvent
150
+ */
151
+ Object.defineProperty(this, 'originalError', {
152
+ value: originalError,
153
+ enumerable: false
154
+ });
155
+
156
+ /**
157
+ * The raw value.
158
+ *
159
+ * We don't want this value sent via IPC; making it non-enumerable will do that.
160
+ *
161
+ * @memberof SerializableEvent
162
+ */
163
+ Object.defineProperty(this, 'originalValue', {
164
+ value: originalValue,
165
+ enumerable: false
166
+ });
167
+ }
168
+
169
+ /**
170
+ * In case you hated using `new` (I do).
171
+ *
172
+ * @param {...any} args - Args for {@link SerializableEvent#constructor}.
173
+ * @returns {SerializableEvent} A new `SerializableEvent`
174
+ */
175
+ static create(...args) {
176
+ return new SerializableEvent(...args);
177
+ }
178
+
179
+ /**
180
+ * Used internally by {@link SerializableEvent#serialize}.
181
+ * @ignore
182
+ * @param {Array<object|string>} pairs - List of parent/key tuples to process; modified in-place. This JSDoc type is an approximation
183
+ * @param {object} parent - Some parent object
184
+ * @param {string} key - Key to inspect
185
+ * @param {WeakSet<Object>} seenObjects - For avoiding circular references
186
+ */
187
+ static _serialize(pairs, parent, key, seenObjects) {
188
+ let value = parent[key];
189
+ if (seenObjects.has(value)) {
190
+ parent[key] = Object.create(null);
191
+ return;
192
+ }
193
+ if (type(value) === 'error' || value instanceof Error) {
194
+ // we need to reference the stack prop b/c it's lazily-loaded.
195
+ // `__type` is necessary for deserialization to create an `Error` later.
196
+ // `message` is apparently not enumerable, so we must handle it specifically.
197
+ value = Object.assign(Object.create(null), value, {
198
+ stack: value.stack,
199
+ message: value.message,
200
+ __type: 'Error'
201
+ });
202
+ parent[key] = value;
203
+ // after this, the result of type(value) will be `object`, and we'll throw
204
+ // whatever other junk is in the original error into the new `value`.
205
+ }
206
+ switch (type(value)) {
207
+ case 'object':
208
+ if (type(value.serialize) === 'function') {
209
+ parent[key] = value.serialize();
210
+ } else {
211
+ // by adding props to the `pairs` array, we will process it further
212
+ pairs.push(
213
+ ...Object.keys(value)
214
+ .filter(key => SERIALIZABLE_TYPES.has(type(value[key])))
215
+ .map(key => [value, key])
216
+ );
217
+ }
218
+ break;
219
+ case 'function':
220
+ // we _may_ want to dig in to functions for some assertion libraries
221
+ // that might put a usable property on a function.
222
+ // for now, just zap it.
223
+ delete parent[key];
224
+ break;
225
+ case 'array':
226
+ pairs.push(
227
+ ...value
228
+ .filter(value => SERIALIZABLE_TYPES.has(type(value)))
229
+ .map((value, index) => [value, index])
230
+ );
231
+ break;
232
+ }
233
+ }
234
+
235
+ /**
236
+ * Modifies this object *in place* (for theoretical memory consumption &
237
+ * performance reasons); serializes `SerializableEvent#originalValue` (placing
238
+ * the result in `SerializableEvent#data`) and `SerializableEvent#error`.
239
+ * Freezes this object. The result is an object that can be transmitted over
240
+ * IPC.
241
+ * If this quickly becomes unmaintainable, we will want to move towards immutable
242
+ * objects post-haste.
243
+ */
244
+ serialize() {
245
+ // given a parent object and a key, inspect the value and decide whether
246
+ // to replace it, remove it, or add it to our `pairs` array to further process.
247
+ // this is recursion in loop form.
248
+ const originalValue = this.originalValue;
249
+ const result = Object.assign(Object.create(null), {
250
+ data:
251
+ type(originalValue) === 'object' &&
252
+ type(originalValue.serialize) === 'function'
253
+ ? originalValue.serialize()
254
+ : originalValue,
255
+ error: this.originalError
256
+ });
257
+
258
+ const pairs = Object.keys(result).map(key => [result, key]);
259
+ const seenObjects = new WeakSet();
260
+
261
+ let pair;
262
+ while ((pair = pairs.shift())) {
263
+ SerializableEvent._serialize(pairs, ...pair, seenObjects);
264
+ seenObjects.add(pair[0]);
265
+ }
266
+
267
+ this.data = result.data;
268
+ this.error = result.error;
269
+
270
+ return Object.freeze(this);
271
+ }
272
+
273
+ /**
274
+ * Used internally by {@link SerializableEvent.deserialize}; creates an `Error`
275
+ * from an `Error`-like (serialized) object
276
+ * @ignore
277
+ * @param {Object} value - An Error-like value
278
+ * @returns {Error} Real error
279
+ */
280
+ static _deserializeError(value) {
281
+ const error = new Error(value.message);
282
+ error.stack = value.stack;
283
+ Object.assign(error, value);
284
+ delete error.__type;
285
+ return error;
286
+ }
287
+
288
+ /**
289
+ * Used internally by {@link SerializableEvent.deserialize}; recursively
290
+ * deserializes an object in-place.
291
+ * @param {object|Array} parent - Some object or array
292
+ * @param {string|number} key - Some prop name or array index within `parent`
293
+ */
294
+ static _deserializeObject(parent, key) {
295
+ if (key === '__proto__') {
296
+ delete parent[key];
297
+ return;
298
+ }
299
+ const value = parent[key];
300
+ // keys beginning with `$$` are converted into functions returning the value
301
+ // and renamed, stripping the `$$` prefix.
302
+ // functions defined this way cannot be array members!
303
+ if (type(key) === 'string' && key.startsWith('$$')) {
304
+ const newKey = key.slice(2);
305
+ parent[newKey] = () => value;
306
+ delete parent[key];
307
+ key = newKey;
308
+ }
309
+ if (type(value) === 'array') {
310
+ value.forEach((_, idx) => {
311
+ SerializableEvent._deserializeObject(value, idx);
312
+ });
313
+ } else if (type(value) === 'object') {
314
+ if (value.__type === 'Error') {
315
+ parent[key] = SerializableEvent._deserializeError(value);
316
+ } else {
317
+ Object.keys(value).forEach(key => {
318
+ SerializableEvent._deserializeObject(value, key);
319
+ });
320
+ }
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Deserialize value returned from a worker into something more useful.
326
+ * Does not return the same object.
327
+ * @todo do this in a loop instead of with recursion (if necessary)
328
+ * @param {SerializedEvent} obj - Object returned from worker
329
+ * @returns {SerializedEvent} Deserialized result
330
+ */
331
+ static deserialize(obj) {
332
+ if (!obj) {
333
+ throw createInvalidArgumentTypeError('Expected value', obj);
334
+ }
335
+
336
+ obj = Object.assign(Object.create(null), obj);
337
+
338
+ if (obj.data) {
339
+ Object.keys(obj.data).forEach(key => {
340
+ SerializableEvent._deserializeObject(obj.data, key);
341
+ });
342
+ }
343
+
344
+ if (obj.error) {
345
+ obj.error = SerializableEvent._deserializeError(obj.error);
346
+ }
347
+
348
+ return obj;
349
+ }
350
+ }
351
+
352
+ /**
353
+ * "Serializes" a value for transmission over IPC as a message.
354
+ *
355
+ * If value is an object and has a `serialize()` method, call that method; otherwise return the object and hope for the best.
356
+ *
357
+ * @param {*} [value] - A value to serialize
358
+ */
359
+ exports.serialize = function serialize(value) {
360
+ const result =
361
+ type(value) === 'object' && type(value.serialize) === 'function'
362
+ ? value.serialize()
363
+ : value;
364
+ debug('serialized: %O', result);
365
+ return result;
366
+ };
367
+
368
+ /**
369
+ * "Deserializes" a "message" received over IPC.
370
+ *
371
+ * This could be expanded with other objects that need deserialization,
372
+ * but at present time we only care about {@link SerializableWorkerResult} objects.
373
+ *
374
+ * @param {*} [value] - A "message" to deserialize
375
+ */
376
+ exports.deserialize = function deserialize(value) {
377
+ const result = SerializableWorkerResult.isSerializedWorkerResult(value)
378
+ ? SerializableWorkerResult.deserialize(value)
379
+ : value;
380
+ debug('deserialized: %O', result);
381
+ return result;
382
+ };
383
+
384
+ exports.SerializableEvent = SerializableEvent;
385
+ exports.SerializableWorkerResult = SerializableWorkerResult;
386
+
387
+ /**
388
+ * The result of calling `SerializableEvent.serialize`, as received
389
+ * by the deserializer.
390
+ * @typedef {Object} SerializedEvent
391
+ * @property {object?} data - Optional serialized data
392
+ * @property {object?} error - Optional serialized `Error`
393
+ */
394
+
395
+ /**
396
+ * The result of calling `SerializableWorkerResult.serialize` as received
397
+ * by the deserializer.
398
+ * @typedef {Object} SerializedWorkerResult
399
+ * @property {number} failureCount - Number of failures
400
+ * @property {SerializedEvent[]} events - Serialized events
401
+ * @property {"SerializedWorkerResult"} __type - Symbol-like to denote the type of object this is
402
+ */
@@ -0,0 +1,154 @@
1
+ /**
2
+ * A worker process. Consumes {@link module:reporters/parallel-buffered} reporter.
3
+ * @module worker
4
+ * @private
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const {
10
+ createInvalidArgumentTypeError,
11
+ createInvalidArgumentValueError
12
+ } = require('../errors');
13
+ const workerpool = require('workerpool');
14
+ const Mocha = require('../mocha');
15
+ const {
16
+ handleRequires,
17
+ validatePlugin,
18
+ loadRootHooks
19
+ } = require('../cli/run-helpers');
20
+ const d = require('debug');
21
+ const debug = d.debug(`mocha:parallel:worker:${process.pid}`);
22
+ const isDebugEnabled = d.enabled(`mocha:parallel:worker:${process.pid}`);
23
+ const {serialize} = require('./serializer');
24
+ const {setInterval, clearInterval} = global;
25
+
26
+ const BUFFERED_REPORTER_PATH = require.resolve('./reporters/parallel-buffered');
27
+
28
+ let rootHooks;
29
+
30
+ if (workerpool.isMainThread) {
31
+ throw new Error(
32
+ 'This script is intended to be run as a worker (by the `workerpool` package).'
33
+ );
34
+ }
35
+
36
+ /**
37
+ * Initializes some stuff on the first call to {@link run}.
38
+ *
39
+ * Handles `--require` and `--ui`. Does _not_ handle `--reporter`,
40
+ * as only the `Buffered` reporter is used.
41
+ *
42
+ * **This function only runs once per worker**; it overwrites itself with a no-op
43
+ * before returning.
44
+ *
45
+ * @param {Options} argv - Command-line options
46
+ */
47
+ let bootstrap = async argv => {
48
+ const rawRootHooks = await handleRequires(argv.require);
49
+ rootHooks = await loadRootHooks(rawRootHooks);
50
+ validatePlugin(argv, 'ui', Mocha.interfaces);
51
+ bootstrap = () => {};
52
+ debug('bootstrap(): finished with args: %O', argv);
53
+ };
54
+
55
+ /**
56
+ * Runs a single test file in a worker thread.
57
+ * @param {string} filepath - Filepath of test file
58
+ * @param {string} [serializedOptions] - **Serialized** options. This string will be eval'd!
59
+ * @see https://npm.im/serialize-javascript
60
+ * @returns {Promise<{failures: number, events: BufferedEvent[]}>} - Test
61
+ * failure count and list of events.
62
+ */
63
+ async function run(filepath, serializedOptions = '{}') {
64
+ if (!filepath) {
65
+ throw createInvalidArgumentTypeError(
66
+ 'Expected a non-empty "filepath" argument',
67
+ 'file',
68
+ 'string'
69
+ );
70
+ }
71
+
72
+ debug('run(): running test file %s', filepath);
73
+
74
+ if (typeof serializedOptions !== 'string') {
75
+ throw createInvalidArgumentTypeError(
76
+ 'run() expects second parameter to be a string which was serialized by the `serialize-javascript` module',
77
+ 'serializedOptions',
78
+ 'string'
79
+ );
80
+ }
81
+ let argv;
82
+ try {
83
+ // eslint-disable-next-line no-eval
84
+ argv = eval('(' + serializedOptions + ')');
85
+ } catch (err) {
86
+ throw createInvalidArgumentValueError(
87
+ 'run() was unable to deserialize the options',
88
+ 'serializedOptions',
89
+ serializedOptions
90
+ );
91
+ }
92
+
93
+ const opts = Object.assign({ui: 'bdd'}, argv, {
94
+ // workers only use the `Buffered` reporter.
95
+ reporter: BUFFERED_REPORTER_PATH,
96
+ // if this was true, it would cause infinite recursion.
97
+ parallel: false,
98
+ // this doesn't work in parallel mode
99
+ forbidOnly: true,
100
+ // it's useful for a Mocha instance to know if it's running in a worker process.
101
+ isWorker: true
102
+ });
103
+
104
+ await bootstrap(opts);
105
+
106
+ opts.rootHooks = rootHooks;
107
+
108
+ const mocha = new Mocha(opts).addFile(filepath);
109
+
110
+ try {
111
+ await mocha.loadFilesAsync();
112
+ } catch (err) {
113
+ debug('run(): could not load file %s: %s', filepath, err);
114
+ throw err;
115
+ }
116
+
117
+ return new Promise((resolve, reject) => {
118
+ let debugInterval;
119
+ /* istanbul ignore next */
120
+ if (isDebugEnabled) {
121
+ debugInterval = setInterval(() => {
122
+ debug('run(): still running %s...', filepath);
123
+ }, 5000).unref();
124
+ }
125
+ mocha.run(result => {
126
+ // Runner adds these; if we don't remove them, we'll get a leak.
127
+ process.removeAllListeners('uncaughtException');
128
+
129
+ try {
130
+ const serialized = serialize(result);
131
+ debug(
132
+ 'run(): completed run with %d test failures; returning to main process',
133
+ typeof result.failures === 'number' ? result.failures : 0
134
+ );
135
+ resolve(serialized);
136
+ } catch (err) {
137
+ // TODO: figure out exactly what the sad path looks like here.
138
+ // rejection should only happen if an error is "unrecoverable"
139
+ debug('run(): serialization failed; rejecting: %O', err);
140
+ reject(err);
141
+ } finally {
142
+ clearInterval(debugInterval);
143
+ }
144
+ });
145
+ });
146
+ }
147
+
148
+ // this registers the `run` function.
149
+ workerpool.worker({run});
150
+
151
+ debug('started worker process');
152
+
153
+ // for testing
154
+ exports.run = run;
@@ -10,7 +10,7 @@ var tty = require('tty');
10
10
  var diff = require('diff');
11
11
  var milliseconds = require('ms');
12
12
  var utils = require('../utils');
13
- var supportsColor = process.browser ? null : require('supports-color');
13
+ var supportsColor = utils.isBrowser() ? null : require('supports-color');
14
14
  var constants = require('../runner').constants;
15
15
  var EVENT_TEST_PASS = constants.EVENT_TEST_PASS;
16
16
  var EVENT_TEST_FAIL = constants.EVENT_TEST_FAIL;
@@ -37,7 +37,7 @@ var consoleLog = console.log;
37
37
  */
38
38
 
39
39
  exports.useColors =
40
- !process.browser &&
40
+ !utils.isBrowser() &&
41
41
  (supportsColor.stdout || process.env.MOCHA_COLORS !== undefined);
42
42
 
43
43
  /**
@@ -62,6 +62,7 @@ function Doc(runner, options) {
62
62
 
63
63
  runner.on(EVENT_TEST_PASS, function(test) {
64
64
  Base.consoleLog('%s <dt>%s</dt>', indent(), utils.escape(test.title));
65
+ Base.consoleLog('%s <dt>%s</dt>', indent(), utils.escape(test.file));
65
66
  var code = utils.escape(utils.clean(test.body));
66
67
  Base.consoleLog('%s <dd><pre><code>%s</code></pre></dd>', indent(), code);
67
68
  });
@@ -72,6 +73,11 @@ function Doc(runner, options) {
72
73
  indent(),
73
74
  utils.escape(test.title)
74
75
  );
76
+ Base.consoleLog(
77
+ '%s <dt class="error">%s</dt>',
78
+ indent(),
79
+ utils.escape(test.file)
80
+ );
75
81
  var code = utils.escape(utils.clean(test.body));
76
82
  Base.consoleLog(
77
83
  '%s <dd class="error"><pre><code>%s</code></pre></dd>',
@@ -82,6 +82,7 @@ function clean(test) {
82
82
  return {
83
83
  title: test.title,
84
84
  fullTitle: test.fullTitle(),
85
+ file: test.file,
85
86
  duration: test.duration,
86
87
  currentRetry: test.currentRetry()
87
88
  };
@@ -87,6 +87,7 @@ function clean(test) {
87
87
  return {
88
88
  title: test.title,
89
89
  fullTitle: test.fullTitle(),
90
+ file: test.file,
90
91
  duration: test.duration,
91
92
  currentRetry: test.currentRetry(),
92
93
  err: cleanCycles(err)
@@ -56,11 +56,12 @@ function Landing(runner, options) {
56
56
 
57
57
  var self = this;
58
58
  var width = (Base.window.width * 0.75) | 0;
59
- var total = runner.total;
60
59
  var stream = process.stdout;
60
+
61
61
  var plane = color('plane', '✈');
62
62
  var crashed = -1;
63
63
  var n = 0;
64
+ var total = 0;
64
65
 
65
66
  function runway() {
66
67
  var buf = Array(width).join('-');
@@ -74,8 +75,7 @@ function Landing(runner, options) {
74
75
 
75
76
  runner.on(EVENT_TEST_END, function(test) {
76
77
  // check if the plane crashed
77
- var col = crashed === -1 ? ((width * ++n) / total) | 0 : crashed;
78
-
78
+ var col = crashed === -1 ? ((width * ++n) / ++total) | 0 : crashed;
79
79
  // show the crash
80
80
  if (test.state === STATE_FAILED) {
81
81
  plane = color('plane crash', '✈');
@@ -98,6 +98,14 @@ function Landing(runner, options) {
98
98
  process.stdout.write('\n');
99
99
  self.epilogue();
100
100
  });
101
+
102
+ // if cursor is hidden when we ctrl-C, then it will remain hidden unless...
103
+ process.once('SIGINT', function() {
104
+ cursor.show();
105
+ process.nextTick(function() {
106
+ process.kill(process.pid, 'SIGINT');
107
+ });
108
+ });
101
109
  }
102
110
 
103
111
  /**
@@ -50,9 +50,7 @@ function TAP(runner, options) {
50
50
  this._producer = createProducer(tapVersion);
51
51
 
52
52
  runner.once(EVENT_RUN_BEGIN, function() {
53
- var ntests = runner.grepTotal(runner.suite);
54
53
  self._producer.writeVersion();
55
- self._producer.writePlan(ntests);
56
54
  });
57
55
 
58
56
  runner.on(EVENT_TEST_END, function() {
@@ -204,6 +202,7 @@ TAPProducer.prototype.writeEpilogue = function(stats) {
204
202
  println('# pass ' + stats.passes);
205
203
  // :TBD: Why are we not showing pending results?
206
204
  println('# fail ' + stats.failures);
205
+ this.writePlan(stats.passes + stats.failures + stats.pending);
207
206
  };
208
207
 
209
208
  /**
@@ -9,7 +9,6 @@
9
9
  var Base = require('./base');
10
10
  var utils = require('../utils');
11
11
  var fs = require('fs');
12
- var mkdirp = require('mkdirp');
13
12
  var path = require('path');
14
13
  var errors = require('../errors');
15
14
  var createUnsupportedError = errors.createUnsupportedError;
@@ -62,7 +61,9 @@ function XUnit(runner, options) {
62
61
  throw createUnsupportedError('file output not supported in browser');
63
62
  }
64
63
 
65
- mkdirp.sync(path.dirname(options.reporterOptions.output));
64
+ fs.mkdirSync(path.dirname(options.reporterOptions.output), {
65
+ recursive: true
66
+ });
66
67
  self.fileStream = fs.createWriteStream(options.reporterOptions.output);
67
68
  }
68
69