UNPKG

8.87 kBJavaScriptView Raw
1'use strict';
2
3Object.defineProperty(exports, '__esModule', {
4 value: true
5});
6exports.default = void 0;
7function crypto() {
8 const data = _interopRequireWildcard(require('crypto'));
9 crypto = function () {
10 return data;
11 };
12 return data;
13}
14function path() {
15 const data = _interopRequireWildcard(require('path'));
16 path = function () {
17 return data;
18 };
19 return data;
20}
21function fs() {
22 const data = _interopRequireWildcard(require('graceful-fs'));
23 fs = function () {
24 return data;
25 };
26 return data;
27}
28function _slash() {
29 const data = _interopRequireDefault(require('slash'));
30 _slash = function () {
31 return data;
32 };
33 return data;
34}
35function _jestHasteMap() {
36 const data = _interopRequireDefault(require('jest-haste-map'));
37 _jestHasteMap = function () {
38 return data;
39 };
40 return data;
41}
42function _interopRequireDefault(obj) {
43 return obj && obj.__esModule ? obj : {default: obj};
44}
45function _getRequireWildcardCache(nodeInterop) {
46 if (typeof WeakMap !== 'function') return null;
47 var cacheBabelInterop = new WeakMap();
48 var cacheNodeInterop = new WeakMap();
49 return (_getRequireWildcardCache = function (nodeInterop) {
50 return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
51 })(nodeInterop);
52}
53function _interopRequireWildcard(obj, nodeInterop) {
54 if (!nodeInterop && obj && obj.__esModule) {
55 return obj;
56 }
57 if (obj === null || (typeof obj !== 'object' && typeof obj !== 'function')) {
58 return {default: obj};
59 }
60 var cache = _getRequireWildcardCache(nodeInterop);
61 if (cache && cache.has(obj)) {
62 return cache.get(obj);
63 }
64 var newObj = {};
65 var hasPropertyDescriptor =
66 Object.defineProperty && Object.getOwnPropertyDescriptor;
67 for (var key in obj) {
68 if (key !== 'default' && Object.prototype.hasOwnProperty.call(obj, key)) {
69 var desc = hasPropertyDescriptor
70 ? Object.getOwnPropertyDescriptor(obj, key)
71 : null;
72 if (desc && (desc.get || desc.set)) {
73 Object.defineProperty(newObj, key, desc);
74 } else {
75 newObj[key] = obj[key];
76 }
77 }
78 }
79 newObj.default = obj;
80 if (cache) {
81 cache.set(obj, newObj);
82 }
83 return newObj;
84}
85/**
86 * Copyright (c) Meta Platforms, Inc. and affiliates.
87 *
88 * This source code is licensed under the MIT license found in the
89 * LICENSE file in the root directory of this source tree.
90 */
91
92const FAIL = 0;
93const SUCCESS = 1;
94/**
95 * The TestSequencer will ultimately decide which tests should run first.
96 * It is responsible for storing and reading from a local cache
97 * map that stores context information for a given test, such as how long it
98 * took to run during the last run and if it has failed or not.
99 * Such information is used on:
100 * TestSequencer.sort(tests: Array<Test>)
101 * to sort the order of the provided tests.
102 *
103 * After the results are collected,
104 * TestSequencer.cacheResults(tests: Array<Test>, results: AggregatedResult)
105 * is called to store/update this information on the cache map.
106 */
107class TestSequencer {
108 _cache = new Map();
109 _getCachePath(testContext) {
110 const {config} = testContext;
111 const HasteMapClass = _jestHasteMap().default.getStatic(config);
112 return HasteMapClass.getCacheFilePath(
113 config.cacheDirectory,
114 `perf-cache-${config.id}`
115 );
116 }
117 _getCache(test) {
118 const {context} = test;
119 if (!this._cache.has(context) && context.config.cache) {
120 const cachePath = this._getCachePath(context);
121 if (fs().existsSync(cachePath)) {
122 try {
123 this._cache.set(
124 context,
125 JSON.parse(fs().readFileSync(cachePath, 'utf8'))
126 );
127 } catch {}
128 }
129 }
130 let cache = this._cache.get(context);
131 if (!cache) {
132 cache = {};
133 this._cache.set(context, cache);
134 }
135 return cache;
136 }
137 _shardPosition(options) {
138 const shardRest = options.suiteLength % options.shardCount;
139 const ratio = options.suiteLength / options.shardCount;
140 return new Array(options.shardIndex)
141 .fill(true)
142 .reduce((acc, _, shardIndex) => {
143 const dangles = shardIndex < shardRest;
144 const shardSize = dangles ? Math.ceil(ratio) : Math.floor(ratio);
145 return acc + shardSize;
146 }, 0);
147 }
148
149 /**
150 * Select tests for shard requested via --shard=shardIndex/shardCount
151 * Sharding is applied before sorting
152 *
153 * @param tests All tests
154 * @param options shardIndex and shardIndex to select
155 *
156 * @example
157 * ```typescript
158 * class CustomSequencer extends Sequencer {
159 * shard(tests, { shardIndex, shardCount }) {
160 * const shardSize = Math.ceil(tests.length / options.shardCount);
161 * const shardStart = shardSize * (options.shardIndex - 1);
162 * const shardEnd = shardSize * options.shardIndex;
163 * return [...tests]
164 * .sort((a, b) => (a.path > b.path ? 1 : -1))
165 * .slice(shardStart, shardEnd);
166 * }
167 * }
168 * ```
169 */
170 shard(tests, options) {
171 const shardStart = this._shardPosition({
172 shardCount: options.shardCount,
173 shardIndex: options.shardIndex - 1,
174 suiteLength: tests.length
175 });
176 const shardEnd = this._shardPosition({
177 shardCount: options.shardCount,
178 shardIndex: options.shardIndex,
179 suiteLength: tests.length
180 });
181 return tests
182 .map(test => {
183 const relativeTestPath = path().posix.relative(
184 (0, _slash().default)(test.context.config.rootDir),
185 (0, _slash().default)(test.path)
186 );
187 return {
188 hash: crypto()
189 .createHash('sha1')
190 .update(relativeTestPath)
191 .digest('hex'),
192 test
193 };
194 })
195 .sort((a, b) => (a.hash < b.hash ? -1 : a.hash > b.hash ? 1 : 0))
196 .slice(shardStart, shardEnd)
197 .map(result => result.test);
198 }
199
200 /**
201 * Sort test to determine order of execution
202 * Sorting is applied after sharding
203 * @param tests
204 *
205 * ```typescript
206 * class CustomSequencer extends Sequencer {
207 * sort(tests) {
208 * const copyTests = Array.from(tests);
209 * return [...tests].sort((a, b) => (a.path > b.path ? 1 : -1));
210 * }
211 * }
212 * ```
213 */
214 sort(tests) {
215 /**
216 * Sorting tests is very important because it has a great impact on the
217 * user-perceived responsiveness and speed of the test run.
218 *
219 * If such information is on cache, tests are sorted based on:
220 * -> Has it failed during the last run ?
221 * Since it's important to provide the most expected feedback as quickly
222 * as possible.
223 * -> How long it took to run ?
224 * Because running long tests first is an effort to minimize worker idle
225 * time at the end of a long test run.
226 * And if that information is not available they are sorted based on file size
227 * since big test files usually take longer to complete.
228 *
229 * Note that a possible improvement would be to analyse other information
230 * from the file other than its size.
231 *
232 */
233 const stats = {};
234 const fileSize = ({path, context: {hasteFS}}) =>
235 stats[path] || (stats[path] = hasteFS.getSize(path) ?? 0);
236 tests.forEach(test => {
237 test.duration = this.time(test);
238 });
239 return tests.sort((testA, testB) => {
240 const failedA = this.hasFailed(testA);
241 const failedB = this.hasFailed(testB);
242 const hasTimeA = testA.duration != null;
243 if (failedA !== failedB) {
244 return failedA ? -1 : 1;
245 } else if (hasTimeA != (testB.duration != null)) {
246 // If only one of two tests has timing information, run it last
247 return hasTimeA ? 1 : -1;
248 } else if (testA.duration != null && testB.duration != null) {
249 return testA.duration < testB.duration ? 1 : -1;
250 } else {
251 return fileSize(testA) < fileSize(testB) ? 1 : -1;
252 }
253 });
254 }
255 allFailedTests(tests) {
256 return this.sort(tests.filter(test => this.hasFailed(test)));
257 }
258 cacheResults(tests, results) {
259 const map = Object.create(null);
260 tests.forEach(test => (map[test.path] = test));
261 results.testResults.forEach(testResult => {
262 const test = map[testResult.testFilePath];
263 if (test != null && !testResult.skipped) {
264 const cache = this._getCache(test);
265 const perf = testResult.perfStats;
266 const testRuntime =
267 perf.runtime ?? test.duration ?? perf.end - perf.start;
268 cache[testResult.testFilePath] = [
269 testResult.numFailingTests > 0 ? FAIL : SUCCESS,
270 testRuntime || 0
271 ];
272 }
273 });
274 this._cache.forEach((cache, context) =>
275 fs().writeFileSync(this._getCachePath(context), JSON.stringify(cache))
276 );
277 }
278 hasFailed(test) {
279 const cache = this._getCache(test);
280 return cache[test.path]?.[0] === FAIL;
281 }
282 time(test) {
283 const cache = this._getCache(test);
284 return cache[test.path]?.[1];
285 }
286}
287exports.default = TestSequencer;