ava 4.0.0-alpha.1 → 4.0.1

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 (70) hide show
  1. package/entrypoints/cli.mjs +4 -0
  2. package/{eslint-plugin-helper.js → entrypoints/eslint-plugin-helper.cjs} +20 -7
  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 -709
  8. package/lib/api.js +95 -46
  9. package/lib/assert.js +122 -173
  10. package/lib/chalk.js +9 -14
  11. package/lib/cli.js +105 -97
  12. package/lib/code-excerpt.js +12 -17
  13. package/lib/concordance-options.js +30 -31
  14. package/lib/context-ref.js +3 -6
  15. package/lib/create-chain.js +32 -4
  16. package/lib/environment-variables.js +1 -4
  17. package/lib/eslint-plugin-helper-worker.js +16 -26
  18. package/lib/extensions.js +2 -2
  19. package/lib/fork.js +42 -83
  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 +10 -17
  26. package/lib/load-config.js +62 -56
  27. package/lib/module-types.js +3 -3
  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 -43
  34. package/lib/provider-manager.js +20 -14
  35. package/lib/reporters/beautify-stack.js +6 -11
  36. package/lib/reporters/colors.js +40 -15
  37. package/lib/reporters/default.js +115 -350
  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 +15 -16
  42. package/lib/run-status.js +25 -23
  43. package/lib/runner.js +138 -127
  44. package/lib/scheduler.js +42 -36
  45. package/lib/serialize-error.js +34 -34
  46. package/lib/snapshot-manager.js +83 -76
  47. package/lib/test.js +114 -195
  48. package/lib/watcher.js +65 -40
  49. package/lib/worker/base.js +48 -99
  50. package/lib/worker/channel.cjs +290 -0
  51. package/lib/worker/dependency-tracker.js +22 -22
  52. package/lib/worker/guard-environment.cjs +19 -0
  53. package/lib/worker/line-numbers.js +57 -19
  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} +31 -16
  57. package/lib/worker/state.cjs +5 -0
  58. package/lib/worker/{utils.js → utils.cjs} +1 -1
  59. package/package.json +60 -68
  60. package/plugin.d.ts +51 -53
  61. package/readme.md +5 -12
  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/index.js +0 -8
  68. package/lib/worker/channel.js +0 -218
  69. package/lib/worker/main.js +0 -20
  70. package/plugin.js +0 -9
package/lib/api.js CHANGED
@@ -1,24 +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');
21
- const scheduler = require('./scheduler');
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';
22
23
 
23
24
  function resolveModules(modules) {
24
25
  return arrify(modules).map(name => {
@@ -41,7 +42,40 @@ function getFilePathPrefix(files) {
41
42
  return commonPathPrefix(files);
42
43
  }
43
44
 
44
- 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 {
45
79
  constructor(options) {
46
80
  super();
47
81
 
@@ -71,11 +105,11 @@ class Api extends Emittery {
71
105
  let bailed = false;
72
106
  const pendingWorkers = new Set();
73
107
  const timedOutWorkerFiles = new Set();
74
- let restartTimer;
108
+ let timeoutTrigger;
75
109
  if (apiOptions.timeout && !apiOptions.debug) {
76
110
  const timeout = ms(apiOptions.timeout);
77
111
 
78
- restartTimer = debounce(() => {
112
+ timeoutTrigger = new TimeoutTrigger(() => {
79
113
  // If failFast is active, prevent new test files from running after
80
114
  // the current ones are exited.
81
115
  if (failFast) {
@@ -90,7 +124,7 @@ class Api extends Emittery {
90
124
  }
91
125
  }, timeout);
92
126
  } else {
93
- restartTimer = Object.assign(() => {}, {cancel() {}});
127
+ timeoutTrigger = new TimeoutTrigger(() => {});
94
128
  }
95
129
 
96
130
  this._interruptHandler = () => {
@@ -103,7 +137,7 @@ class Api extends Emittery {
103
137
  bailed = true;
104
138
 
105
139
  // Make sure we don't run the timeout handler
106
- restartTimer.cancel();
140
+ timeoutTrigger.discard();
107
141
 
108
142
  runStatus.emitStateChange({type: 'interrupt'});
109
143
 
@@ -112,6 +146,8 @@ class Api extends Emittery {
112
146
  }
113
147
  };
114
148
 
149
+ const {providers = []} = this.options;
150
+
115
151
  let testFiles;
116
152
  try {
117
153
  testFiles = await globs.findTests({cwd: this.options.projectDir, ...apiOptions.globs});
@@ -119,7 +155,8 @@ class Api extends Emittery {
119
155
  selectedFiles = filter.length === 0 ? testFiles : globs.applyTestFileFilter({
120
156
  cwd: this.options.projectDir,
121
157
  filter: filter.map(({pattern}) => pattern),
122
- testFiles
158
+ providers,
159
+ testFiles,
123
160
  });
124
161
  }
125
162
  } catch (error) {
@@ -127,6 +164,13 @@ class Api extends Emittery {
127
164
  setupOrGlobError = error;
128
165
  }
129
166
 
167
+ const selectionInsights = {
168
+ filter,
169
+ ignoredFilterPatternFiles: selectedFiles.ignoredFilterPatternFiles || [],
170
+ testFileCount: testFiles.length,
171
+ selectionCount: selectedFiles.length,
172
+ };
173
+
130
174
  try {
131
175
  if (this.options.parallelRuns) {
132
176
  const {currentIndex, totalRuns} = this.options.parallelRuns;
@@ -138,9 +182,9 @@ class Api extends Emittery {
138
182
 
139
183
  const currentFileCount = selectedFiles.length;
140
184
 
141
- runStatus = new RunStatus(fileCount, {currentFileCount, currentIndex, totalRuns});
185
+ runStatus = new RunStatus(fileCount, {currentFileCount, currentIndex, totalRuns}, selectionInsights);
142
186
  } else {
143
- runStatus = new RunStatus(selectedFiles.length, null);
187
+ runStatus = new RunStatus(selectedFiles.length, null, selectionInsights);
144
188
  }
145
189
 
146
190
  selectedFiles = scheduler.failingTestsFirst(selectedFiles, this._getLocalCacheDir(), this.options.cacheEnabled);
@@ -158,7 +202,7 @@ class Api extends Emittery {
158
202
  previousFailures: runtimeOptions.previousFailures || 0,
159
203
  runOnlyExclusive: runtimeOptions.runOnlyExclusive === true,
160
204
  runVector: runtimeOptions.runVector || 0,
161
- status: runStatus
205
+ status: runStatus,
162
206
  });
163
207
 
164
208
  if (setupOrGlobError) {
@@ -172,9 +216,9 @@ class Api extends Emittery {
172
216
 
173
217
  runStatus.on('stateChange', record => {
174
218
  if (record.testFile && !timedOutWorkerFiles.has(record.testFile)) {
175
- // Restart the timer whenever there is activity from workers that
219
+ // Debounce the timer whenever there is activity from workers that
176
220
  // haven't already timed out.
177
- restartTimer();
221
+ timeoutTrigger.debounce();
178
222
  }
179
223
 
180
224
  if (failFast && (record.type === 'hook-failed' || record.type === 'test-failed' || record.type === 'worker-failed')) {
@@ -188,11 +232,13 @@ class Api extends Emittery {
188
232
  }
189
233
  });
190
234
 
191
- const {providers = []} = this.options;
192
- const providerStates = (await Promise.all(providers.map(async ({type, main}) => {
235
+ const providerStates = [];
236
+ await Promise.all(providers.map(async ({type, main}) => {
193
237
  const state = await main.compile({cacheDir: this._createCacheDir(), files: testFiles});
194
- return state === null ? null : {type, state};
195
- }))).filter(state => state !== null);
238
+ if (state !== null) {
239
+ providerStates.push({type, state});
240
+ }
241
+ }));
196
242
 
197
243
  // Resolve the correct concurrency value.
198
244
  let concurrency = Math.min(os.cpus().length, isCi ? 2 : Number.POSITIVE_INFINITY);
@@ -223,7 +269,7 @@ class Api extends Emittery {
223
269
  lineNumbers,
224
270
  recordNewSnapshots: !isCi,
225
271
  // If we're looking for matches, run every single test process in exclusive-only mode
226
- runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true
272
+ runOnlyExclusive: apiOptions.match.length > 0 || runtimeOptions.runOnlyExclusive === true,
227
273
  };
228
274
 
229
275
  if (runtimeOptions.updateSnapshots) {
@@ -232,14 +278,19 @@ class Api extends Emittery {
232
278
  }
233
279
 
234
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
+ });
235
286
  runStatus.observeWorker(worker, file, {selectingLines: lineNumbers.length > 0});
236
- deregisteredSharedWorkers.push(sharedWorkers.observeWorkerProcess(worker, runStatus));
287
+ deregisteredSharedWorkers.push(observeWorkerProcess(worker, runStatus));
237
288
 
238
289
  pendingWorkers.add(worker);
239
290
  worker.promise.then(() => {
240
291
  pendingWorkers.delete(worker);
241
292
  });
242
- restartTimer();
293
+ timeoutTrigger.debounce();
243
294
 
244
295
  await worker.promise;
245
296
  }, {concurrency, stopOnError: false});
@@ -249,7 +300,7 @@ class Api extends Emittery {
249
300
  scheduler.storeFailedTestFiles(runStatus, this.options.cacheEnabled === false ? null : this._createCacheDir());
250
301
  } catch (error) {
251
302
  if (error && error.name === 'AggregateError') {
252
- for (const error_ of error) {
303
+ for (const error_ of error.errors) {
253
304
  runStatus.emitStateChange({type: 'internal-error', err: serializeError('Internal error', false, error_)});
254
305
  }
255
306
  } else {
@@ -257,7 +308,7 @@ class Api extends Emittery {
257
308
  }
258
309
  }
259
310
 
260
- restartTimer.cancel();
311
+ timeoutTrigger.discard();
261
312
  return runStatus;
262
313
  }
263
314
 
@@ -270,9 +321,9 @@ class Api extends Emittery {
270
321
  return this._cacheDir;
271
322
  }
272
323
 
273
- const cacheDir = this.options.cacheEnabled === false ?
274
- fs.mkdtempSync(`${tempDir}${path.sep}`) :
275
- this._getLocalCacheDir();
324
+ const cacheDir = this.options.cacheEnabled === false
325
+ ? fs.mkdtempSync(`${tempDir}${path.sep}`)
326
+ : this._getLocalCacheDir();
276
327
 
277
328
  // Ensure cacheDir exists
278
329
  fs.mkdirSync(cacheDir, {recursive: true});
@@ -282,5 +333,3 @@ class Api extends Emittery {
282
333
  return cacheDir;
283
334
  }
284
335
  }
285
-
286
- module.exports = Api;