1 | 'use strict';
|
2 | const Emittery = require('emittery');
|
3 | const cloneDeep = require('lodash/cloneDeep');
|
4 |
|
5 | class 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 |
|
199 | module.exports = RunStatus;
|