UNPKG

4.44 kBJavaScriptView Raw
1'use strict';
2const Emittery = require('emittery');
3const cloneDeep = require('lodash/cloneDeep');
4
5class RunStatus extends Emittery {
6 constructor(files, parallelRuns) {
7 super();
8
9 this.pendingTests = new Map();
10
11 this.emptyParallelRun = parallelRuns &&
12 parallelRuns.currentFileCount === 0 &&
13 parallelRuns.totalRuns > 1 &&
14 files > 0;
15
16 this.stats = {
17 byFile: new Map(),
18 declaredTests: 0,
19 failedHooks: 0,
20 failedTests: 0,
21 failedWorkers: 0,
22 files,
23 parallelRuns,
24 finishedWorkers: 0,
25 internalErrors: 0,
26 remainingTests: 0,
27 passedKnownFailingTests: 0,
28 passedTests: 0,
29 selectedTests: 0,
30 sharedWorkerErrors: 0,
31 skippedTests: 0,
32 timeouts: 0,
33 todoTests: 0,
34 uncaughtExceptions: 0,
35 unhandledRejections: 0
36 };
37 }
38
39 observeWorker(worker, testFile, stats) {
40 this.stats.byFile.set(testFile, {
41 declaredTests: 0,
42 failedHooks: 0,
43 failedTests: 0,
44 internalErrors: 0,
45 remainingTests: 0,
46 passedKnownFailingTests: 0,
47 passedTests: 0,
48 selectedTests: 0,
49 selectingLines: false,
50 skippedTests: 0,
51 todoTests: 0,
52 uncaughtExceptions: 0,
53 unhandledRejections: 0,
54 ...stats
55 });
56
57 this.pendingTests.set(testFile, new Set());
58 worker.onStateChange(data => this.emitStateChange(data));
59 }
60
61 emitStateChange(event) {
62 const {stats} = this;
63 const fileStats = stats.byFile.get(event.testFile);
64
65 let changedStats = true;
66 switch (event.type) {
67 case 'declared-test':
68 stats.declaredTests++;
69 fileStats.declaredTests++;
70 break;
71 case 'hook-failed':
72 stats.failedHooks++;
73 fileStats.failedHooks++;
74 break;
75 case 'internal-error':
76 stats.internalErrors++;
77 if (event.testFile) {
78 fileStats.internalErrors++;
79 }
80
81 break;
82 case 'selected-test':
83 stats.selectedTests++;
84 fileStats.selectedTests++;
85 if (event.skip) {
86 stats.skippedTests++;
87 fileStats.skippedTests++;
88 } else if (event.todo) {
89 stats.todoTests++;
90 fileStats.todoTests++;
91 } else {
92 stats.remainingTests++;
93 fileStats.remainingTests++;
94 this.addPendingTest(event);
95 }
96
97 break;
98 case 'shared-worker-error':
99 stats.sharedWorkerErrors++;
100 break;
101 case 'test-failed':
102 stats.failedTests++;
103 fileStats.failedTests++;
104 stats.remainingTests--;
105 fileStats.remainingTests--;
106 this.removePendingTest(event);
107 break;
108 case 'test-passed':
109 if (event.knownFailing) {
110 stats.passedKnownFailingTests++;
111 fileStats.passedKnownFailingTests++;
112 } else {
113 stats.passedTests++;
114 fileStats.passedTests++;
115 }
116
117 stats.remainingTests--;
118 fileStats.remainingTests--;
119 this.removePendingTest(event);
120 break;
121 case 'timeout':
122 event.pendingTests = this.pendingTests;
123 this.pendingTests = new Map();
124 stats.timeouts++;
125 break;
126 case 'interrupt':
127 event.pendingTests = this.pendingTests;
128 this.pendingTests = new Map();
129 break;
130 case 'uncaught-exception':
131 stats.uncaughtExceptions++;
132 fileStats.uncaughtExceptions++;
133 break;
134 case 'unhandled-rejection':
135 stats.unhandledRejections++;
136 fileStats.unhandledRejections++;
137 break;
138 case 'worker-failed':
139 stats.failedWorkers++;
140 break;
141 case 'worker-finished':
142 stats.finishedWorkers++;
143 break;
144 default:
145 changedStats = false;
146 break;
147 }
148
149 if (changedStats) {
150 this.emit('stateChange', {type: 'stats', stats: cloneDeep(stats)});
151 }
152
153 this.emit('stateChange', event);
154 }
155
156 suggestExitCode(circumstances) {
157 if (this.emptyParallelRun) {
158 return 0;
159 }
160
161 if (circumstances.matching && this.stats.selectedTests === 0) {
162 return 1;
163 }
164
165 if (
166 this.stats.declaredTests === 0 ||
167 this.stats.internalErrors > 0 ||
168 this.stats.failedHooks > 0 ||
169 this.stats.failedTests > 0 ||
170 this.stats.failedWorkers > 0 ||
171 this.stats.sharedWorkerErrors > 0 ||
172 this.stats.timeouts > 0 ||
173 this.stats.uncaughtExceptions > 0 ||
174 this.stats.unhandledRejections > 0
175 ) {
176 return 1;
177 }
178
179 if ([...this.stats.byFile.values()].some(stats => stats.selectingLines && stats.selectedTests === 0)) {
180 return 1;
181 }
182
183 return 0;
184 }
185
186 addPendingTest(event) {
187 if (this.pendingTests.has(event.testFile)) {
188 this.pendingTests.get(event.testFile).add(event.title);
189 }
190 }
191
192 removePendingTest(event) {
193 if (this.pendingTests.has(event.testFile)) {
194 this.pendingTests.get(event.testFile).delete(event.title);
195 }
196 }
197}
198
199module.exports = RunStatus;