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