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
@@ -1,37 +1,32 @@
1
- 'use strict';
2
- const util = require('util'); // eslint-disable-line unicorn/import-style
3
- const ansiStyles = require('ansi-styles');
4
- const stripAnsi = require('strip-ansi');
5
- const cloneDeepWith = require('lodash/cloneDeepWith');
6
- const reactPlugin = require('@concordance/react');
7
- const chalk = require('./chalk').get();
1
+ import {inspect} from 'node:util';
8
2
 
9
- // Wrap Concordance's React plugin. Change the name to avoid collisions if in
10
- // the future users can register plugins themselves.
11
- const avaReactPlugin = {...reactPlugin, name: 'ava-plugin-react'};
12
- const plugins = [avaReactPlugin];
3
+ import ansiStyles from 'ansi-styles';
4
+ import {Chalk} from 'chalk'; // eslint-disable-line unicorn/import-style
5
+ import stripAnsi from 'strip-ansi';
13
6
 
14
- const forceColor = new chalk.Instance({level: Math.max(chalk.level, 1)});
7
+ import {chalk} from './chalk.js';
8
+
9
+ const forceColor = new Chalk({level: Math.max(chalk.level, 1)});
15
10
 
16
11
  const colorTheme = {
17
12
  boolean: ansiStyles.yellow,
18
13
  circular: forceColor.grey('[Circular]'),
19
14
  date: {
20
15
  invalid: forceColor.red('invalid'),
21
- value: ansiStyles.blue
16
+ value: ansiStyles.blue,
22
17
  },
23
18
  diffGutters: {
24
19
  actual: forceColor.red('-') + ' ',
25
20
  expected: forceColor.green('+') + ' ',
26
- padding: ' '
21
+ padding: ' ',
27
22
  },
28
23
  error: {
29
24
  ctor: {open: ansiStyles.grey.open + '(', close: ')' + ansiStyles.grey.close},
30
- name: ansiStyles.magenta
25
+ name: ansiStyles.magenta,
31
26
  },
32
27
  function: {
33
28
  name: ansiStyles.blue,
34
- stringTag: ansiStyles.magenta
29
+ stringTag: ansiStyles.magenta,
35
30
  },
36
31
  global: ansiStyles.magenta,
37
32
  item: {after: forceColor.grey(',')},
@@ -45,44 +40,16 @@ const colorTheme = {
45
40
  closeBracket: forceColor.grey('}'),
46
41
  ctor: ansiStyles.magenta,
47
42
  stringTag: {open: ansiStyles.magenta.open + '@', close: ansiStyles.magenta.close},
48
- secondaryStringTag: {open: ansiStyles.grey.open + '@', close: ansiStyles.grey.close}
43
+ secondaryStringTag: {open: ansiStyles.grey.open + '@', close: ansiStyles.grey.close},
49
44
  },
50
45
  property: {
51
46
  after: forceColor.grey(','),
52
47
  keyBracket: {open: forceColor.grey('['), close: forceColor.grey(']')},
53
- valueFallback: forceColor.grey('…')
54
- },
55
- react: {
56
- functionType: forceColor.grey('\u235F'),
57
- openTag: {
58
- start: forceColor.grey('<'),
59
- end: forceColor.grey('>'),
60
- selfClose: forceColor.grey('/'),
61
- selfCloseVoid: ' ' + forceColor.grey('/')
62
- },
63
- closeTag: {
64
- open: forceColor.grey('</'),
65
- close: forceColor.grey('>')
66
- },
67
- tagName: ansiStyles.magenta,
68
- attribute: {
69
- separator: '=',
70
- value: {
71
- openBracket: forceColor.grey('{'),
72
- closeBracket: forceColor.grey('}'),
73
- string: {
74
- line: {open: forceColor.blue('"'), close: forceColor.blue('"'), escapeQuote: '"'}
75
- }
76
- }
77
- },
78
- child: {
79
- openBracket: forceColor.grey('{'),
80
- closeBracket: forceColor.grey('}')
81
- }
48
+ valueFallback: forceColor.grey('…'),
82
49
  },
83
50
  regexp: {
84
51
  source: {open: ansiStyles.blue.open + '/', close: '/' + ansiStyles.blue.close},
85
- flags: ansiStyles.yellow
52
+ flags: ansiStyles.yellow,
86
53
  },
87
54
  stats: {separator: forceColor.grey('---')},
88
55
  string: {
@@ -94,45 +61,42 @@ const colorTheme = {
94
61
  diff: {
95
62
  insert: {
96
63
  open: ansiStyles.bgGreen.open + ansiStyles.black.open,
97
- close: ansiStyles.black.close + ansiStyles.bgGreen.close
64
+ close: ansiStyles.black.close + ansiStyles.bgGreen.close,
98
65
  },
99
66
  delete: {
100
67
  open: ansiStyles.bgRed.open + ansiStyles.black.open,
101
- close: ansiStyles.black.close + ansiStyles.bgRed.close
68
+ close: ansiStyles.black.close + ansiStyles.bgRed.close,
102
69
  },
103
70
  equal: ansiStyles.blue,
104
71
  insertLine: {
105
72
  open: ansiStyles.green.open,
106
- close: ansiStyles.green.close
73
+ close: ansiStyles.green.close,
107
74
  },
108
75
  deleteLine: {
109
76
  open: ansiStyles.red.open,
110
- close: ansiStyles.red.close
111
- }
112
- }
77
+ close: ansiStyles.red.close,
78
+ },
79
+ },
113
80
  },
114
81
  symbol: ansiStyles.yellow,
115
82
  typedArray: {
116
- bytes: ansiStyles.yellow
83
+ bytes: ansiStyles.yellow,
117
84
  },
118
- undefined: ansiStyles.yellow
85
+ undefined: ansiStyles.yellow,
119
86
  };
120
87
 
121
- const plainTheme = cloneDeepWith(colorTheme, value => {
122
- if (typeof value === 'string') {
123
- return stripAnsi(value);
124
- }
125
- });
88
+ const plainTheme = JSON.parse(JSON.stringify(colorTheme), value => typeof value === 'string' ? stripAnsi(value) : value);
126
89
 
127
90
  const theme = chalk.level > 0 ? colorTheme : plainTheme;
128
91
 
129
- exports.default = {
92
+ const concordanceOptions = {
130
93
  // Use Node's object inspection depth, clamped to a minimum of 3
131
94
  get maxDepth() {
132
- return Math.max(3, util.inspect.defaultOptions.depth);
95
+ return Math.max(3, inspect.defaultOptions.depth);
133
96
  },
134
- plugins,
135
- theme
97
+ theme,
136
98
  };
137
99
 
138
- exports.snapshotManager = {plugins, theme: plainTheme};
100
+ export default concordanceOptions;
101
+
102
+ export const snapshotManager = {theme: plainTheme};
@@ -1,7 +1,4 @@
1
- 'use strict';
2
- const clone = require('lodash/clone');
3
-
4
- class ContextRef {
1
+ export default class ContextRef {
5
2
  constructor() {
6
3
  this.value = {};
7
4
  }
@@ -18,7 +15,6 @@ class ContextRef {
18
15
  return new LateBinding(this);
19
16
  }
20
17
  }
21
- module.exports = ContextRef;
22
18
 
23
19
  class LateBinding extends ContextRef {
24
20
  constructor(ref) {
@@ -29,7 +25,8 @@ class LateBinding extends ContextRef {
29
25
 
30
26
  get() {
31
27
  if (!this.bound) {
32
- this.set(clone(this.ref.get()));
28
+ const value = this.ref.get();
29
+ this.set(value !== null && typeof value === 'object' ? {...value} : value);
33
30
  }
34
31
 
35
32
  return super.get();
@@ -1,4 +1,3 @@
1
- 'use strict';
2
1
  const chainRegistry = new WeakMap();
3
2
 
4
3
  function startChain(name, call, defaults) {
@@ -48,20 +47,16 @@ function createHookChain(hook, isAfterHook) {
48
47
  // * `skip` must come at the end
49
48
  // * no `only`
50
49
  // * no repeating
51
- extendChain(hook, 'cb', 'callback');
52
50
  extendChain(hook, 'skip', 'skipped');
53
- extendChain(hook.cb, 'skip', 'skipped');
54
51
  if (isAfterHook) {
55
52
  extendChain(hook, 'always');
56
- extendChain(hook.always, 'cb', 'callback');
57
53
  extendChain(hook.always, 'skip', 'skipped');
58
- extendChain(hook.always.cb, 'skip', 'skipped');
59
54
  }
60
55
 
61
56
  return hook;
62
57
  }
63
58
 
64
- function createChain(fn, defaults, meta) {
59
+ export default function createChain(fn, defaults, meta) {
65
60
  // Test chaining rules:
66
61
  // * `serial` must come at the start
67
62
  // * `only` and `skip` must come at the end
@@ -69,27 +64,15 @@ function createChain(fn, defaults, meta) {
69
64
  // * `only` and `skip` cannot be chained together
70
65
  // * no repeating
71
66
  const root = startChain('test', fn, {...defaults, type: 'test'});
72
- extendChain(root, 'cb', 'callback');
73
67
  extendChain(root, 'failing');
74
68
  extendChain(root, 'only', 'exclusive');
75
69
  extendChain(root, 'serial');
76
70
  extendChain(root, 'skip', 'skipped');
77
- extendChain(root.cb, 'failing');
78
- extendChain(root.cb, 'only', 'exclusive');
79
- extendChain(root.cb, 'skip', 'skipped');
80
- extendChain(root.cb.failing, 'only', 'exclusive');
81
- extendChain(root.cb.failing, 'skip', 'skipped');
82
71
  extendChain(root.failing, 'only', 'exclusive');
83
72
  extendChain(root.failing, 'skip', 'skipped');
84
- extendChain(root.serial, 'cb', 'callback');
85
73
  extendChain(root.serial, 'failing');
86
74
  extendChain(root.serial, 'only', 'exclusive');
87
75
  extendChain(root.serial, 'skip', 'skipped');
88
- extendChain(root.serial.cb, 'failing');
89
- extendChain(root.serial.cb, 'only', 'exclusive');
90
- extendChain(root.serial.cb, 'skip', 'skipped');
91
- extendChain(root.serial.cb.failing, 'only', 'exclusive');
92
- extendChain(root.serial.cb.failing, 'skip', 'skipped');
93
76
  extendChain(root.serial.failing, 'only', 'exclusive');
94
77
  extendChain(root.serial.failing, 'skip', 'skipped');
95
78
 
@@ -108,9 +91,38 @@ function createChain(fn, defaults, meta) {
108
91
  root.todo = startChain('test.todo', fn, {...defaults, type: 'test', todo: true});
109
92
  root.serial.todo = startChain('test.serial.todo', fn, {...defaults, serial: true, type: 'test', todo: true});
110
93
 
94
+ root.macro = options => {
95
+ if (typeof options === 'function') {
96
+ return Object.freeze({exec: options});
97
+ }
98
+
99
+ return Object.freeze({exec: options.exec, title: options.title});
100
+ };
101
+
111
102
  root.meta = meta;
112
103
 
104
+ // Our type definition uses ESM syntax; when using CJS with VSCode, the
105
+ // auto-completion assumes the root is accessed through `require('ava').default`.
106
+ // Placate VSCode by adding a mostly hidden default property on the root.
107
+ // This is available through both CJS and ESM imports. We use a proxy so that
108
+ // we don't end up with root.default.default.default chains.
109
+ Object.defineProperty(root, 'default', {
110
+ configurable: false,
111
+ enumerable: false,
112
+ writable: false,
113
+ value: new Proxy(root, {
114
+ apply(target, thisArg, argumentsList) {
115
+ target.apply(thisArg, argumentsList);
116
+ },
117
+ get(target, prop) {
118
+ if (prop === 'default') {
119
+ throw new TypeError('Cannot access default.default');
120
+ }
121
+
122
+ return target[prop];
123
+ },
124
+ }),
125
+ });
126
+
113
127
  return root;
114
128
  }
115
-
116
- module.exports = createChain;
@@ -1,5 +1,4 @@
1
- 'use strict';
2
- function validateEnvironmentVariables(environmentVariables) {
1
+ export default function validateEnvironmentVariables(environmentVariables) {
3
2
  if (!environmentVariables) {
4
3
  return {};
5
4
  }
@@ -12,5 +11,3 @@ function validateEnvironmentVariables(environmentVariables) {
12
11
 
13
12
  return environmentVariables;
14
13
  }
15
-
16
- module.exports = validateEnvironmentVariables;
@@ -0,0 +1,73 @@
1
+ import v8 from 'node:v8';
2
+ import {parentPort, workerData} from 'node:worker_threads';
3
+
4
+ import normalizeExtensions from './extensions.js';
5
+ import {normalizeGlobs} from './globs.js';
6
+ import {loadConfig} from './load-config.js';
7
+ import providerManager from './provider-manager.js';
8
+
9
+ const MAX_DATA_LENGTH_EXCLUSIVE = 100 * 1024; // Allocate 100 KiB to exchange globs.
10
+
11
+ const configCache = new Map();
12
+
13
+ const collectProviders = async ({conf, projectDir}) => {
14
+ const providers = [];
15
+ if (Reflect.has(conf, 'typescript')) {
16
+ const {level, main} = await providerManager.typescript(projectDir);
17
+ providers.push({
18
+ level,
19
+ main: main({config: conf.typescript}),
20
+ type: 'typescript',
21
+ });
22
+ }
23
+
24
+ return providers;
25
+ };
26
+
27
+ const buildGlobs = ({conf, providers, projectDir, overrideExtensions, overrideFiles}) => {
28
+ const extensions = overrideExtensions
29
+ ? normalizeExtensions(overrideExtensions)
30
+ : normalizeExtensions(conf.extensions, providers);
31
+
32
+ return {
33
+ cwd: projectDir,
34
+ ...normalizeGlobs({
35
+ extensions,
36
+ files: overrideFiles ? overrideFiles : conf.files,
37
+ providers,
38
+ }),
39
+ };
40
+ };
41
+
42
+ const resolveGlobs = async (projectDir, overrideExtensions, overrideFiles) => {
43
+ if (!configCache.has(projectDir)) {
44
+ configCache.set(projectDir, loadConfig({resolveFrom: projectDir}).then(async conf => {
45
+ const providers = await collectProviders({conf, projectDir});
46
+ return {conf, providers};
47
+ }));
48
+ }
49
+
50
+ const {conf, providers} = await configCache.get(projectDir);
51
+ return buildGlobs({conf, providers, projectDir, overrideExtensions, overrideFiles});
52
+ };
53
+
54
+ const data = new Uint8Array(workerData.dataBuffer);
55
+ const sync = new Int32Array(workerData.syncBuffer);
56
+
57
+ const handleMessage = async ({projectDir, overrideExtensions, overrideFiles}) => {
58
+ let encoded;
59
+ try {
60
+ const globs = await resolveGlobs(projectDir, overrideExtensions, overrideFiles);
61
+ encoded = v8.serialize(globs);
62
+ } catch (error) {
63
+ encoded = v8.serialize(error);
64
+ }
65
+
66
+ const byteLength = encoded.length < MAX_DATA_LENGTH_EXCLUSIVE ? encoded.copy(data) : MAX_DATA_LENGTH_EXCLUSIVE;
67
+ Atomics.store(sync, 0, byteLength);
68
+ Atomics.notify(sync, 0);
69
+ };
70
+
71
+ parentPort.on('message', handleMessage);
72
+ handleMessage(workerData.firstMessage);
73
+ delete workerData.firstMessage;
package/lib/extensions.js CHANGED
@@ -1,4 +1,4 @@
1
- module.exports = (configuredExtensions, providers = []) => {
1
+ export default function resolveExtensions(configuredExtensions, providers = []) {
2
2
  // Combine all extensions possible for testing. Remove duplicate extensions.
3
3
  const duplicates = new Set();
4
4
  const seen = new Set();
@@ -43,4 +43,4 @@ module.exports = (configuredExtensions, providers = []) => {
43
43
  }
44
44
 
45
45
  return [...seen];
46
- };
46
+ }
package/lib/fork.js CHANGED
@@ -1,68 +1,71 @@
1
- 'use strict';
2
- const childProcess = require('child_process');
3
- const path = require('path');
4
- const fs = require('fs');
5
- const Emittery = require('emittery');
6
- const {controlFlow} = require('./ipc-flow-control');
7
-
8
- if (fs.realpathSync(__filename) !== __filename) {
9
- console.warn('WARNING: `npm link ava` and the `--preserve-symlink` flag are incompatible. We have detected that AVA is linked via `npm link`, and that you are using either an early version of Node 6, or the `--preserve-symlink` flag. This breaks AVA. You should upgrade to Node 6.2.0+, avoid the `--preserve-symlink` flag, or avoid using `npm link ava`.');
10
- }
11
-
12
- // In case the test file imports a different AVA install,
13
- // the presence of this variable allows it to require this one instead
14
- const AVA_PATH = path.resolve(__dirname, '..');
15
- const WORKER_PATH = require.resolve('./worker/subprocess');
1
+ import childProcess from 'node:child_process';
2
+ import process from 'node:process';
3
+ import {fileURLToPath} from 'node:url';
4
+ import {Worker} from 'node:worker_threads';
16
5
 
17
- class SharedWorkerChannel extends Emittery {
18
- constructor({channelId, filename, initialData}, sendToFork) {
19
- super();
6
+ import Emittery from 'emittery';
7
+ import {pEvent} from 'p-event';
20
8
 
21
- this.id = channelId;
22
- this.filename = filename;
23
- this.initialData = initialData;
24
- this.sendToFork = sendToFork;
25
- }
9
+ import {controlFlow} from './ipc-flow-control.cjs';
10
+ import serializeError from './serialize-error.js';
26
11
 
27
- signalReady() {
28
- this.sendToFork({
29
- type: 'shared-worker-ready',
30
- channelId: this.id
31
- });
32
- }
33
-
34
- signalError() {
35
- this.sendToFork({
36
- type: 'shared-worker-error',
37
- channelId: this.id
38
- });
39
- }
12
+ let workerPath = new URL('worker/base.js', import.meta.url);
13
+ export function _testOnlyReplaceWorkerPath(replacement) {
14
+ workerPath = replacement;
15
+ }
40
16
 
41
- emitMessage({messageId, replyTo, serializedData}) {
42
- this.emit('message', {
43
- messageId,
44
- replyTo,
45
- serializedData
17
+ const additionalExecArgv = ['--enable-source-maps'];
18
+
19
+ const createWorker = (options, execArgv) => {
20
+ let worker;
21
+ let postMessage;
22
+ let close;
23
+ if (options.workerThreads) {
24
+ worker = new Worker(workerPath, {
25
+ argv: options.workerArgv,
26
+ env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables},
27
+ execArgv: [...execArgv, ...additionalExecArgv],
28
+ workerData: {
29
+ options,
30
+ },
31
+ trackUnmanagedFds: true,
32
+ stdin: true,
33
+ stdout: true,
34
+ stderr: true,
46
35
  });
47
- }
48
-
49
- forwardMessageToFork({messageId, replyTo, serializedData}) {
50
- this.sendToFork({
51
- type: 'shared-worker-message',
52
- channelId: this.id,
53
- messageId,
54
- replyTo,
55
- serializedData
36
+ postMessage = worker.postMessage.bind(worker);
37
+
38
+ // Ensure we've seen this event before we terminate the worker thread, as a
39
+ // workaround for https://github.com/nodejs/node/issues/38418.
40
+ const starting = pEvent(worker, 'message', ({ava}) => ava && ava.type === 'starting');
41
+
42
+ close = async () => {
43
+ try {
44
+ await starting;
45
+ await worker.terminate();
46
+ } finally {
47
+ // No-op
48
+ }
49
+ };
50
+ } else {
51
+ worker = childProcess.fork(fileURLToPath(workerPath), options.workerArgv, {
52
+ cwd: options.projectDir,
53
+ silent: true,
54
+ env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables},
55
+ execArgv: [...execArgv, ...additionalExecArgv],
56
56
  });
57
+ postMessage = controlFlow(worker);
58
+ close = async () => worker.kill();
57
59
  }
58
- }
59
-
60
- let forkCounter = 0;
61
60
 
62
- module.exports = (file, options, execArgv = process.execArgv) => {
63
- const forkId = `fork/${++forkCounter}`;
64
- const sharedWorkerChannels = new Map();
61
+ return {
62
+ worker,
63
+ postMessage,
64
+ close,
65
+ };
66
+ };
65
67
 
68
+ export default function loadFork(file, options, execArgv = process.execArgv) {
66
69
  let finished = false;
67
70
 
68
71
  const emitter = new Emittery();
@@ -75,31 +78,22 @@ module.exports = (file, options, execArgv = process.execArgv) => {
75
78
  options = {
76
79
  baseDir: process.cwd(),
77
80
  file,
78
- forkId,
79
- ...options
81
+ ...options,
80
82
  };
81
83
 
82
- const subprocess = childProcess.fork(WORKER_PATH, options.workerArgv, {
83
- cwd: options.projectDir,
84
- silent: true,
85
- env: {NODE_ENV: 'test', ...process.env, ...options.environmentVariables, AVA_PATH},
86
- execArgv
87
- });
88
-
89
- subprocess.stdout.on('data', chunk => {
84
+ const {worker, postMessage, close} = createWorker(options, execArgv);
85
+ worker.stdout.on('data', chunk => {
90
86
  emitStateChange({type: 'worker-stdout', chunk});
91
87
  });
92
88
 
93
- subprocess.stderr.on('data', chunk => {
89
+ worker.stderr.on('data', chunk => {
94
90
  emitStateChange({type: 'worker-stderr', chunk});
95
91
  });
96
92
 
97
- const bufferedSend = controlFlow(subprocess);
98
-
99
93
  let forcedExit = false;
100
94
  const send = evt => {
101
95
  if (!finished && !forcedExit) {
102
- bufferedSend({ava: evt});
96
+ postMessage({ava: evt});
103
97
  }
104
98
  };
105
99
 
@@ -109,7 +103,7 @@ module.exports = (file, options, execArgv = process.execArgv) => {
109
103
  resolve();
110
104
  };
111
105
 
112
- subprocess.on('message', message => {
106
+ worker.on('message', message => {
113
107
  if (!message.ava) {
114
108
  return;
115
109
  }
@@ -119,15 +113,18 @@ module.exports = (file, options, execArgv = process.execArgv) => {
119
113
  send({type: 'options', options});
120
114
  break;
121
115
  case 'shared-worker-connect': {
122
- const channel = new SharedWorkerChannel(message.ava, send);
123
- sharedWorkerChannels.set(channel.id, channel);
124
- emitter.emit('connectSharedWorker', channel);
116
+ const {channelId, filename, initialData, port} = message.ava;
117
+ emitter.emit('connectSharedWorker', {
118
+ filename,
119
+ initialData,
120
+ port,
121
+ signalError() {
122
+ send({type: 'shared-worker-error', channelId});
123
+ },
124
+ });
125
125
  break;
126
126
  }
127
127
 
128
- case 'shared-worker-message':
129
- sharedWorkerChannels.get(message.ava.channelId).emitMessage(message.ava);
130
- break;
131
128
  case 'ping':
132
129
  send({type: 'pong'});
133
130
  break;
@@ -136,12 +133,12 @@ module.exports = (file, options, execArgv = process.execArgv) => {
136
133
  }
137
134
  });
138
135
 
139
- subprocess.on('error', err => {
140
- emitStateChange({type: 'worker-failed', err});
136
+ worker.on('error', error => {
137
+ emitStateChange({type: 'worker-failed', err: serializeError('Worker error', false, error, file)});
141
138
  finish();
142
139
  });
143
140
 
144
- subprocess.on('exit', (code, signal) => {
141
+ worker.on('exit', (code, signal) => {
145
142
  if (forcedExit) {
146
143
  emitStateChange({type: 'worker-finished', forcedExit});
147
144
  } else if (code > 0) {
@@ -158,12 +155,12 @@ module.exports = (file, options, execArgv = process.execArgv) => {
158
155
 
159
156
  return {
160
157
  file,
161
- forkId,
158
+ threadId: worker.threadId,
162
159
  promise,
163
160
 
164
161
  exit() {
165
162
  forcedExit = true;
166
- subprocess.kill();
163
+ close();
167
164
  },
168
165
 
169
166
  notifyOfPeerFailure() {
@@ -176,6 +173,6 @@ module.exports = (file, options, execArgv = process.execArgv) => {
176
173
 
177
174
  onStateChange(listener) {
178
175
  return emitter.on('stateChange', listener);
179
- }
176
+ },
180
177
  };
181
- };
178
+ }