ava 3.15.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/entrypoints/cli.mjs +4 -0
  2. package/entrypoints/eslint-plugin-helper.cjs +109 -0
  3. package/entrypoints/main.cjs +2 -0
  4. package/entrypoints/main.mjs +1 -0
  5. package/entrypoints/plugin.cjs +2 -0
  6. package/entrypoints/plugin.mjs +4 -0
  7. package/index.d.ts +6 -816
  8. package/lib/api.js +108 -49
  9. package/lib/assert.js +255 -270
  10. package/lib/chalk.js +9 -14
  11. package/lib/cli.js +118 -112
  12. package/lib/code-excerpt.js +12 -17
  13. package/lib/concordance-options.js +29 -65
  14. package/lib/context-ref.js +3 -6
  15. package/lib/create-chain.js +32 -20
  16. package/lib/environment-variables.js +1 -4
  17. package/lib/eslint-plugin-helper-worker.js +73 -0
  18. package/lib/extensions.js +2 -2
  19. package/lib/fork.js +81 -84
  20. package/lib/glob-helpers.cjs +140 -0
  21. package/lib/globs.js +136 -163
  22. package/lib/{ipc-flow-control.js → ipc-flow-control.cjs} +1 -0
  23. package/lib/is-ci.js +4 -2
  24. package/lib/like-selector.js +7 -13
  25. package/lib/line-numbers.js +11 -18
  26. package/lib/load-config.js +56 -180
  27. package/lib/module-types.js +3 -7
  28. package/lib/node-arguments.js +4 -5
  29. package/lib/{now-and-timers.js → now-and-timers.cjs} +0 -0
  30. package/lib/parse-test-args.js +22 -11
  31. package/lib/pkg.cjs +2 -0
  32. package/lib/plugin-support/shared-worker-loader.js +45 -48
  33. package/lib/plugin-support/shared-workers.js +24 -46
  34. package/lib/provider-manager.js +20 -14
  35. package/lib/reporters/beautify-stack.js +6 -12
  36. package/lib/reporters/colors.js +40 -15
  37. package/lib/reporters/default.js +114 -364
  38. package/lib/reporters/format-serialized-error.js +7 -18
  39. package/lib/reporters/improper-usage-messages.js +8 -9
  40. package/lib/reporters/prefix-title.js +17 -15
  41. package/lib/reporters/tap.js +18 -25
  42. package/lib/run-status.js +29 -23
  43. package/lib/runner.js +157 -172
  44. package/lib/scheduler.js +53 -0
  45. package/lib/serialize-error.js +61 -64
  46. package/lib/snapshot-manager.js +271 -289
  47. package/lib/test.js +135 -291
  48. package/lib/watcher.js +69 -44
  49. package/lib/worker/base.js +208 -0
  50. package/lib/worker/channel.cjs +290 -0
  51. package/lib/worker/dependency-tracker.js +24 -23
  52. package/lib/worker/{ensure-forked.js → guard-environment.cjs} +5 -4
  53. package/lib/worker/line-numbers.js +58 -20
  54. package/lib/worker/main.cjs +12 -0
  55. package/lib/worker/{options.js → options.cjs} +0 -0
  56. package/lib/worker/{plugin.js → plugin.cjs} +30 -21
  57. package/lib/worker/state.cjs +5 -0
  58. package/lib/worker/utils.cjs +6 -0
  59. package/package.json +71 -68
  60. package/plugin.d.ts +51 -53
  61. package/readme.md +5 -13
  62. package/types/assertions.d.ts +327 -0
  63. package/types/subscribable.ts +6 -0
  64. package/types/test-fn.d.ts +231 -0
  65. package/types/try-fn.d.ts +58 -0
  66. package/cli.js +0 -11
  67. package/eslint-plugin-helper.js +0 -201
  68. package/index.js +0 -8
  69. package/lib/worker/ipc.js +0 -201
  70. package/lib/worker/main.js +0 -21
  71. package/lib/worker/subprocess.js +0 -266
  72. package/plugin.js +0 -9
package/lib/api.js CHANGED
@@ -1,23 +1,25 @@
1
- 'use strict';
2
- const fs = require('fs');
3
- const path = require('path');
4
- const os = require('os');
5
- const commonPathPrefix = require('common-path-prefix');
6
- const resolveCwd = require('resolve-cwd');
7
- const debounce = require('lodash/debounce');
8
- const arrify = require('arrify');
9
- const ms = require('ms');
10
- const chunkd = require('chunkd');
11
- const Emittery = require('emittery');
12
- const pMap = require('p-map');
13
- const tempDir = require('temp-dir');
14
- const globs = require('./globs');
15
- const isCi = require('./is-ci');
16
- const RunStatus = require('./run-status');
17
- const fork = require('./fork');
18
- const serializeError = require('./serialize-error');
19
- const {getApplicableLineNumbers} = require('./line-numbers');
20
- const sharedWorkers = require('./plugin-support/shared-workers');
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import process from 'node:process';
5
+
6
+ import arrify from 'arrify';
7
+ import chunkd from 'chunkd';
8
+ import commonPathPrefix from 'common-path-prefix';
9
+ import Emittery from 'emittery';
10
+ import ms from 'ms';
11
+ import pMap from 'p-map';
12
+ import resolveCwd from 'resolve-cwd';
13
+ import tempDir from 'temp-dir';
14
+
15
+ import fork from './fork.js';
16
+ import * as globs from './globs.js';
17
+ import isCi from './is-ci.js';
18
+ import {getApplicableLineNumbers} from './line-numbers.js';
19
+ import {observeWorkerProcess} from './plugin-support/shared-workers.js';
20
+ import RunStatus from './run-status.js';
21
+ import scheduler from './scheduler.js';
22
+ import serializeError from './serialize-error.js';
21
23
 
22
24
  function resolveModules(modules) {
23
25
  return arrify(modules).map(name => {
@@ -40,7 +42,40 @@ function getFilePathPrefix(files) {
40
42
  return commonPathPrefix(files);
41
43
  }
42
44
 
43
- class Api extends Emittery {
45
+ class TimeoutTrigger {
46
+ constructor(fn, waitMs = 0) {
47
+ this.fn = fn.bind(null);
48
+ this.ignoreUntil = 0;
49
+ this.waitMs = waitMs;
50
+ this.timer = undefined;
51
+ }
52
+
53
+ debounce() {
54
+ if (this.timer === undefined) {
55
+ this.timer = setTimeout(() => this.trigger(), this.waitMs);
56
+ } else {
57
+ this.timer.refresh();
58
+ }
59
+ }
60
+
61
+ discard() {
62
+ // N.B. this.timer is not cleared so if debounce() is called after it will
63
+ // not run again.
64
+ clearTimeout(this.timer);
65
+ }
66
+
67
+ ignoreFor(periodMs) {
68
+ this.ignoreUntil = Math.max(this.ignoreUntil, Date.now() + periodMs);
69
+ }
70
+
71
+ trigger() {
72
+ if (Date.now() >= this.ignoreUntil) {
73
+ this.fn();
74
+ }
75
+ }
76
+ }
77
+
78
+ export default class Api extends Emittery {
44
79
  constructor(options) {
45
80
  super();
46
81
 
@@ -55,7 +90,7 @@ class Api extends Emittery {
55
90
  }
56
91
  }
57
92
 
58
- async run({files: selectedFiles = [], filter = [], runtimeOptions = {}} = {}) {
93
+ async run({files: selectedFiles = [], filter = [], runtimeOptions = {}} = {}) { // eslint-disable-line complexity
59
94
  let setupOrGlobError;
60
95
 
61
96
  const apiOptions = this.options;
@@ -70,11 +105,11 @@ class Api extends Emittery {
70
105
  let bailed = false;
71
106
  const pendingWorkers = new Set();
72
107
  const timedOutWorkerFiles = new Set();
73
- let restartTimer;
108
+ let timeoutTrigger;
74
109
  if (apiOptions.timeout && !apiOptions.debug) {
75
110
  const timeout = ms(apiOptions.timeout);
76
111
 
77
- restartTimer = debounce(() => {
112
+ timeoutTrigger = new TimeoutTrigger(() => {
78
113
  // If failFast is active, prevent new test files from running after
79
114
  // the current ones are exited.
80
115
  if (failFast) {
@@ -89,7 +124,7 @@ class Api extends Emittery {
89
124
  }
90
125
  }, timeout);
91
126
  } else {
92
- restartTimer = Object.assign(() => {}, {cancel() {}});
127
+ timeoutTrigger = new TimeoutTrigger(() => {});
93
128
  }
94
129
 
95
130
  this._interruptHandler = () => {
@@ -102,7 +137,7 @@ class Api extends Emittery {
102
137
  bailed = true;
103
138
 
104
139
  // Make sure we don't run the timeout handler
105
- restartTimer.cancel();
140
+ timeoutTrigger.discard();
106
141
 
107
142
  runStatus.emitStateChange({type: 'interrupt'});
108
143
 
@@ -111,6 +146,8 @@ class Api extends Emittery {
111
146
  }
112
147
  };
113
148
 
149
+ const {providers = []} = this.options;
150
+
114
151
  let testFiles;
115
152
  try {
116
153
  testFiles = await globs.findTests({cwd: this.options.projectDir, ...apiOptions.globs});
@@ -118,7 +155,8 @@ class Api extends Emittery {
118
155
  selectedFiles = filter.length === 0 ? testFiles : globs.applyTestFileFilter({
119
156
  cwd: this.options.projectDir,
120
157
  filter: filter.map(({pattern}) => pattern),
121
- testFiles
158
+ providers,
159
+ testFiles,
122
160
  });
123
161
  }
124
162
  } catch (error) {
@@ -126,6 +164,13 @@ class Api extends Emittery {
126
164
  setupOrGlobError = error;
127
165
  }
128
166
 
167
+ const selectionInsights = {
168
+ filter,
169
+ ignoredFilterPatternFiles: selectedFiles.ignoredFilterPatternFiles || [],
170
+ testFileCount: testFiles.length,
171
+ selectionCount: selectedFiles.length,
172
+ };
173
+
129
174
  try {
130
175
  if (this.options.parallelRuns) {
131
176
  const {currentIndex, totalRuns} = this.options.parallelRuns;
@@ -137,11 +182,13 @@ class Api extends Emittery {
137
182
 
138
183
  const currentFileCount = selectedFiles.length;
139
184
 
140
- runStatus = new RunStatus(fileCount, {currentFileCount, currentIndex, totalRuns});
185
+ runStatus = new RunStatus(fileCount, {currentFileCount, currentIndex, totalRuns}, selectionInsights);
141
186
  } else {
142
- runStatus = new RunStatus(selectedFiles.length, null);
187
+ runStatus = new RunStatus(selectedFiles.length, null, selectionInsights);
143
188
  }
144
189
 
190
+ selectedFiles = scheduler.failingTestsFirst(selectedFiles, this._getLocalCacheDir(), this.options.cacheEnabled);
191
+
145
192
  const debugWithoutSpecificFile = Boolean(this.options.debug) && !this.options.debug.active && selectedFiles.length !== 1;
146
193
 
147
194
  await this.emit('run', {
@@ -155,7 +202,7 @@ class Api extends Emittery {
155
202
  previousFailures: runtimeOptions.previousFailures || 0,
156
203
  runOnlyExclusive: runtimeOptions.runOnlyExclusive === true,
157
204
  runVector: runtimeOptions.runVector || 0,
158
- status: runStatus
205
+ status: runStatus,
159
206
  });
160
207
 
161
208
  if (setupOrGlobError) {
@@ -169,9 +216,9 @@ class Api extends Emittery {
169
216
 
170
217
  runStatus.on('stateChange', record => {
171
218
  if (record.testFile && !timedOutWorkerFiles.has(record.testFile)) {
172
- // Restart the timer whenever there is activity from workers that
219
+ // Debounce the timer whenever there is activity from workers that
173
220
  // haven't already timed out.
174
- restartTimer();
221
+ timeoutTrigger.debounce();
175
222
  }
176
223
 
177
224
  if (failFast && (record.type === 'hook-failed' || record.type === 'test-failed' || record.type === 'worker-failed')) {
@@ -185,14 +232,16 @@ class Api extends Emittery {
185
232
  }
186
233
  });
187
234
 
188
- const {providers = []} = this.options;
189
- const providerStates = (await Promise.all(providers.map(async ({type, main}) => {
235
+ const providerStates = [];
236
+ await Promise.all(providers.map(async ({type, main}) => {
190
237
  const state = await main.compile({cacheDir: this._createCacheDir(), files: testFiles});
191
- return state === null ? null : {type, state};
192
- }))).filter(state => state !== null);
238
+ if (state !== null) {
239
+ providerStates.push({type, state});
240
+ }
241
+ }));
193
242
 
194
243
  // Resolve the correct concurrency value.
195
- let concurrency = Math.min(os.cpus().length, isCi ? 2 : Infinity);
244
+ let concurrency = Math.min(os.cpus().length, isCi ? 2 : Number.POSITIVE_INFINITY);
196
245
  if (apiOptions.concurrency > 0) {
197
246
  concurrency = apiOptions.concurrency;
198
247
  }
@@ -212,13 +261,15 @@ class Api extends Emittery {
212
261
  }
213
262
 
214
263
  const lineNumbers = getApplicableLineNumbers(globs.normalizeFileForMatching(apiOptions.projectDir, file), filter);
264
+ // Removing `providers` field because they cannot be transfered to the worker threads.
265
+ const {providers, ...forkOptions} = apiOptions;
215
266
  const options = {
216
- ...apiOptions,
267
+ ...forkOptions,
217
268
  providerStates,
218
269
  lineNumbers,
219
270
  recordNewSnapshots: !isCi,
220
271
  // If we're looking for matches, run every single test process in exclusive-only mode
221
- runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true
272
+ runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true,
222
273
  };
223
274
 
224
275
  if (runtimeOptions.updateSnapshots) {
@@ -227,42 +278,52 @@ class Api extends Emittery {
227
278
  }
228
279
 
229
280
  const worker = fork(file, options, apiOptions.nodeArguments);
281
+ worker.onStateChange(data => {
282
+ if (data.type === 'test-timeout-configured' && !apiOptions.debug) {
283
+ timeoutTrigger.ignoreFor(data.period);
284
+ }
285
+ });
230
286
  runStatus.observeWorker(worker, file, {selectingLines: lineNumbers.length > 0});
231
- deregisteredSharedWorkers.push(sharedWorkers.observeWorkerProcess(worker, runStatus));
287
+ deregisteredSharedWorkers.push(observeWorkerProcess(worker, runStatus));
232
288
 
233
289
  pendingWorkers.add(worker);
234
290
  worker.promise.then(() => {
235
291
  pendingWorkers.delete(worker);
236
292
  });
237
- restartTimer();
293
+ timeoutTrigger.debounce();
238
294
 
239
295
  await worker.promise;
240
296
  }, {concurrency, stopOnError: false});
241
297
 
242
298
  // Allow shared workers to clean up before the run ends.
243
299
  await Promise.all(deregisteredSharedWorkers);
300
+ scheduler.storeFailedTestFiles(runStatus, this.options.cacheEnabled === false ? null : this._createCacheDir());
244
301
  } catch (error) {
245
302
  if (error && error.name === 'AggregateError') {
246
- for (const err of error) {
247
- runStatus.emitStateChange({type: 'internal-error', err: serializeError('Internal error', false, err)});
303
+ for (const error_ of error.errors) {
304
+ runStatus.emitStateChange({type: 'internal-error', err: serializeError('Internal error', false, error_)});
248
305
  }
249
306
  } else {
250
307
  runStatus.emitStateChange({type: 'internal-error', err: serializeError('Internal error', false, error)});
251
308
  }
252
309
  }
253
310
 
254
- restartTimer.cancel();
311
+ timeoutTrigger.discard();
255
312
  return runStatus;
256
313
  }
257
314
 
315
+ _getLocalCacheDir() {
316
+ return path.join(this.options.projectDir, 'node_modules', '.cache', 'ava');
317
+ }
318
+
258
319
  _createCacheDir() {
259
320
  if (this._cacheDir) {
260
321
  return this._cacheDir;
261
322
  }
262
323
 
263
- const cacheDir = this.options.cacheEnabled === false ?
264
- fs.mkdtempSync(`${tempDir}${path.sep}`) :
265
- path.join(this.options.projectDir, 'node_modules', '.cache', 'ava');
324
+ const cacheDir = this.options.cacheEnabled === false
325
+ ? fs.mkdtempSync(`${tempDir}${path.sep}`)
326
+ : this._getLocalCacheDir();
266
327
 
267
328
  // Ensure cacheDir exists
268
329
  fs.mkdirSync(cacheDir, {recursive: true});
@@ -272,5 +333,3 @@ class Api extends Emittery {
272
333
  return cacheDir;
273
334
  }
274
335
  }
275
-
276
- module.exports = Api;