ava
Version:
277 lines (233 loc) • 6.03 kB
JavaScript
import v8 from 'node:v8';
import Emittery from 'emittery';
const copyStats = stats => v8.deserialize(v8.serialize(stats));
export default class RunStatus extends Emittery {
constructor(files, parallelRuns, selectionInsights) {
super();
this.pendingTests = new Map();
this.pendingTestsLogs = new Map();
this.emptyParallelRun = parallelRuns
&& parallelRuns.currentFileCount === 0
&& parallelRuns.totalRuns > 1
&& files > 0;
this.selectionInsights = selectionInsights;
this.stats = {
byFile: new Map(),
declaredTests: 0,
failedHooks: 0,
failedTests: 0,
failedWorkers: 0,
files,
parallelRuns,
finishedWorkers: 0,
internalErrors: 0,
remainingTests: 0,
passedKnownFailingTests: 0,
passedTests: 0,
selectedTests: 0,
sharedWorkerErrors: 0,
skippedTests: 0,
timedOutTests: 0,
timeouts: 0,
todoTests: 0,
uncaughtExceptions: 0,
unexpectedProcessExits: 0,
unhandledRejections: 0,
};
}
observeWorker(worker, testFile, stats) {
this.stats.byFile.set(testFile, {
declaredTests: 0,
failedHooks: 0,
failedTests: 0,
internalErrors: 0,
remainingTests: 0,
passedKnownFailingTests: 0,
passedTests: 0,
selectedTests: 0,
selectingLines: false,
skippedTests: 0,
todoTests: 0,
uncaughtExceptions: 0,
unexpectedProcessExits: 0,
unhandledRejections: 0,
...stats,
});
this.pendingTests.set(testFile, new Set());
this.pendingTestsLogs.set(testFile, new Map());
worker.onStateChange(data => this.emitStateChange(data));
}
// eslint-disable-next-line complexity
emitStateChange(event) {
const {stats} = this;
const fileStats = stats.byFile.get(event.testFile);
let changedStats = true;
switch (event.type) {
case 'declared-test': {
stats.declaredTests++;
fileStats.declaredTests++;
break;
}
case 'hook-failed': {
stats.failedHooks++;
fileStats.failedHooks++;
break;
}
case 'internal-error': {
stats.internalErrors++;
if (event.testFile) {
fileStats.internalErrors++;
}
break;
}
case 'selected-test': {
stats.selectedTests++;
fileStats.selectedTests++;
if (event.skip) {
stats.skippedTests++;
fileStats.skippedTests++;
} else if (event.todo) {
stats.todoTests++;
fileStats.todoTests++;
} else {
stats.remainingTests++;
fileStats.remainingTests++;
this.addPendingTest(event);
}
break;
}
case 'shared-worker-error': {
stats.sharedWorkerErrors++;
break;
}
case 'test-failed': {
stats.failedTests++;
fileStats.failedTests++;
stats.remainingTests--;
fileStats.remainingTests--;
this.removePendingTest(event);
break;
}
case 'test-passed': {
if (event.knownFailing) {
stats.passedKnownFailingTests++;
fileStats.passedKnownFailingTests++;
} else {
stats.passedTests++;
fileStats.passedTests++;
}
stats.remainingTests--;
fileStats.remainingTests--;
this.removePendingTest(event);
break;
}
case 'test-register-log-reference': {
this.addPendingTestLogs(event);
break;
}
case 'timeout': {
stats.timeouts++;
event.pendingTests = this.pendingTests;
event.pendingTestsLogs = this.pendingTestsLogs;
this.pendingTests = new Map();
this.pendingTestsLogs = new Map();
for (const testsInFile of event.pendingTests.values()) {
stats.timedOutTests += testsInFile.size;
}
break;
}
case 'interrupt': {
event.pendingTests = this.pendingTests;
event.pendingTestsLogs = this.pendingTestsLogs;
this.pendingTests = new Map();
this.pendingTestsLogs = new Map();
break;
}
case 'process-exit': {
stats.unexpectedProcessExits++;
fileStats.unexpectedProcessExits++;
event.pendingTests = this.pendingTests;
event.pendingTestsLogs = this.pendingTestsLogs;
this.pendingTests = new Map();
this.pendingTestsLogs = new Map();
break;
}
case 'uncaught-exception': {
stats.uncaughtExceptions++;
fileStats.uncaughtExceptions++;
break;
}
case 'unhandled-rejection': {
stats.unhandledRejections++;
fileStats.unhandledRejections++;
break;
}
case 'worker-failed': {
stats.failedWorkers++;
break;
}
case 'worker-finished': {
stats.finishedWorkers++;
if (this.pendingTests.get(event.testFile)?.size === 0) {
this.pendingTests.delete(event.testFile);
}
break;
}
default: {
changedStats = false;
break;
}
}
if (changedStats) {
this.emit('stateChange', {type: 'stats', stats: copyStats(stats)});
}
this.emit('stateChange', event);
}
end() {
this.emitStateChange({type: 'end'});
return this;
}
suggestExitCode(circumstances) {
if (this.emptyParallelRun) {
return 0;
}
if (circumstances.matching && this.stats.selectedTests === 0) {
return 1;
}
if (
this.stats.declaredTests === 0
|| this.stats.internalErrors > 0
|| this.stats.failedHooks > 0
|| this.stats.failedTests > 0
|| this.stats.failedWorkers > 0
|| this.stats.remainingTests > 0
|| this.stats.sharedWorkerErrors > 0
|| this.stats.timeouts > 0
|| this.stats.uncaughtExceptions > 0
|| this.stats.unexpectedProcessExits > 0
|| this.stats.unhandledRejections > 0
) {
return 1;
}
if ([...this.stats.byFile.values()].some(stats => stats.selectingLines && stats.selectedTests === 0)) {
return 1;
}
return 0;
}
addPendingTestLogs(event) {
this.pendingTestsLogs.get(event.testFile)?.set(event.title, event.logs);
}
addPendingTest(event) {
if (this.pendingTests.has(event.testFile)) {
this.pendingTests.get(event.testFile).add(event.title);
}
}
removePendingTest(event) {
if (this.pendingTests.has(event.testFile)) {
this.pendingTests.get(event.testFile).delete(event.title);
}
}
getFailedTestFiles() {
return [...this.stats.byFile].filter(statByFile => statByFile[1].failedTests).map(statByFile => statByFile[0]);
}
}