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
@@ -1,15 +1,26 @@
1
- 'use strict';
2
- function parseTestArgs(args) {
3
- const rawTitle = typeof args[0] === 'string' ? args.shift() : undefined;
4
- const receivedImplementationArray = Array.isArray(args[0]);
5
- const implementations = receivedImplementationArray ? args.shift() : args.splice(0, 1);
1
+ const buildTitle = (raw, implementation, args) => {
2
+ let value = implementation && implementation.title ? implementation.title(raw, ...args) : raw;
3
+ const isValid = typeof value === 'string';
4
+ if (isValid) {
5
+ value = value.trim().replace(/\s+/g, ' ');
6
+ }
6
7
 
7
- const buildTitle = implementation => {
8
- const title = implementation.title ? implementation.title(rawTitle, ...args) : rawTitle;
9
- return {title, isSet: typeof title !== 'undefined', isValid: typeof title === 'string', isEmpty: !title};
8
+ return {
9
+ raw,
10
+ value,
11
+ isSet: value !== undefined,
12
+ isValid,
13
+ isEmpty: !isValid || value === '',
10
14
  };
15
+ };
11
16
 
12
- return {args, buildTitle, implementations, rawTitle, receivedImplementationArray};
13
- }
17
+ export default function parseTestArgs(args) {
18
+ const rawTitle = typeof args[0] === 'string' ? args.shift() : undefined;
19
+ const implementation = args.shift();
14
20
 
15
- module.exports = parseTestArgs;
21
+ return {
22
+ args,
23
+ implementation: implementation && implementation.exec ? implementation.exec : implementation,
24
+ title: buildTitle(rawTitle, implementation, args),
25
+ };
26
+ }
package/lib/pkg.cjs ADDED
@@ -0,0 +1,2 @@
1
+ 'use strict';
2
+ module.exports = require('../package.json');
@@ -1,20 +1,30 @@
1
- const {EventEmitter, on} = require('events');
2
- const v8 = require('v8');
3
- const {workerData, parentPort} = require('worker_threads');
4
- const pkg = require('../../package.json');
1
+ import {EventEmitter, on} from 'node:events';
2
+ import process from 'node:process';
3
+ import {workerData, parentPort, threadId} from 'node:worker_threads';
5
4
 
6
- // Used to forward messages received over the `parentPort`. Every subscription
7
- // adds a listener, so do not enforce any maximums.
5
+ import pkg from '../pkg.cjs';
6
+
7
+ // Used to forward messages received over the `parentPort` and any direct ports
8
+ // to test workers. Every subscription adds a listener, so do not enforce any
9
+ // maximums.
8
10
  const events = new EventEmitter().setMaxListeners(0);
11
+ const emitMessage = message => {
12
+ // Wait for a turn of the event loop, to allow new subscriptions to be
13
+ // set up in response to the previous message.
14
+ setImmediate(() => events.emit('message', message));
15
+ };
9
16
 
10
17
  // Map of active test workers, used in receiveMessages() to get a reference to
11
18
  // the TestWorker instance, and relevant release functions.
12
19
  const activeTestWorkers = new Map();
13
20
 
21
+ const internalMessagePort = Symbol('Internal MessagePort');
22
+
14
23
  class TestWorker {
15
- constructor(id, file) {
24
+ constructor(id, file, port) {
16
25
  this.id = id;
17
26
  this.file = file;
27
+ this[internalMessagePort] = port;
18
28
  }
19
29
 
20
30
  teardown(fn) {
@@ -47,10 +57,10 @@ class TestWorker {
47
57
  }
48
58
 
49
59
  class ReceivedMessage {
50
- constructor(testWorker, id, serializedData) {
60
+ constructor(testWorker, id, data) {
51
61
  this.testWorker = testWorker;
52
62
  this.id = id;
53
- this.data = v8.deserialize(new Uint8Array(serializedData));
63
+ this.data = data;
54
64
  }
55
65
 
56
66
  reply(data) {
@@ -98,7 +108,7 @@ async function * receiveMessages(fromTestWorker, replyTo) {
98
108
 
99
109
  let received = messageCache.get(message);
100
110
  if (received === undefined) {
101
- received = new ReceivedMessage(active.instance, message.messageId, message.serializedData);
111
+ received = new ReceivedMessage(active.instance, message.messageId, message.data);
102
112
  messageCache.set(message, received);
103
113
  }
104
114
 
@@ -107,59 +117,47 @@ async function * receiveMessages(fromTestWorker, replyTo) {
107
117
  }
108
118
 
109
119
  let messageCounter = 0;
110
- const messageIdPrefix = `${workerData.id}/message`;
120
+ const messageIdPrefix = `${threadId}/message`;
111
121
  const nextMessageId = () => `${messageIdPrefix}/${++messageCounter}`;
112
122
 
113
123
  function publishMessage(testWorker, data, replyTo) {
114
124
  const id = nextMessageId();
115
- parentPort.postMessage({
125
+ testWorker[internalMessagePort].postMessage({
116
126
  type: 'message',
117
127
  messageId: id,
118
- testWorkerId: testWorker.id,
119
- serializedData: [...v8.serialize(data)],
120
- replyTo
128
+ data,
129
+ replyTo,
121
130
  });
122
131
 
123
132
  return {
124
133
  id,
125
134
  async * replies() {
126
135
  yield * receiveMessages(testWorker, id);
127
- }
136
+ },
128
137
  };
129
138
  }
130
139
 
131
140
  function broadcastMessage(data) {
132
141
  const id = nextMessageId();
133
- parentPort.postMessage({
134
- type: 'broadcast',
135
- messageId: id,
136
- serializedData: [...v8.serialize(data)]
137
- });
142
+ for (const trackedWorker of activeTestWorkers.values()) {
143
+ trackedWorker.instance[internalMessagePort].postMessage({
144
+ type: 'message',
145
+ messageId: id,
146
+ data,
147
+ });
148
+ }
138
149
 
139
150
  return {
140
151
  id,
141
152
  async * replies() {
142
153
  yield * receiveMessages(undefined, id);
143
- }
154
+ },
144
155
  };
145
156
  }
146
157
 
147
158
  async function loadFactory() {
148
- try {
149
- const mod = require(workerData.filename);
150
- if (typeof mod === 'function') {
151
- return mod;
152
- }
153
-
154
- return mod.default;
155
- } catch (error) {
156
- if (error && (error.code === 'ERR_REQUIRE_ESM' || (error.code === 'MODULE_NOT_FOUND' && workerData.filename.startsWith('file://')))) {
157
- const {default: factory} = await import(workerData.filename); // eslint-disable-line node/no-unsupported-features/es-syntax
158
- return factory;
159
- }
160
-
161
- throw error;
162
- }
159
+ const {default: factory} = await import(workerData.filename); // eslint-disable-line node/no-unsupported-features/es-syntax
160
+ return factory;
163
161
  }
164
162
 
165
163
  let signalAvailable = () => {
@@ -175,7 +173,7 @@ loadFactory(workerData.filename).then(factory => {
175
173
 
176
174
  factory({
177
175
  negotiateProtocol(supported) {
178
- if (!supported.includes('experimental')) {
176
+ if (!supported.includes('ava-4')) {
179
177
  fatal = new Error(`This version of AVA (${pkg.version}) is not compatible with shared worker plugin at ${workerData.filename}`);
180
178
  throw fatal;
181
179
  }
@@ -184,12 +182,13 @@ loadFactory(workerData.filename).then(factory => {
184
182
 
185
183
  parentPort.on('message', async message => {
186
184
  if (message.type === 'register-test-worker') {
187
- const {id, file} = message;
188
- const instance = new TestWorker(id, file);
185
+ const {id, file, port} = message;
186
+ const instance = new TestWorker(id, file, port);
189
187
 
190
188
  activeTestWorkers.set(id, {instance, teardownFns: new Set()});
191
189
 
192
190
  produceTestWorker(instance);
191
+ port.on('message', message => emitMessage({testWorkerId: id, ...message}));
193
192
  }
194
193
 
195
194
  if (message.type === 'deregister-test-worker') {
@@ -205,18 +204,16 @@ loadFactory(workerData.filename).then(factory => {
205
204
 
206
205
  parentPort.postMessage({
207
206
  type: 'deregistered-test-worker',
208
- id
207
+ id,
209
208
  });
210
- }
211
209
 
212
- // Wait for a turn of the event loop, to allow new subscriptions to be
213
- // set up in response to the previous message.
214
- setImmediate(() => events.emit('message', message));
210
+ emitMessage(message);
211
+ }
215
212
  });
216
213
 
217
214
  return {
218
215
  initialData: workerData.initialData,
219
- protocol: 'experimental',
216
+ protocol: 'ava-4',
220
217
 
221
218
  ready() {
222
219
  signalAvailable();
@@ -235,9 +232,9 @@ loadFactory(workerData.filename).then(factory => {
235
232
  for await (const [worker] of on(events, 'testWorker')) {
236
233
  yield worker;
237
234
  }
238
- }
235
+ },
239
236
  };
240
- }
237
+ },
241
238
  });
242
239
  }).catch(error => {
243
240
  if (fatal === undefined) {
@@ -1,11 +1,11 @@
1
- const events = require('events');
2
- const {Worker} = require('worker_threads');
1
+ import events from 'node:events';
2
+ import {pathToFileURL} from 'node:url';
3
+ import {Worker} from 'node:worker_threads';
3
4
 
4
- const serializeError = require('../serialize-error');
5
+ import serializeError from '../serialize-error.js';
5
6
 
6
- const LOADER = require.resolve('./shared-worker-loader');
7
+ const LOADER = new URL('shared-worker-loader.js', import.meta.url);
7
8
 
8
- let sharedWorkerCounter = 0;
9
9
  const launchedWorkers = new Map();
10
10
 
11
11
  const waitForAvailable = async worker => {
@@ -16,30 +16,28 @@ const waitForAvailable = async worker => {
16
16
  }
17
17
  };
18
18
 
19
- function launchWorker({filename, initialData}) {
19
+ function launchWorker(filename, initialData) {
20
20
  if (launchedWorkers.has(filename)) {
21
21
  return launchedWorkers.get(filename);
22
22
  }
23
23
 
24
- const id = `shared-worker/${++sharedWorkerCounter}`;
25
24
  const worker = new Worker(LOADER, {
26
25
  // Ensure the worker crashes for unhandled rejections, rather than allowing undefined behavior.
27
26
  execArgv: ['--unhandled-rejections=strict'],
28
27
  workerData: {
29
28
  filename,
30
- id,
31
- initialData
32
- }
29
+ initialData,
30
+ },
33
31
  });
34
32
  worker.setMaxListeners(0);
35
33
 
36
34
  const launched = {
37
35
  statePromises: {
38
36
  available: waitForAvailable(worker),
39
- error: events.once(worker, 'error').then(([error]) => error) // eslint-disable-line promise/prefer-await-to-then
37
+ error: events.once(worker, 'error').then(([error]) => error),
40
38
  },
41
39
  exited: false,
42
- worker
40
+ worker,
43
41
  };
44
42
 
45
43
  launchedWorkers.set(filename, launched);
@@ -50,7 +48,7 @@ function launchWorker({filename, initialData}) {
50
48
  return launched;
51
49
  }
52
50
 
53
- async function observeWorkerProcess(fork, runStatus) {
51
+ export async function observeWorkerProcess(fork, runStatus) {
54
52
  let registrationCount = 0;
55
53
  let signalDeregistered;
56
54
  const deregistered = new Promise(resolve => {
@@ -63,26 +61,11 @@ async function observeWorkerProcess(fork, runStatus) {
63
61
  }
64
62
  });
65
63
 
66
- fork.onConnectSharedWorker(async channel => {
67
- const launched = launchWorker(channel);
68
-
69
- const handleChannelMessage = ({messageId, replyTo, serializedData}) => {
70
- launched.worker.postMessage({
71
- type: 'message',
72
- testWorkerId: fork.forkId,
73
- messageId,
74
- replyTo,
75
- serializedData
76
- });
77
- };
64
+ fork.onConnectSharedWorker(async ({filename, initialData, port, signalError}) => {
65
+ const launched = launchWorker(filename, initialData);
78
66
 
79
67
  const handleWorkerMessage = async message => {
80
- if (message.type === 'broadcast' || (message.type === 'message' && message.testWorkerId === fork.forkId)) {
81
- const {messageId, replyTo, serializedData} = message;
82
- channel.forwardMessageToFork({messageId, replyTo, serializedData});
83
- }
84
-
85
- if (message.type === 'deregistered-test-worker' && message.id === fork.forkId) {
68
+ if (message.type === 'deregistered-test-worker' && message.id === fork.threadId) {
86
69
  launched.worker.off('message', handleWorkerMessage);
87
70
 
88
71
  registrationCount--;
@@ -92,35 +75,35 @@ async function observeWorkerProcess(fork, runStatus) {
92
75
  }
93
76
  };
94
77
 
95
- launched.statePromises.error.then(error => { // eslint-disable-line promise/prefer-await-to-then
78
+ launched.statePromises.error.then(error => {
96
79
  signalDeregistered();
97
80
  launched.worker.off('message', handleWorkerMessage);
98
81
  runStatus.emitStateChange({type: 'shared-worker-error', err: serializeError('Shared worker error', true, error)});
99
- channel.signalError();
82
+ signalError();
100
83
  });
101
84
 
102
85
  try {
103
86
  await launched.statePromises.available;
104
87
 
105
88
  registrationCount++;
89
+
90
+ port.postMessage({type: 'ready'});
91
+
106
92
  launched.worker.postMessage({
107
93
  type: 'register-test-worker',
108
- id: fork.forkId,
109
- file: fork.file
110
- });
94
+ id: fork.threadId,
95
+ file: pathToFileURL(fork.file).toString(),
96
+ port,
97
+ }, [port]);
111
98
 
112
99
  fork.promise.finally(() => {
113
100
  launched.worker.postMessage({
114
101
  type: 'deregister-test-worker',
115
- id: fork.forkId
102
+ id: fork.threadId,
116
103
  });
117
-
118
- channel.off('message', handleChannelMessage);
119
104
  });
120
105
 
121
106
  launched.worker.on('message', handleWorkerMessage);
122
- channel.on('message', handleChannelMessage);
123
- channel.signalReady();
124
107
  } catch {
125
108
  return;
126
109
  } finally {
@@ -133,5 +116,3 @@ async function observeWorkerProcess(fork, runStatus) {
133
116
 
134
117
  return deregistered;
135
118
  }
136
-
137
- exports.observeWorkerProcess = observeWorkerProcess;
@@ -1,21 +1,21 @@
1
- const pkg = require('../package.json');
2
- const globs = require('./globs');
1
+ import * as globs from './globs.js';
2
+ import pkg from './pkg.cjs';
3
3
 
4
4
  const levels = {
5
- ava3: 1,
6
- pathRewrites: 2
5
+ // As the protocol changes, comparing levels by integer allows AVA to be
6
+ // compatible with different versions. Currently there is only one supported
7
+ // version, so this is effectively unused. The infrastructure is retained for
8
+ // future use.
9
+ levelIntegersAreCurrentlyUnused: 0,
7
10
  };
8
11
 
9
- exports.levels = levels;
10
-
11
12
  const levelsByProtocol = {
12
- 'ava-3': levels.ava3,
13
- 'ava-3.2': levels.pathRewrites
13
+ 'ava-3.2': levels.levelIntegersAreCurrentlyUnused,
14
14
  };
15
15
 
16
- function load(providerModule, projectDir) {
16
+ async function load(providerModule, projectDir) {
17
17
  const ava = {version: pkg.version};
18
- const makeProvider = require(providerModule);
18
+ const {default: makeProvider} = await import(providerModule); // eslint-disable-line node/no-unsupported-features/es-syntax
19
19
 
20
20
  let fatal;
21
21
  let level;
@@ -37,9 +37,9 @@ function load(providerModule, projectDir) {
37
37
  },
38
38
  identifier,
39
39
  normalizeGlobPatterns: globs.normalizePatterns,
40
- projectDir
40
+ projectDir,
41
41
  };
42
- }
42
+ },
43
43
  });
44
44
 
45
45
  if (fatal) {
@@ -49,5 +49,11 @@ function load(providerModule, projectDir) {
49
49
  return {...provider, level};
50
50
  }
51
51
 
52
- exports.babel = projectDir => load('@ava/babel', projectDir);
53
- exports.typescript = projectDir => load('@ava/typescript', projectDir);
52
+ const providerManager = {
53
+ levels,
54
+ async typescript(projectDir) {
55
+ return load('@ava/typescript', projectDir);
56
+ },
57
+ };
58
+
59
+ export default providerManager;
@@ -1,15 +1,10 @@
1
- 'use strict';
2
- const StackUtils = require('stack-utils');
1
+ import StackUtils from 'stack-utils';
3
2
 
4
3
  const stackUtils = new StackUtils({
5
4
  ignoredPackages: [
6
- '@ava/babel',
7
- '@ava/require-precompiled',
8
5
  '@ava/typescript',
9
- 'append-transform',
10
6
  'ava',
11
- 'empower-core',
12
- 'nyc'
7
+ 'nyc',
13
8
  ],
14
9
  internals: [
15
10
  // AVA internals, which ignoredPackages don't ignore when we run our own unit tests.
@@ -19,8 +14,8 @@ const stackUtils = new StackUtils({
19
14
  /\(internal\/process\/task_queues\.js:\d+:\d+\)$/,
20
15
  /\(internal\/modules\/cjs\/.+?\.js:\d+:\d+\)$/,
21
16
  /async Promise\.all \(index/,
22
- /new Promise \(<anonymous>\)/
23
- ]
17
+ /new Promise \(<anonymous>\)/,
18
+ ],
24
19
  });
25
20
 
26
21
  /*
@@ -59,7 +54,7 @@ const stackUtils = new StackUtils({
59
54
  * Module.runMain (module.js:604:10)
60
55
  * ```
61
56
  */
62
- module.exports = stack => {
57
+ export default function beautifyStack(stack) {
63
58
  if (!stack) {
64
59
  return [];
65
60
  }
@@ -69,4 +64,4 @@ module.exports = stack => {
69
64
  .split('\n')
70
65
  .map(line => line.trim())
71
66
  .filter(line => line !== '');
72
- };
67
+ }
@@ -1,17 +1,42 @@
1
- 'use strict';
2
- const chalk = require('../chalk').get();
1
+ import {chalk} from '../chalk.js';
3
2
 
4
- module.exports = {
5
- log: chalk.gray,
6
- title: chalk.bold,
7
- error: chalk.red,
8
- skip: chalk.yellow,
9
- todo: chalk.blue,
10
- pass: chalk.green,
11
- duration: chalk.gray.dim,
12
- errorSource: chalk.gray,
13
- errorStack: chalk.gray,
14
- errorStackInternal: chalk.gray.dim,
15
- stack: chalk.red,
16
- information: chalk.magenta
3
+ const colors = {
4
+ get log() {
5
+ return chalk.gray;
6
+ },
7
+ get title() {
8
+ return chalk.bold;
9
+ },
10
+ get error() {
11
+ return chalk.red;
12
+ },
13
+ get skip() {
14
+ return chalk.yellow;
15
+ },
16
+ get todo() {
17
+ return chalk.blue;
18
+ },
19
+ get pass() {
20
+ return chalk.green;
21
+ },
22
+ get duration() {
23
+ return chalk.gray.dim;
24
+ },
25
+ get errorSource() {
26
+ return chalk.gray;
27
+ },
28
+ get errorStack() {
29
+ return chalk.gray;
30
+ },
31
+ get errorStackInternal() {
32
+ return chalk.gray.dim;
33
+ },
34
+ get stack() {
35
+ return chalk.red;
36
+ },
37
+ get information() {
38
+ return chalk.magenta;
39
+ },
17
40
  };
41
+
42
+ export default colors;