ava 3.8.1 → 3.10.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.
@@ -1,463 +0,0 @@
1
- 'use strict';
2
- const os = require('os');
3
- const path = require('path');
4
- const stream = require('stream');
5
-
6
- const figures = require('figures');
7
- const indentString = require('indent-string');
8
- const plur = require('plur');
9
- const prettyMs = require('pretty-ms');
10
- const trimOffNewlines = require('trim-off-newlines');
11
- const beautifyStack = require('./beautify-stack');
12
-
13
- const chalk = require('../chalk').get();
14
- const codeExcerpt = require('../code-excerpt');
15
- const colors = require('./colors');
16
- const formatSerializedError = require('./format-serialized-error');
17
- const improperUsageMessages = require('./improper-usage-messages');
18
- const prefixTitle = require('./prefix-title');
19
- const whileCorked = require('./while-corked');
20
-
21
- const nodeInternals = require('stack-utils').nodeInternals();
22
-
23
- class LineWriter extends stream.Writable {
24
- constructor(dest) {
25
- super();
26
-
27
- this.dest = dest;
28
- this.columns = dest.columns || 80;
29
- this.lastLineIsEmpty = false;
30
- }
31
-
32
- _write(chunk, encoding, callback) {
33
- this.dest.write(chunk);
34
- callback();
35
- }
36
-
37
- writeLine(string) {
38
- if (string) {
39
- this.write(indentString(string, 2) + os.EOL);
40
- this.lastLineIsEmpty = false;
41
- } else {
42
- this.write(os.EOL);
43
- this.lastLineIsEmpty = true;
44
- }
45
- }
46
-
47
- ensureEmptyLine() {
48
- if (!this.lastLineIsEmpty) {
49
- this.writeLine();
50
- }
51
- }
52
- }
53
-
54
- class VerboseReporter {
55
- constructor(options) {
56
- this.durationThreshold = options.durationThreshold || 100;
57
- this.reportStream = options.reportStream;
58
- this.stdStream = options.stdStream;
59
- this.watching = options.watching;
60
-
61
- this.lineWriter = new LineWriter(this.reportStream);
62
- this.consumeStateChange = whileCorked(this.reportStream, this.consumeStateChange);
63
- this.endRun = whileCorked(this.reportStream, this.endRun);
64
- this.relativeFile = file => path.relative(options.projectDir, file);
65
-
66
- this.reset();
67
- }
68
-
69
- reset() {
70
- if (this.removePreviousListener) {
71
- this.removePreviousListener();
72
- }
73
-
74
- this.failFastEnabled = false;
75
- this.failures = [];
76
- this.filesWithMissingAvaImports = new Set();
77
- this.knownFailures = [];
78
- this.runningTestFiles = new Map();
79
- this.lastLineIsEmpty = false;
80
- this.matching = false;
81
- this.prefixTitle = (testFile, title) => title;
82
- this.previousFailures = 0;
83
- this.removePreviousListener = null;
84
- this.stats = null;
85
- }
86
-
87
- startRun(plan) {
88
- if (plan.bailWithoutReporting) {
89
- return;
90
- }
91
-
92
- this.reset();
93
-
94
- this.failFastEnabled = plan.failFastEnabled;
95
- this.matching = plan.matching;
96
- this.previousFailures = plan.previousFailures;
97
- this.emptyParallelRun = plan.status.emptyParallelRun;
98
-
99
- if (this.watching || plan.files.length > 1) {
100
- this.prefixTitle = (testFile, title) => prefixTitle(plan.filePathPrefix, testFile, title);
101
- }
102
-
103
- this.removePreviousListener = plan.status.on('stateChange', evt => this.consumeStateChange(evt));
104
-
105
- if (this.watching && plan.runVector > 1) {
106
- this.lineWriter.write(chalk.gray.dim('\u2500'.repeat(this.reportStream.columns || 80)) + os.EOL);
107
- }
108
-
109
- this.lineWriter.writeLine();
110
- }
111
-
112
- consumeStateChange(evt) { // eslint-disable-line complexity
113
- const fileStats = this.stats && evt.testFile ? this.stats.byFile.get(evt.testFile) : null;
114
-
115
- switch (evt.type) {
116
- case 'hook-failed':
117
- this.failures.push(evt);
118
- this.writeTestSummary(evt);
119
- break;
120
- case 'internal-error':
121
- if (evt.testFile) {
122
- this.lineWriter.writeLine(colors.error(`${figures.cross} Internal error when running ${this.relativeFile(evt.testFile)}`));
123
- } else {
124
- this.lineWriter.writeLine(colors.error(`${figures.cross} Internal error`));
125
- }
126
-
127
- this.lineWriter.writeLine(colors.stack(evt.err.summary));
128
- this.lineWriter.writeLine(colors.errorStack(evt.err.stack));
129
- this.lineWriter.writeLine();
130
- this.lineWriter.writeLine();
131
- break;
132
- case 'line-number-selection-error':
133
- this.lineWriter.writeLine(colors.information(`${figures.warning} Could not parse ${this.relativeFile(evt.testFile)} for line number selection`));
134
- break;
135
- case 'missing-ava-import':
136
- this.filesWithMissingAvaImports.add(evt.testFile);
137
- this.lineWriter.writeLine(colors.error(`${figures.cross} No tests found in ${this.relativeFile(evt.testFile)}, make sure to import "ava" at the top of your test file`));
138
- break;
139
- case 'hook-finished':
140
- if (evt.logs.length > 0) {
141
- this.lineWriter.writeLine(` ${this.prefixTitle(evt.testFile, evt.title)}`);
142
- this.writeLogs(evt);
143
- }
144
-
145
- break;
146
- case 'selected-test':
147
- if (evt.skip) {
148
- this.lineWriter.writeLine(colors.skip(`- ${this.prefixTitle(evt.testFile, evt.title)}`));
149
- } else if (evt.todo) {
150
- this.lineWriter.writeLine(colors.todo(`- ${this.prefixTitle(evt.testFile, evt.title)}`));
151
- }
152
-
153
- break;
154
- case 'stats':
155
- this.stats = evt.stats;
156
- break;
157
- case 'test-failed':
158
- this.failures.push(evt);
159
- this.writeTestSummary(evt);
160
- break;
161
- case 'test-passed':
162
- if (evt.knownFailing) {
163
- this.knownFailures.push(evt);
164
- }
165
-
166
- this.writeTestSummary(evt);
167
- break;
168
- case 'timeout':
169
- this.lineWriter.writeLine(colors.error(`\n${figures.cross} Timed out while running tests`));
170
- this.lineWriter.writeLine('');
171
- this.writePendingTests(evt);
172
- break;
173
- case 'interrupt':
174
- this.lineWriter.writeLine(colors.error(`\n${figures.cross} Exiting due to SIGINT`));
175
- this.lineWriter.writeLine('');
176
- this.writePendingTests(evt);
177
- break;
178
- case 'uncaught-exception':
179
- this.lineWriter.ensureEmptyLine();
180
- this.lineWriter.writeLine(colors.title(`Uncaught exception in ${this.relativeFile(evt.testFile)}`));
181
- this.lineWriter.writeLine();
182
- this.writeErr(evt);
183
- this.lineWriter.writeLine();
184
- break;
185
- case 'unhandled-rejection':
186
- this.lineWriter.ensureEmptyLine();
187
- this.lineWriter.writeLine(colors.title(`Unhandled rejection in ${this.relativeFile(evt.testFile)}`));
188
- this.lineWriter.writeLine();
189
- this.writeErr(evt);
190
- this.lineWriter.writeLine();
191
- break;
192
- case 'worker-failed':
193
- if (!this.filesWithMissingAvaImports.has(evt.testFile)) {
194
- if (evt.nonZeroExitCode) {
195
- this.lineWriter.writeLine(colors.error(`${figures.cross} ${this.relativeFile(evt.testFile)} exited with a non-zero exit code: ${evt.nonZeroExitCode}`));
196
- } else {
197
- this.lineWriter.writeLine(colors.error(`${figures.cross} ${this.relativeFile(evt.testFile)} exited due to ${evt.signal}`));
198
- }
199
- }
200
-
201
- break;
202
- case 'worker-finished':
203
- if (!evt.forcedExit && !this.filesWithMissingAvaImports.has(evt.testFile)) {
204
- if (fileStats.declaredTests === 0) {
205
- this.lineWriter.writeLine(colors.error(`${figures.cross} No tests found in ${this.relativeFile(evt.testFile)}`));
206
- } else if (fileStats.selectingLines && fileStats.selectedTests === 0) {
207
- this.lineWriter.writeLine(colors.error(`${figures.cross} Line numbers for ${this.relativeFile(evt.testFile)} did not match any tests`));
208
- } else if (!this.failFastEnabled && fileStats.remainingTests > 0) {
209
- this.lineWriter.writeLine(colors.error(`${figures.cross} ${fileStats.remainingTests} ${plur('test', fileStats.remainingTests)} remaining in ${this.relativeFile(evt.testFile)}`));
210
- }
211
- }
212
-
213
- break;
214
- case 'worker-stderr':
215
- case 'worker-stdout':
216
- this.stdStream.write(evt.chunk);
217
- // If the chunk does not end with a linebreak, *forcibly* write one to
218
- // ensure it remains visible in the TTY.
219
- // Tests cannot assume their standard output is not interrupted. Indeed
220
- // we multiplex stdout and stderr into a single stream. However as
221
- // long as stdStream is different from reportStream users can read
222
- // their original output by redirecting the streams.
223
- if (evt.chunk[evt.chunk.length - 1] !== 0x0A) {
224
- this.reportStream.write(os.EOL);
225
- }
226
-
227
- break;
228
- default:
229
- break;
230
- }
231
- }
232
-
233
- writeErr(evt) {
234
- if (evt.err.name === 'TSError' && evt.err.object && evt.err.object.diagnosticText) {
235
- this.lineWriter.writeLine(colors.errorStack(trimOffNewlines(evt.err.object.diagnosticText)));
236
- return;
237
- }
238
-
239
- if (evt.err.source) {
240
- this.lineWriter.writeLine(colors.errorSource(`${this.relativeFile(evt.err.source.file)}:${evt.err.source.line}`));
241
- const excerpt = codeExcerpt(evt.err.source, {maxWidth: this.reportStream.columns - 2});
242
- if (excerpt) {
243
- this.lineWriter.writeLine();
244
- this.lineWriter.writeLine(excerpt);
245
- }
246
- }
247
-
248
- if (evt.err.avaAssertionError) {
249
- const result = formatSerializedError(evt.err);
250
- if (result.printMessage) {
251
- this.lineWriter.writeLine();
252
- this.lineWriter.writeLine(evt.err.message);
253
- }
254
-
255
- if (result.formatted) {
256
- this.lineWriter.writeLine();
257
- this.lineWriter.writeLine(result.formatted);
258
- }
259
-
260
- const message = improperUsageMessages.forError(evt.err);
261
- if (message) {
262
- this.lineWriter.writeLine();
263
- this.lineWriter.writeLine(message);
264
- }
265
- } else if (evt.err.nonErrorObject) {
266
- this.lineWriter.writeLine(trimOffNewlines(evt.err.formatted));
267
- } else {
268
- this.lineWriter.writeLine();
269
- this.lineWriter.writeLine(evt.err.summary);
270
- }
271
-
272
- const formatted = this.formatErrorStack(evt.err);
273
- if (formatted.length > 0) {
274
- this.lineWriter.writeLine();
275
- this.lineWriter.writeLine(formatted.join('\n'));
276
- }
277
- }
278
-
279
- formatErrorStack(error) {
280
- if (!error.stack) {
281
- return [];
282
- }
283
-
284
- if (error.shouldBeautifyStack) {
285
- return beautifyStack(error.stack).map(line => {
286
- if (nodeInternals.some(internal => internal.test(line))) {
287
- return colors.errorStackInternal(`${figures.pointerSmall} ${line}`);
288
- }
289
-
290
- return colors.errorStack(`${figures.pointerSmall} ${line}`);
291
- });
292
- }
293
-
294
- return [error.stack];
295
- }
296
-
297
- writePendingTests(evt) {
298
- for (const [file, testsInFile] of evt.pendingTests) {
299
- if (testsInFile.size === 0) {
300
- continue;
301
- }
302
-
303
- this.lineWriter.writeLine(`${testsInFile.size} tests were pending in ${this.relativeFile(file)}\n`);
304
- for (const title of testsInFile) {
305
- this.lineWriter.writeLine(`${figures.circleDotted} ${this.prefixTitle(file, title)}`);
306
- }
307
-
308
- this.lineWriter.writeLine('');
309
- }
310
- }
311
-
312
- writeLogs(evt) {
313
- if (evt.logs) {
314
- for (const log of evt.logs) {
315
- const logLines = indentString(colors.log(log), 4);
316
- const logLinesWithLeadingFigure = logLines.replace(
317
- /^ {4}/,
318
- ` ${colors.information(figures.info)} `
319
- );
320
- this.lineWriter.writeLine(logLinesWithLeadingFigure);
321
- }
322
- }
323
- }
324
-
325
- writeTestSummary(evt) {
326
- if (evt.type === 'hook-failed' || evt.type === 'test-failed') {
327
- this.lineWriter.writeLine(`${colors.error(figures.cross)} ${this.prefixTitle(evt.testFile, evt.title)} ${colors.error(evt.err.message)}`);
328
- } else if (evt.knownFailing) {
329
- this.lineWriter.writeLine(`${colors.error(figures.tick)} ${colors.error(this.prefixTitle(evt.testFile, evt.title))}`);
330
- } else {
331
- const duration = evt.duration > this.durationThreshold ? colors.duration(' (' + prettyMs(evt.duration) + ')') : '';
332
-
333
- this.lineWriter.writeLine(`${colors.pass(figures.tick)} ${this.prefixTitle(evt.testFile, evt.title)}${duration}`);
334
- }
335
-
336
- this.writeLogs(evt);
337
- }
338
-
339
- writeFailure(evt) {
340
- this.lineWriter.writeLine(`${colors.title(this.prefixTitle(evt.testFile, evt.title))}`);
341
- this.writeLogs(evt);
342
- this.lineWriter.writeLine();
343
- this.writeErr(evt);
344
- }
345
-
346
- endRun() { // eslint-disable-line complexity
347
- if (this.emptyParallelRun) {
348
- this.lineWriter.writeLine('No files tested in this parallel run');
349
- this.lineWriter.writeLine();
350
- return;
351
- }
352
-
353
- if (!this.stats) {
354
- this.lineWriter.writeLine(colors.error(`${figures.cross} Couldn’t find any files to test`));
355
- this.lineWriter.writeLine();
356
- return;
357
- }
358
-
359
- if (this.matching && this.stats.selectedTests === 0) {
360
- this.lineWriter.writeLine(colors.error(`${figures.cross} Couldn’t find any matching tests`));
361
- this.lineWriter.writeLine();
362
- return;
363
- }
364
-
365
- this.lineWriter.writeLine();
366
-
367
- if (this.stats.parallelRuns) {
368
- const {currentFileCount, currentIndex, totalRuns} = this.stats.parallelRuns;
369
- this.lineWriter.writeLine(colors.information(`Ran ${currentFileCount} test ${plur('file', currentFileCount)} out of ${this.stats.files} for job ${currentIndex + 1} of ${totalRuns}`));
370
- this.lineWriter.writeLine();
371
- }
372
-
373
- let firstLinePostfix = this.watching ?
374
- ' ' + chalk.gray.dim('[' + new Date().toLocaleTimeString('en-US', {hour12: false}) + ']') :
375
- '';
376
-
377
- if (this.stats.failedHooks > 0) {
378
- this.lineWriter.writeLine(colors.error(`${this.stats.failedHooks} ${plur('hook', this.stats.failedHooks)} failed`) + firstLinePostfix);
379
- firstLinePostfix = '';
380
- }
381
-
382
- if (this.stats.failedTests > 0) {
383
- this.lineWriter.writeLine(colors.error(`${this.stats.failedTests} ${plur('test', this.stats.failedTests)} failed`) + firstLinePostfix);
384
- firstLinePostfix = '';
385
- }
386
-
387
- if (this.stats.failedHooks === 0 && this.stats.failedTests === 0 && this.stats.passedTests > 0) {
388
- this.lineWriter.writeLine(colors.pass(`${this.stats.passedTests} ${plur('test', this.stats.passedTests)} passed`) + firstLinePostfix);
389
- firstLinePostfix = '';
390
- }
391
-
392
- if (this.stats.passedKnownFailingTests > 0) {
393
- this.lineWriter.writeLine(colors.error(`${this.stats.passedKnownFailingTests} ${plur('known failure', this.stats.passedKnownFailingTests)}`));
394
- }
395
-
396
- if (this.stats.skippedTests > 0) {
397
- this.lineWriter.writeLine(colors.skip(`${this.stats.skippedTests} ${plur('test', this.stats.skippedTests)} skipped`));
398
- }
399
-
400
- if (this.stats.todoTests > 0) {
401
- this.lineWriter.writeLine(colors.todo(`${this.stats.todoTests} ${plur('test', this.stats.todoTests)} todo`));
402
- }
403
-
404
- if (this.stats.unhandledRejections > 0) {
405
- this.lineWriter.writeLine(colors.error(`${this.stats.unhandledRejections} unhandled ${plur('rejection', this.stats.unhandledRejections)}`));
406
- }
407
-
408
- if (this.stats.uncaughtExceptions > 0) {
409
- this.lineWriter.writeLine(colors.error(`${this.stats.uncaughtExceptions} uncaught ${plur('exception', this.stats.uncaughtExceptions)}`));
410
- }
411
-
412
- if (this.previousFailures > 0) {
413
- this.lineWriter.writeLine(colors.error(`${this.previousFailures} previous ${plur('failure', this.previousFailures)} in test files that were not rerun`));
414
- }
415
-
416
- if (this.stats.passedKnownFailingTests > 0) {
417
- this.lineWriter.writeLine();
418
- for (const evt of this.knownFailures) {
419
- this.lineWriter.writeLine(colors.error(this.prefixTitle(evt.testFile, evt.title)));
420
- }
421
- }
422
-
423
- const shouldWriteFailFastDisclaimer = this.failFastEnabled && (this.stats.remainingTests > 0 || this.stats.files > this.stats.finishedWorkers);
424
-
425
- if (this.failures.length > 0) {
426
- this.lineWriter.writeLine();
427
-
428
- const lastFailure = this.failures[this.failures.length - 1];
429
- for (const evt of this.failures) {
430
- this.writeFailure(evt);
431
- if (evt !== lastFailure || shouldWriteFailFastDisclaimer) {
432
- this.lineWriter.writeLine();
433
- this.lineWriter.writeLine();
434
- this.lineWriter.writeLine();
435
- }
436
- }
437
- }
438
-
439
- if (shouldWriteFailFastDisclaimer) {
440
- let remaining = '';
441
- if (this.stats.remainingTests > 0) {
442
- remaining += `At least ${this.stats.remainingTests} ${plur('test was', 'tests were', this.stats.remainingTests)} skipped`;
443
- if (this.stats.files > this.stats.finishedWorkers) {
444
- remaining += ', as well as ';
445
- }
446
- }
447
-
448
- if (this.stats.files > this.stats.finishedWorkers) {
449
- const skippedFileCount = this.stats.files - this.stats.finishedWorkers;
450
- remaining += `${skippedFileCount} ${plur('test file', 'test files', skippedFileCount)}`;
451
- if (this.stats.remainingTests === 0) {
452
- remaining += ` ${plur('was', 'were', skippedFileCount)} skipped`;
453
- }
454
- }
455
-
456
- this.lineWriter.writeLine(colors.information(`\`--fail-fast\` is on. ${remaining}.`));
457
- }
458
-
459
- this.lineWriter.writeLine();
460
- }
461
- }
462
-
463
- module.exports = VerboseReporter;