diff --git a/ava.config.js b/ava.config.js
index 559513382..940832901 100644
--- a/ava.config.js
+++ b/ava.config.js
@@ -4,7 +4,9 @@ const skipWatchMode = process.env.TEST_AVA_SKIP_WATCH_MODE ? ['!test/watch-mode/
export default { // eslint-disable-line import/no-anonymous-default-export
files: ['test/**', '!test/**/{fixtures,helpers}/**', ...skipWatchMode],
- ignoredByWatcher: ['{coverage,docs,media,test-types,test-tap}/**'],
+ watchMode: {
+ ignoreChanges: ['{coverage,docs,media,test-types,test-tap}/**'],
+ },
environmentVariables: {
AVA_FAKE_SCM_ROOT: '.fake-root', // This is an internal test flag.
},
diff --git a/docs/01-writing-tests.md b/docs/01-writing-tests.md
index abec90c38..84836a1a5 100644
--- a/docs/01-writing-tests.md
+++ b/docs/01-writing-tests.md
@@ -10,7 +10,7 @@ AVA tries to run test files with their current working directory set to the dire
## Test isolation
-Each test file is run in a new worker thread. This is new as of AVA 4, though you can fall back to AVA 3's behavior of running in separate processes.
+By default each test file is run in a new worker thread. You can fall back running in separate processes.
AVA will set `process.env.NODE_ENV` to `test`, unless the `NODE_ENV` environment variable has been set. This is useful if the code you're testing has test defaults (for example when picking what database to connect to). It may cause your code or its dependencies to behave differently though. Note that `'NODE_ENV' in process.env` will always be `true`.
@@ -154,7 +154,7 @@ AVA lets you register hooks that are run before and after your tests. This allow
If a test is skipped with the `.skip` modifier, the respective `.beforeEach()`, `.afterEach()` and `.afterEach.always()` hooks are not run. Likewise, if all tests in a test file are skipped `.before()`, `.after()` and `.after.always()` hooks for the file are not run.
-*You may not need to use `.afterEach.always()` hooks to clean up after a test.* You can use [`t.teardown()`](./02-execution-context.md#tteardownfn) to undo side-effects *within* a particular test.
+*You may not need to use `.afterEach.always()` hooks to clean up after a test.* You can use [`t.teardown()`](./02-execution-context.md#tteardownfn) to undo side-effects *within* a particular test. Or use [`registerCompletionHandler()`](./08-common-pitfalls.md#timeouts-because-a-file-failed-to-exit) to run cleanup code after AVA has completed its work.
Like `test()` these methods take an optional title and an implementation function. The title is shown if your hook fails to execute. The implementation is called with an [execution object](./02-execution-context.md). You can use assertions in your hooks. You can also pass a [macro function](#reusing-test-logic-through-macros) and additional arguments.
diff --git a/docs/03-assertions.md b/docs/03-assertions.md
index 1613658ec..f5800b063 100644
--- a/docs/03-assertions.md
+++ b/docs/03-assertions.md
@@ -21,13 +21,11 @@ test('unicorns are truthy', t => {
If multiple assertion failures are encountered within a single test, AVA will only display the *first* one.
-In AVA 6, assertions return `true` if they've passed and throw otherwise. Catching this error does not cause the test to pass. The error value is undocumented.
-
-In AVA 5, assertions return a boolean and do not throw. You can use this to return early from a test. The `snapshot()` assertion does not return a value.
+Assertions return `true` if they've passed and throw otherwise. Catching this error does not cause the test to pass. The error value is undocumented.
If you use TypeScript you can use some assertions as type guards.
-Note that the "throws" assertions return the error that was thrown (provided the assertion passed). In AVA 5, they return `undefined` if the assertion failed.
+Note that the "throws" assertions return the error that was thrown (provided the assertion passed).
## Assertion planning
@@ -179,7 +177,7 @@ t.like([1, 2, 3, 4], [1, , 3])
Assert that an error is thrown. `fn` must be a function which should throw. By default, the thrown value *must* be an error. It is returned so you can run more assertions against it.
`expectation` can be an object with one or more of the following properties:
-* `any`: a boolean only available in AVA 6, if `true` then the thrown value does not need to be an error. Defaults to `false`
+* `any`: a boolean, if `true` then the thrown value does not need to be an error. Defaults to `false`
* `instanceOf`: a constructor, the thrown error must be an instance of
* `is`: the thrown error must be strictly equal to `expectation.is`
* `message`: the following types are valid:
@@ -214,7 +212,7 @@ Assert that an error is thrown. `thrower` can be an async function which should
By default, the thrown value *must* be an error. It is returned so you can run more assertions against it.
`expectation` can be an object with one or more of the following properties:
-* `any`: a boolean only available in AVA 6, if `true` then the thrown value does not need to be an error. Defaults to `false`
+* `any`: a boolean, if `true` then the thrown value does not need to be an error. Defaults to `false`
* `instanceOf`: a constructor, the thrown error must be an instance of
* `is`: the thrown error must be strictly equal to `expectation.is`
* `message`: the following types are valid:
@@ -279,7 +277,7 @@ Compares the `expected` value with a previously recorded snapshot. Snapshots are
The implementation function behaves the same as any other test function. You can even use macros. The first title argument is always optional. Additional arguments are passed to the implementation or macro function.
-`.try()` is an asynchronous function. You must `await` it. The result object has `commit()` and `discard()` methods. You must decide whether to commit or discard the result. If you commit a failed result, your test will fail. In AVA 6, calling `commit()` on a failed result will throw an error.
+`.try()` is an asynchronous function. You must `await` it. The result object has `commit()` and `discard()` methods. You must decide whether to commit or discard the result. If you commit a failed result, your test will fail. Calling `commit()` on a failed result will throw an error.
You can check whether the attempt passed using the `passed` property. Any assertion errors are available through the `errors` property. The attempt title is available through the `title` property.
diff --git a/docs/07-test-timeouts.md b/docs/07-test-timeouts.md
index 85dc99649..0e0f7b9a0 100644
--- a/docs/07-test-timeouts.md
+++ b/docs/07-test-timeouts.md
@@ -4,7 +4,7 @@ Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/docs
[](https://stackblitz.com/github/avajs/ava/tree/main/examples/timeouts?file=test.js&terminal=test&view=editor)
-Timeouts in AVA behave differently than in other test frameworks. AVA resets a timer after each test, forcing tests to quit if no new test results were received within the specified timeout. This can be used to handle stalled tests.
+Timeouts in AVA behave differently than in other test frameworks. AVA resets a timer after each test, forcing tests to quit if no new test results were received within the specified timeout. This can be used to handle stalled tests. This same mechanism is used to determine when a test file is preventing a clean exit.
The default timeout is 10 seconds.
diff --git a/docs/08-common-pitfalls.md b/docs/08-common-pitfalls.md
index 15fef0643..9a2533fa0 100644
--- a/docs/08-common-pitfalls.md
+++ b/docs/08-common-pitfalls.md
@@ -81,6 +81,37 @@ Error [ERR_WORKER_INVALID_EXEC_ARGV]: Initiated Worker with invalid execArgv fla
If possible don't specify the command line option when running AVA. Alternatively you could [disable worker threads in AVA](./06-configuration.md#options).
+## Timeouts because a file failed to exit
+
+You may get a "Timed out while running tests" error because AVA failed to exit when running a particular file.
+
+AVA waits for Node.js to exit the worker thread or child process. If this takes too long, AVA counts it as a timeout.
+
+It is best practice to make sure your code exits cleanly. We've also seen occurrences where an explicit `process.exit()` call inside a worker thread could not be observed in AVA's main process.
+
+For these reasons we're not providing an option to disable this timeout behavior. However, it is possible to register a callback for when AVA has completed the test run without uncaught exceptions or unhandled rejections. From inside this callback you can do whatever you need to do, including calling `process.exit()`.
+
+Create a `_force-exit.mjs` file:
+
+```js
+import process from 'node:process';
+import { registerCompletionHandler } from 'ava';
+
+registerCompletionHandler(() => {
+ process.exit();
+});
+```
+
+Completion handlers are invoked in order of registration. Results are not awaited.
+
+Load it for all test files through AVA's `require` option:
+
+```js
+export default {
+ require: ['./_force-exit.mjs'],
+};
+```
+
## Sharing variables between asynchronous tests
By default AVA executes tests concurrently. This can cause problems if your tests are asynchronous and share variables.
diff --git a/docs/recipes/es-modules.md b/docs/recipes/es-modules.md
deleted file mode 100644
index 6358063bb..000000000
--- a/docs/recipes/es-modules.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Using ES modules in AVA
-
-AVA 4 supports ES modules out of the box.
diff --git a/docs/recipes/react.md b/docs/recipes/react.md
deleted file mode 100644
index edb792fce..000000000
--- a/docs/recipes/react.md
+++ /dev/null
@@ -1,189 +0,0 @@
-# Testing React components with AVA 3
-
-Translations: [Español](https://github.com/avajs/ava-docs/blob/main/es_ES/docs/recipes/react.md), [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/docs/recipes/react.md)
-
-**AVA 4 no longer has built-in Babel support, and `t.snapshot()` and `t.deepEqual()` no longer recognize React elements either. Therefore this recipe is mostly relevant for AVA 3 users.**
-
-## Setting up Babel
-
-When you [enable Babel](https://github.com/avajs/babel), AVA 3 will automatically extend your regular (project-level) Babel configuration. You should be able to use React in your test files without any additional configuration.
-
-However if you want to set it up explicitly, add the preset to the test options in AVA's Babel pipeline by modifying your `package.json` or `ava.config.*` file.
-
-**`package.json`:**
-
-```json
-{
- "ava": {
- "babel": {
- "testOptions": {
- "presets": ["@babel/preset-react"]
- }
- }
- }
-}
-```
-
-You can find more information in [`@ava/babel`](https://github.com/avajs/babel).
-
-## Using [Enzyme](https://github.com/airbnb/enzyme)
-
-Let's first see how to use AVA with one of the most popular React testing libraries: [Enzyme](https://github.com/enzymejs/enzyme).
-
-If you intend to only use [shallow component rendering](https://facebook.github.io/react/docs/test-utils.html#shallow-rendering), you don't need any extra setup.
-
-### Install Enzyme
-
-First install [Enzyme and its required adapter](https://github.com/enzymejs/enzyme#installation):
-
-```console
-$ npm install --save-dev enzyme enzyme-adapter-react-16
-```
-
-### Set up Enzyme
-
-Create a helper file, prefixed with an underscore. This ensures AVA does not treat it as a test.
-
-`test/_setup-enzyme-adapter.js`:
-
-```js
-import Enzyme from 'enzyme';
-import Adapter from 'enzyme-adapter-react-16';
-
-Enzyme.configure({
- adapter: new Adapter()
-});
-```
-
-### Configure tests to use Enzyme
-
-Configure AVA to `require` the helper before every test file.
-
-**`package.json`:**
-
-```json
-{
- "ava": {
- "require": [
- "./test/_setup-enzyme-adapter.js"
- ]
- }
-}
-```
-
-### Enjoy
-
-Then you can use Enzyme straight away:
-
-`test.js`:
-
-```js
-import test from 'ava';
-import React from 'react';
-import PropTypes from 'prop-types';
-import {shallow} from 'enzyme';
-
-const Foo = ({children}) =>
-
- bar
- {children}
- bar
-
;
-
-Foo.propTypes = {
- children: PropTypes.any
-};
-
-test('has a .Foo class name', t => {
- const wrapper = shallow();
- t.true(wrapper.hasClass('Foo'));
-});
-
-test('renders two `.Bar`', t => {
- const wrapper = shallow();
- t.is(wrapper.find('.bar').length, 2);
-});
-
-test('renders children when passed in', t => {
- const wrapper = shallow(
-
-
-
- );
- t.true(wrapper.contains());
-});
-```
-
-Enzyme also has a `mount` and `render` helper to test in an actual browser environment. If you want to use these helpers, you will have to setup a browser environment. Check out the [browser testing recipe](https://github.com/avajs/ava/blob/main/docs/recipes/browser-testing.md) on how to do so.
-
-To see an example of AVA working together with Enzyme set up for browser testing, have a look at [this sample project](https://github.com/adriantoine/ava-enzyme-demo).
-
-This is a basic example on how to integrate Enzyme with AVA. For more information about using Enzyme for unit testing React component, have a look at [Enzyme's documentation](https://enzymejs.github.io/enzyme/).
-
-## Using JSX helpers
-
-Another approach to testing React component is to use the [`react-element-to-jsx-string`](https://github.com/algolia/react-element-to-jsx-string) package to compare DOM trees as strings. [`jsx-test-helpers`](https://github.com/MoOx/jsx-test-helpers) is a nice library handling [shallow component rendering](https://facebook.github.io/react/docs/test-utils.html#shallow-rendering) and converting JSX to string in order to test React components using AVA assertions.
-
-```console
-$ npm install --save-dev jsx-test-helpers
-```
-
-Usage example:
-
-```js
-import test from 'ava';
-import React from 'react';
-import PropTypes from 'prop-types';
-import {renderJSX, JSX} from 'jsx-test-helpers';
-
-const Foo = ({children}) =>
-
- bar
- {children}
- bar
-
;
-
-Foo.propTypes = {
- children: PropTypes.any
-};
-
-test('renders correct markup', t => {
- const actual = renderJSX();
- const expected = JSX(
-
- bar
- bar
-
- );
- t.is(actual, expected);
-});
-
-test('renders children when passed in', t => {
- const actual = renderJSX(
-
-
-
- );
- const expected = JSX(
-
- );
- t.is(actual, expected);
-});
-```
-
-Note that you have to use variables like `actual` and `expected` because [`power-assert` doesn't handle JSX correctly](https://github.com/power-assert-js/power-assert/issues/34).
-
-This is a basic example on how to use `jsx-test-helpers` with AVA. To see a more advanced usage of this library, have a look at [this annotated test file](https://github.com/MoOx/jsx-test-helpers/blob/master/src/__tests__/index.js).
-
-[This sample project](https://github.com/MoOx/jsx-test-helpers) shows a basic and minimal setup of AVA with `jsx-test-helpers`.
-
-## Using other assertion libraries
-
-In AVA, you can use any assertion library, and there are already a few out there to test React components. Here is a list of assertion libraries working well with AVA:
-
-- [`expect-jsx`](https://github.com/algolia/expect-jsx) ([Example](https://github.com/avajs/ava/issues/186#issuecomment-161317068))
-- [`unexpected-react`](https://github.com/bruderstein/unexpected-react) ([Sample project with an output example](https://github.com/adriantoine/ava-unexpected-react-demo))
diff --git a/docs/recipes/typescript.md b/docs/recipes/typescript.md
index 0a6298412..e110a4c58 100644
--- a/docs/recipes/typescript.md
+++ b/docs/recipes/typescript.md
@@ -177,7 +177,7 @@ Note that, despite the type cast above, when executing `t.context` is an empty o
## Typing `throws` assertions
-In AVA 6, the `t.throws()` and `t.throwsAsync()` assertions are typed to always return an `Error`. You can customize the error class using generics:
+The `t.throws()` and `t.throwsAsync()` assertions are typed to always return an `Error`. You can customize the error class using generics:
```ts
import test from 'ava';
@@ -206,6 +206,4 @@ test('throwsAsync', async t => {
});
```
-In AVA 5, the assertion is typed to return the `Error` if the assertion passes *or* `undefined` if it fails.
-
[`@ava/typescript`]: https://github.com/avajs/typescript
diff --git a/docs/recipes/watch-mode.md b/docs/recipes/watch-mode.md
index 830031220..39d7c4bee 100644
--- a/docs/recipes/watch-mode.md
+++ b/docs/recipes/watch-mode.md
@@ -16,17 +16,13 @@ Please note that integrated debugging and the TAP reporter are unavailable when
## Requirements
-AVA 5 uses [`chokidar`] as the file watcher. Note that even if you see warnings about optional dependencies failing during install, it will still work fine. Please refer to the *[Install Troubleshooting]* section of `chokidar` documentation for how to resolve the installation problems with chokidar.
-
-Otherwise, AVA 6 uses `fs.watch()`. Support for `recursive` mode is required. Note that this has only become available on Linux since Node.js 20. [Other caveats apply](https://nodejs.org/api/fs.html#caveats), for example this won't work well on network filesystems and Docker host mounts.
+AVA uses `fs.watch()`. Support for `recursive` mode is required. Note that this has only become available on Linux since Node.js 20. [Other caveats apply](https://nodejs.org/api/fs.html#caveats), for example this won't work well on network filesystems and Docker host mounts.
## Ignoring changes
By default AVA watches for changes to all files, except for those with a `.snap.md` extension, `ava.config.*` and files in [certain directories](https://github.com/novemberborn/ignore-by-default/blob/master/index.js) as provided by the [`ignore-by-default`] package.
-With AVA 5, you can configure additional patterns for files to ignore in the [`ava` section of your `package.json`, or `ava.config.*` file][config], using the `ignoredByWatcher` key.
-
-With AVA 6, place these patterns within the `watchMode` object:
+You can configure additional patterns for files to ignore in the [`ava` section of your `package.json`, or `ava.config.*` file][config], using the `ignoreChanges` key within the `watchMode` object:
```js
export default {
@@ -42,9 +38,7 @@ If your tests write to disk they may trigger the watcher to rerun your tests. Co
AVA tracks which source files your test files depend on. If you change such a dependency only the test file that depends on it will be rerun. AVA will rerun all tests if it cannot determine which test file depends on the changed source file.
-AVA 5 spies on `require()` calls to track dependencies. Custom extensions and transpilers are supported, provided you [added them in your `package.json` or `ava.config.*` file][config], and not from inside your test file.
-
-With AVA 6, dependency tracking works for `require()` and `import` syntax, as supported by [@vercel/nft](https://github.com/vercel/nft). `import()` is supported but dynamic paths such as `import(myVariable)` are not.
+Dependency tracking works for `require()` and `import` syntax, as supported by [@vercel/nft](https://github.com/vercel/nft). `import()` is supported but dynamic paths such as `import(myVariable)` are not.
Files accessed using the `fs` module are not tracked.
diff --git a/entrypoints/internal.d.mts b/entrypoints/internal.d.mts
index 753b780e5..8afc573ea 100644
--- a/entrypoints/internal.d.mts
+++ b/entrypoints/internal.d.mts
@@ -1,4 +1,4 @@
-import type {StateChangeEvent} from '../types/state-change-events.d';
+import type {StateChangeEvent} from '../types/state-change-events.d.cts';
export type Event = StateChangeEvent;
diff --git a/entrypoints/main.d.mts b/entrypoints/main.d.mts
index d4fcdc160..6b4fb27ca 100644
--- a/entrypoints/main.d.mts
+++ b/entrypoints/main.d.mts
@@ -10,3 +10,11 @@ declare const test: TestFn;
/** Call to declare a test, or chain to declare hooks or test modifiers */
export default test;
+
+/**
+ * Register a function to be called when AVA has completed a test run without uncaught exceptions or unhandled rejections.
+ *
+ * Completion handlers are invoked in order of registration. Results are not awaited.
+ */
+declare const registerCompletionHandler: (handler: () => void) => void;
+export {registerCompletionHandler};
diff --git a/entrypoints/main.mjs b/entrypoints/main.mjs
index 36b076bb6..fec379316 100644
--- a/entrypoints/main.mjs
+++ b/entrypoints/main.mjs
@@ -1 +1,2 @@
export {default} from '../lib/worker/main.cjs';
+export {registerCompletionHandler} from '../lib/worker/completion-handlers.js';
diff --git a/lib/api.js b/lib/api.js
index 39a3c435c..ff0f0f74c 100644
--- a/lib/api.js
+++ b/lib/api.js
@@ -303,7 +303,9 @@ export default class Api extends Emittery {
// Allow shared workers to clean up before the run ends.
await Promise.all(deregisteredSharedWorkers);
const files = scheduler.storeFailedTestFiles(runStatus, this.options.cacheEnabled === false ? null : this._createCacheDir());
- runStatus.emitStateChange({type: 'touched-files', files});
+ if (files) {
+ runStatus.emitStateChange({type: 'touched-files', files});
+ }
} catch (error) {
runStatus.emitStateChange({type: 'internal-error', err: serializeError(error)});
}
diff --git a/lib/assert.js b/lib/assert.js
index f4d5ff518..9e93d31fa 100644
--- a/lib/assert.js
+++ b/lib/assert.js
@@ -626,13 +626,6 @@ export class Assertions {
}));
}
- if (message?.id !== undefined) {
- throw fail(new AssertionError('Since AVA 4, snapshot IDs are no longer supported', {
- assertion: 't.snapshot()',
- formattedDetails: [formatWithLabel('Called with id:', message.id)],
- }));
- }
-
assertMessage(message, 't.snapshot()');
if (message === '') {
diff --git a/lib/run-status.js b/lib/run-status.js
index cd80fc847..ae6b68c4e 100644
--- a/lib/run-status.js
+++ b/lib/run-status.js
@@ -38,6 +38,7 @@ export default class RunStatus extends Emittery {
timeouts: 0,
todoTests: 0,
uncaughtExceptions: 0,
+ unexpectedProcessExits: 0,
unhandledRejections: 0,
};
}
@@ -56,6 +57,7 @@ export default class RunStatus extends Emittery {
skippedTests: 0,
todoTests: 0,
uncaughtExceptions: 0,
+ unexpectedProcessExits: 0,
unhandledRejections: 0,
...stats,
});
@@ -167,6 +169,8 @@ export default class RunStatus extends Emittery {
}
case 'process-exit': {
+ stats.unexpectedProcessExits++;
+ fileStats.unexpectedProcessExits++;
event.pendingTests = this.pendingTests;
event.pendingTestsLogs = this.pendingTestsLogs;
this.pendingTests = new Map();
@@ -237,6 +241,7 @@ export default class RunStatus extends Emittery {
|| this.stats.sharedWorkerErrors > 0
|| this.stats.timeouts > 0
|| this.stats.uncaughtExceptions > 0
+ || this.stats.unexpectedProcessExits > 0
|| this.stats.unhandledRejections > 0
) {
return 1;
diff --git a/lib/runner.js b/lib/runner.js
index d81c11ad3..8c80f2271 100644
--- a/lib/runner.js
+++ b/lib/runner.js
@@ -123,14 +123,10 @@ export default class Runner extends Emittery {
todo: true,
});
} else {
- if (!implementation) {
+ if (typeof implementation !== 'function') {
throw new TypeError('Expected an implementation. Use `test.todo()` for tests without an implementation.');
}
- if (Array.isArray(implementation)) {
- throw new TypeError('AVA 4 no longer supports multiple implementations.');
- }
-
if (title.isSet && !title.isValid) {
throw new TypeError('Test & hook titles must be strings');
}
diff --git a/lib/test.js b/lib/test.js
index e821c9f7a..caccda844 100644
--- a/lib/test.js
+++ b/lib/test.js
@@ -99,14 +99,10 @@ class ExecutionContext extends Assertions {
const {args, implementation, title} = parseTestArgs(attemptArgs);
- if (!implementation) {
+ if (typeof implementation !== 'function') {
throw new TypeError('Expected an implementation.');
}
- if (Array.isArray(implementation)) {
- throw new TypeError('AVA 4 no longer supports t.try() with multiple implementations.');
- }
-
let attemptTitle;
if (!title.isSet || title.isEmpty) {
attemptTitle = `${test.title} ─ attempt ${test.attemptCount + 1}`;
diff --git a/lib/watcher.js b/lib/watcher.js
index cf29c5518..d95fbb942 100644
--- a/lib/watcher.js
+++ b/lib/watcher.js
@@ -20,7 +20,7 @@ const END_MESSAGE = chalk.gray('Type `r` and press enter to rerun tests\nType `u
export function available(projectDir) {
try {
- fs.watch(projectDir, {recursive: true, signal: AbortSignal.abort()});
+ fs.watch(projectDir, {persistent: false, recursive: true, signal: AbortSignal.abort()});
} catch (error) {
if (error.code === 'ERR_FEATURE_UNAVAILABLE_ON_PLATFORM') {
return false;
diff --git a/lib/worker/base.js b/lib/worker/base.js
index d5a483af5..cc8d444c1 100644
--- a/lib/worker/base.js
+++ b/lib/worker/base.js
@@ -15,6 +15,7 @@ import Runner from '../runner.js';
import serializeError from '../serialize-error.js';
import channel from './channel.cjs';
+import {runCompletionHandlers} from './completion-handlers.js';
import lineNumberSelection from './line-numbers.js';
import {set as setOptions} from './options.cjs';
import {flags, refs, sharedWorkerTeardowns} from './state.cjs';
@@ -23,17 +24,22 @@ import {isRunningInThread, isRunningInChildProcess} from './utils.cjs';
const currentlyUnhandled = setUpCurrentlyUnhandled();
let runner;
-let forcingExit = false;
+let expectingExit = false;
const forceExit = () => {
- forcingExit = true;
+ expectingExit = true;
process.exit(1);
};
+const avaIsDone = () => {
+ expectingExit = true;
+ runCompletionHandlers();
+};
+
// Override process.exit with an undetectable replacement
// to report when it is called from a test (which it should never be).
const handleProcessExit = (target, thisArg, args) => {
- if (!forcingExit) {
+ if (!expectingExit) {
const error = new Error('Unexpected process.exit()');
Error.captureStackTrace(error, handleProcessExit);
channel.send({type: 'process-exit', stack: error.stack});
@@ -118,7 +124,7 @@ const run = async options => {
nowAndTimers.setImmediate(() => {
const unhandled = currentlyUnhandled();
if (unhandled.length === 0) {
- return;
+ return avaIsDone();
}
for (const rejection of unhandled) {
diff --git a/lib/worker/completion-handlers.js b/lib/worker/completion-handlers.js
new file mode 100644
index 000000000..ddf4b479e
--- /dev/null
+++ b/lib/worker/completion-handlers.js
@@ -0,0 +1,13 @@
+import process from 'node:process';
+
+import state from './state.cjs';
+
+export function runCompletionHandlers() {
+ for (const handler of state.completionHandlers) {
+ process.nextTick(() => handler());
+ }
+}
+
+export function registerCompletionHandler(handler) {
+ state.completionHandlers.push(handler);
+}
diff --git a/lib/worker/state.cjs b/lib/worker/state.cjs
index 9e7deaeaf..3cd9e2d29 100644
--- a/lib/worker/state.cjs
+++ b/lib/worker/state.cjs
@@ -1,5 +1,6 @@
'use strict';
exports.flags = {loadedMain: false};
exports.refs = {runnerChain: null};
+exports.completionHandlers = [];
exports.sharedWorkerTeardowns = [];
exports.waitForReady = [];
diff --git a/package-lock.json b/package-lock.json
index aee814ebf..a0a155e35 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "ava",
- "version": "6.0.1",
+ "version": "6.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ava",
- "version": "6.0.1",
+ "version": "6.1.0",
"license": "MIT",
"dependencies": {
"@vercel/nft": "^0.24.4",
@@ -106,11 +106,11 @@
},
"node_modules/@ava/test": {
"version": "0.0.0",
- "resolved": "git+ssh://git@github.com/avajs/test.git#011135c4aec550d2bfbea03d144e33e3066cec82",
+ "resolved": "git+ssh://git@github.com/avajs/test.git#8482014de4cabf2fac14e28ca80130cbe8adee21",
"dev": true,
"license": "MIT",
"dependencies": {
- "@ava/v5": "npm:ava@5.3.1"
+ "@ava/v6": "npm:ava@^6"
},
"bin": {
"test-ava": "cli.js"
@@ -179,52 +179,49 @@
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"dev": true
},
- "node_modules/@ava/v5": {
+ "node_modules/@ava/v6": {
"name": "ava",
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/ava/-/ava-5.3.1.tgz",
- "integrity": "sha512-Scv9a4gMOXB6+ni4toLuhAm9KYWEjsgBglJl+kMGI5+IVDt120CCDZyB5HNU9DjmLI2t4I0GbnxGLmmRfGTJGg==",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/ava/-/ava-6.0.1.tgz",
+ "integrity": "sha512-9zR0wOwlcJdOWwHOKnpi0GrPRLTlxDFapGalP4rGD0oQRKxDVoucBBWvxVQ/2cPv10Hx1PkDXLJH5iUzhPn0/g==",
"dev": true,
"dependencies": {
- "acorn": "^8.8.2",
- "acorn-walk": "^8.2.0",
+ "@vercel/nft": "^0.24.4",
+ "acorn": "^8.11.2",
+ "acorn-walk": "^8.3.0",
"ansi-styles": "^6.2.1",
"arrgv": "^1.0.2",
"arrify": "^3.0.0",
- "callsites": "^4.0.0",
- "cbor": "^8.1.0",
- "chalk": "^5.2.0",
- "chokidar": "^3.5.3",
+ "callsites": "^4.1.0",
+ "cbor": "^9.0.1",
+ "chalk": "^5.3.0",
"chunkd": "^2.0.1",
- "ci-info": "^3.8.0",
+ "ci-info": "^4.0.0",
"ci-parallel-vars": "^1.0.1",
- "clean-yaml-object": "^0.1.0",
- "cli-truncate": "^3.1.0",
+ "cli-truncate": "^4.0.0",
"code-excerpt": "^4.0.0",
"common-path-prefix": "^3.0.0",
"concordance": "^5.0.4",
"currently-unhandled": "^0.4.1",
"debug": "^4.3.4",
"emittery": "^1.0.1",
- "figures": "^5.0.0",
- "globby": "^13.1.4",
+ "figures": "^6.0.1",
+ "globby": "^14.0.0",
"ignore-by-default": "^2.1.0",
"indent-string": "^5.0.0",
- "is-error": "^2.2.2",
"is-plain-object": "^5.0.0",
"is-promise": "^4.0.0",
"matcher": "^5.0.0",
- "mem": "^9.0.2",
+ "memoize": "^10.0.0",
"ms": "^2.1.3",
- "p-event": "^5.0.1",
- "p-map": "^5.5.0",
- "picomatch": "^2.3.1",
- "pkg-conf": "^4.0.0",
+ "p-map": "^6.0.0",
+ "package-config": "^5.0.0",
+ "picomatch": "^3.0.1",
"plur": "^5.1.0",
"pretty-ms": "^8.0.0",
"resolve-cwd": "^3.0.0",
"stack-utils": "^2.0.6",
- "strip-ansi": "^7.0.1",
+ "strip-ansi": "^7.1.0",
"supertap": "^3.0.1",
"temp-dir": "^3.0.0",
"write-file-atomic": "^5.0.1",
@@ -234,7 +231,7 @@
"ava": "entrypoints/cli.mjs"
},
"engines": {
- "node": ">=14.19 <15 || >=16.15 <17 || >=18"
+ "node": "^18.18 || ^20.8 || ^21"
},
"peerDependencies": {
"@ava/typescript": "*"
@@ -245,158 +242,6 @@
}
}
},
- "node_modules/@ava/v5/node_modules/cbor": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz",
- "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==",
- "dev": true,
- "dependencies": {
- "nofilter": "^3.1.0"
- },
- "engines": {
- "node": ">=12.19"
- }
- },
- "node_modules/@ava/v5/node_modules/ci-info": {
- "version": "3.9.0",
- "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
- "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
- "dev": true,
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/sibiraj-s"
- }
- ],
- "engines": {
- "node": ">=8"
- }
- },
- "node_modules/@ava/v5/node_modules/cli-truncate": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz",
- "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==",
- "dev": true,
- "dependencies": {
- "slice-ansi": "^5.0.0",
- "string-width": "^5.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@ava/v5/node_modules/emoji-regex": {
- "version": "9.2.2",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
- "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
- "dev": true
- },
- "node_modules/@ava/v5/node_modules/figures": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz",
- "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==",
- "dev": true,
- "dependencies": {
- "escape-string-regexp": "^5.0.0",
- "is-unicode-supported": "^1.2.0"
- },
- "engines": {
- "node": ">=14"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@ava/v5/node_modules/globby": {
- "version": "13.2.2",
- "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz",
- "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==",
- "dev": true,
- "dependencies": {
- "dir-glob": "^3.0.1",
- "fast-glob": "^3.3.0",
- "ignore": "^5.2.4",
- "merge2": "^1.4.1",
- "slash": "^4.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@ava/v5/node_modules/is-unicode-supported": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz",
- "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@ava/v5/node_modules/p-map": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz",
- "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==",
- "dev": true,
- "dependencies": {
- "aggregate-error": "^4.0.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@ava/v5/node_modules/picomatch": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
- "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
- "dev": true,
- "engines": {
- "node": ">=8.6"
- },
- "funding": {
- "url": "https://github.com/sponsors/jonschlinkert"
- }
- },
- "node_modules/@ava/v5/node_modules/slash": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
- "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/@ava/v5/node_modules/string-width": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
- "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
- "dev": true,
- "dependencies": {
- "eastasianwidth": "^0.2.0",
- "emoji-regex": "^9.2.2",
- "strip-ansi": "^7.0.1"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/@babel/code-frame": {
"version": "7.23.5",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
@@ -2775,22 +2620,6 @@
"node": ">= 6.0.0"
}
},
- "node_modules/aggregate-error": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz",
- "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==",
- "dev": true,
- "dependencies": {
- "clean-stack": "^4.0.0",
- "indent-string": "^5.0.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -3582,30 +3411,6 @@
"node": ">=0.8.0"
}
},
- "node_modules/clean-stack": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz",
- "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==",
- "dev": true,
- "dependencies": {
- "escape-string-regexp": "5.0.0"
- },
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/clean-yaml-object": {
- "version": "0.1.0",
- "resolved": "https://registry.npmjs.org/clean-yaml-object/-/clean-yaml-object-0.1.0.tgz",
- "integrity": "sha512-3yONmlN9CSAkzNwnRCiJQ7Q2xK5mWuEfL3PuTZcAUzhObbXsfsnMptJzXwz93nc5zn9V9TwCVMmV7w4xsm43dw==",
- "dev": true,
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/cli-boxes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz",
@@ -6750,12 +6555,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/is-error": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/is-error/-/is-error-2.2.2.tgz",
- "integrity": "sha512-IOQqts/aHWbiisY5DuPJQ0gcbvaLFCa7fBa9xoLfxBZvQ+ZI/Zh9xoI7Gk+G64N0FdK4AbibytHht2tWgpJWLg==",
- "dev": true
- },
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -7913,18 +7712,6 @@
"node": "^16.14.0 || >=18.0.0"
}
},
- "node_modules/map-age-cleaner": {
- "version": "0.1.3",
- "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
- "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
- "dev": true,
- "dependencies": {
- "p-defer": "^1.0.0"
- },
- "engines": {
- "node": ">=6"
- }
- },
"node_modules/map-obj": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
@@ -7962,22 +7749,6 @@
"node": ">=8"
}
},
- "node_modules/mem": {
- "version": "9.0.2",
- "resolved": "https://registry.npmjs.org/mem/-/mem-9.0.2.tgz",
- "integrity": "sha512-F2t4YIv9XQUBHt6AOJ0y7lSmP1+cY7Fm1DRh9GClTGzKST7UWLMx6ly9WZdLH/G/ppM5RL4MlQfRT71ri9t19A==",
- "dev": true,
- "dependencies": {
- "map-age-cleaner": "^0.1.3",
- "mimic-fn": "^4.0.0"
- },
- "engines": {
- "node": ">=12.20"
- },
- "funding": {
- "url": "https://github.com/sindresorhus/mem?sponsor=1"
- }
- },
"node_modules/memoize": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/memoize/-/memoize-10.0.0.tgz",
@@ -9067,30 +8838,6 @@
"node": ">= 0.8.0"
}
},
- "node_modules/p-defer": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
- "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==",
- "dev": true,
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/p-event": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/p-event/-/p-event-5.0.1.tgz",
- "integrity": "sha512-dd589iCQ7m1L0bmC5NLlVYfy3TbBEsMUfWx9PyAgPeIcFZ/E2yaTZ4Rz4MiBmmJShviiftHVXOqfnfzJ6kyMrQ==",
- "dev": true,
- "dependencies": {
- "p-timeout": "^5.0.2"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -9132,18 +8879,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/p-timeout": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-5.1.0.tgz",
- "integrity": "sha512-auFDyzzzGZZZdHz3BtET9VEz0SE/uMEAx7uWfGPucfzEwwe/xH0iVeZibQmANYE/hp9T2+UUZT5m+BKyrDp3Ew==",
- "dev": true,
- "engines": {
- "node": ">=12"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
@@ -9368,104 +9103,6 @@
"node": ">= 6"
}
},
- "node_modules/pkg-conf": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-4.0.0.tgz",
- "integrity": "sha512-7dmgi4UY4qk+4mj5Cd8v/GExPo0K+SlY+hulOSdfZ/T6jVH6//y7NtzZo5WrfhDBxuQ0jCa7fLZmNaNh7EWL/w==",
- "dev": true,
- "dependencies": {
- "find-up": "^6.0.0",
- "load-json-file": "^7.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/pkg-conf/node_modules/find-up": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz",
- "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==",
- "dev": true,
- "dependencies": {
- "locate-path": "^7.1.0",
- "path-exists": "^5.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/pkg-conf/node_modules/locate-path": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz",
- "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==",
- "dev": true,
- "dependencies": {
- "p-locate": "^6.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/pkg-conf/node_modules/p-limit": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
- "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
- "dev": true,
- "dependencies": {
- "yocto-queue": "^1.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/pkg-conf/node_modules/p-locate": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz",
- "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==",
- "dev": true,
- "dependencies": {
- "p-limit": "^4.0.0"
- },
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
- "node_modules/pkg-conf/node_modules/path-exists": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz",
- "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==",
- "dev": true,
- "engines": {
- "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
- }
- },
- "node_modules/pkg-conf/node_modules/yocto-queue": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
- "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
- "dev": true,
- "engines": {
- "node": ">=12.20"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/pkg-dir": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-5.0.0.tgz",
diff --git a/package.json b/package.json
index 984c501e6..287f15c9d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "ava",
- "version": "6.0.1",
+ "version": "6.1.0",
"description": "Node.js test runner that lets you develop with confidence.",
"license": "MIT",
"repository": "avajs/ava",
diff --git a/readme.md b/readme.md
index 35faa09ff..fd7161e04 100644
--- a/readme.md
+++ b/readme.md
@@ -4,7 +4,7 @@
#
-AVA is a test runner for Node.js with a concise API, detailed error output, embrace of new language features and htread isolation that lets you develop with confidence 🚀
+AVA is a test runner for Node.js with a concise API, detailed error output, embrace of new language features and thread isolation that lets you develop with confidence 🚀
Watch this repository and follow the [Discussions](https://github.com/avajs/ava/discussions) for updates.
@@ -69,7 +69,7 @@ Alternatively you can install `ava` manually:
npm install --save-dev ava
```
-*Make sure to install AVA locally. As of AVA 4 it can no longer be run globally.*
+*Make sure to install AVA locally. AVA cannot be run globally.*
Don't forget to configure the `test` script in your `package.json` as per above.
diff --git a/scripts/ci.sh b/scripts/ci.sh
index 390570328..2838c77e7 100755
--- a/scripts/ci.sh
+++ b/scripts/ci.sh
@@ -1,9 +1,17 @@
#!/bin/bash
set -ex
+# Set environment variable to have the AVA config skip wathch mode tests.
TEST_AVA_SKIP_WATCH_MODE=1 npx c8 --report=none npx test-ava
-# Reduce concurrency and be generous with timeouts to give watch mode tests a
-# better chance of succeeding in a CI environment.
-npx c8 --report=none --no-clean npx test-ava --serial --timeout 30s test/watch-mode
-npx c8 --report=none --no-clean npx tap
+
+# Reduce concurrency to give watch mode tests a better chance of succeeding in
+# a CI environment.
+npx c8 --report=none --no-clean npx test-ava --serial test/watch-mode
+
+# Only run reporter tests on Linux where they're least likely to flake out.
+case "$(uname -s)" in
+ Linux*) npx c8 --report=none --no-clean npx tap;;
+ *) npx c8 --report=none --no-clean npx tap --exclude="test-tap/reporters/{default,tap}.js"
+esac
+
npx c8 report
diff --git a/test-tap/assert.js b/test-tap/assert.js
index 4ba1e4f50..633814a8b 100644
--- a/test-tap/assert.js
+++ b/test-tap/assert.js
@@ -1538,19 +1538,6 @@ test('.snapshot()', async t => {
});
}
- {
- // See https://github.com/avajs/ava/issues/2669
- const assertions = setup('id');
- failsWith(t, () => assertions.snapshot({foo: 'bar'}, {id: 'an id'}), {
- assertion: 't.snapshot()',
- message: 'Since AVA 4, snapshot IDs are no longer supported',
- formattedDetails: [{
- label: 'Called with id:',
- formatted: '\'an id\'',
- }],
- });
- }
-
await manager.save();
t.end();
});
diff --git a/test-tap/reporters/tap.regular.v18.log b/test-tap/reporters/tap.regular.v18.log
index 7686951e4..5ff5a1375 100644
--- a/test-tap/reporters/tap.regular.v18.log
+++ b/test-tap/reporters/tap.regular.v18.log
@@ -113,7 +113,7 @@ not ok 12 - test › no longer failing
message: >-
Test was expected to fail, but succeeded, you should stop marking the test as
failing
- at: 'Test.finish (/lib/test.js:633:28)'
+ at: 'Test.finish (/lib/test.js:629:28)'
...
---tty-stream-chunk-separator
not ok 13 - test › logs
@@ -144,7 +144,7 @@ not ok 15 - test › implementation throws non-error
details:
'Error thrown in test:': 'null'
message: Error thrown in test
- at: 'Test.run (/lib/test.js:546:25)'
+ at: 'Test.run (/lib/test.js:542:25)'
...
---tty-stream-chunk-separator
not ok 16 - traces-in-t-throws › throws
diff --git a/test-tap/reporters/tap.regular.v20.log b/test-tap/reporters/tap.regular.v20.log
index 7686951e4..5ff5a1375 100644
--- a/test-tap/reporters/tap.regular.v20.log
+++ b/test-tap/reporters/tap.regular.v20.log
@@ -113,7 +113,7 @@ not ok 12 - test › no longer failing
message: >-
Test was expected to fail, but succeeded, you should stop marking the test as
failing
- at: 'Test.finish (/lib/test.js:633:28)'
+ at: 'Test.finish (/lib/test.js:629:28)'
...
---tty-stream-chunk-separator
not ok 13 - test › logs
@@ -144,7 +144,7 @@ not ok 15 - test › implementation throws non-error
details:
'Error thrown in test:': 'null'
message: Error thrown in test
- at: 'Test.run (/lib/test.js:546:25)'
+ at: 'Test.run (/lib/test.js:542:25)'
...
---tty-stream-chunk-separator
not ok 16 - traces-in-t-throws › throws
diff --git a/test-tap/reporters/tap.regular.v21.log b/test-tap/reporters/tap.regular.v21.log
index 7686951e4..5ff5a1375 100644
--- a/test-tap/reporters/tap.regular.v21.log
+++ b/test-tap/reporters/tap.regular.v21.log
@@ -113,7 +113,7 @@ not ok 12 - test › no longer failing
message: >-
Test was expected to fail, but succeeded, you should stop marking the test as
failing
- at: 'Test.finish (/lib/test.js:633:28)'
+ at: 'Test.finish (/lib/test.js:629:28)'
...
---tty-stream-chunk-separator
not ok 13 - test › logs
@@ -144,7 +144,7 @@ not ok 15 - test › implementation throws non-error
details:
'Error thrown in test:': 'null'
message: Error thrown in test
- at: 'Test.run (/lib/test.js:546:25)'
+ at: 'Test.run (/lib/test.js:542:25)'
...
---tty-stream-chunk-separator
not ok 16 - traces-in-t-throws › throws
diff --git a/test/completion-handlers/fixtures/exit0.js b/test/completion-handlers/fixtures/exit0.js
new file mode 100644
index 000000000..c4884b9ff
--- /dev/null
+++ b/test/completion-handlers/fixtures/exit0.js
@@ -0,0 +1,7 @@
+import test, { registerCompletionHandler } from 'ava'
+
+registerCompletionHandler(() => {
+ process.exit(0)
+})
+
+test('pass', t => t.pass())
diff --git a/test/completion-handlers/fixtures/one.js b/test/completion-handlers/fixtures/one.js
new file mode 100644
index 000000000..229e8035c
--- /dev/null
+++ b/test/completion-handlers/fixtures/one.js
@@ -0,0 +1,9 @@
+import test, { registerCompletionHandler } from 'ava'
+
+registerCompletionHandler(() => {
+ console.error('one')
+})
+
+test('pass', t => {
+ t.pass()
+})
diff --git a/test/completion-handlers/fixtures/package.json b/test/completion-handlers/fixtures/package.json
new file mode 100644
index 000000000..54f672450
--- /dev/null
+++ b/test/completion-handlers/fixtures/package.json
@@ -0,0 +1,8 @@
+{
+ "type": "module",
+ "ava": {
+ "files": [
+ "*.js"
+ ]
+ }
+}
diff --git a/test/completion-handlers/fixtures/two.js b/test/completion-handlers/fixtures/two.js
new file mode 100644
index 000000000..a688a1d2e
--- /dev/null
+++ b/test/completion-handlers/fixtures/two.js
@@ -0,0 +1,10 @@
+import test, { registerCompletionHandler } from 'ava'
+
+registerCompletionHandler(() => {
+ console.error('one')
+})
+registerCompletionHandler(() => {
+ console.error('two')
+})
+
+test('pass', t => t.pass())
diff --git a/test/completion-handlers/test.js b/test/completion-handlers/test.js
new file mode 100644
index 000000000..04dd28d57
--- /dev/null
+++ b/test/completion-handlers/test.js
@@ -0,0 +1,17 @@
+import test from '@ava/test';
+
+import {cleanOutput, fixture} from '../helpers/exec.js';
+
+test('runs a single completion handler', async t => {
+ const result = await fixture(['one.js']);
+ t.is(cleanOutput(result.stderr), 'one');
+});
+
+test('runs multiple completion handlers in registration order', async t => {
+ const result = await fixture(['two.js']);
+ t.deepEqual(cleanOutput(result.stderr).split('\n'), ['one', 'two']);
+});
+
+test('completion handlers may exit the process', async t => {
+ await t.notThrowsAsync(fixture(['exit0.js']));
+});
diff --git a/test/multiple-implementations/test.js b/test/multiple-implementations/test.js
index 4cefc477b..1bbeeecbe 100644
--- a/test/multiple-implementations/test.js
+++ b/test/multiple-implementations/test.js
@@ -4,10 +4,10 @@ import {fixture} from '../helpers/exec.js';
test('test()', async t => {
const result = await t.throwsAsync(fixture(['test.js']));
- t.regex(result.stdout, /AVA 4 no longer supports multiple implementations/);
+ t.regex(result.stdout, /Expected an implementation/);
});
test('t.try()', async t => {
const result = await t.throwsAsync(fixture(['try.js']));
- t.regex(result.stdout, /AVA 4 no longer supports t\.try\(\) with multiple implementations/);
+ t.regex(result.stdout, /Expected an implementation/);
});
diff --git a/test/watch-mode/helpers/watch.js b/test/watch-mode/helpers/watch.js
index ab553cdae..32c00ced1 100644
--- a/test/watch-mode/helpers/watch.js
+++ b/test/watch-mode/helpers/watch.js
@@ -80,23 +80,19 @@ export const withFixture = fixture => async (t, task) => {
const assertIdle = async next => {
assertingIdle = true;
- // TODO: When testing using AVA 6, enable for better managed timeouts.
- // t.timeout(10_000);
+ t.timeout(30_000);
const promise = Promise.all([delay(5000, null, {ref: false}), next?.()]).finally(() => {
if (idlePromise === promise) {
idlePromise = new Promise(() => {});
assertingIdle = false;
- // TODO: When testing using AVA 6, enable for better managed timeouts.
- // t.timeout.clear();
+ t.timeout.clear();
if (failedIdleAssertion) {
failedIdleAssertion = false;
t.fail('Watcher performed a test run while it should have been idle');
}
}
-
- return {};
- });
+ }).then(() => ({}));
idlePromise = promise;
await promise;
@@ -104,12 +100,32 @@ export const withFixture = fixture => async (t, task) => {
let state = {};
let pendingState;
+ let process;
+
+ t.teardown(async () => {
+ if (process?.connected) {
+ process.send('abort-watcher');
+ }
+
+ // Sending the `abort-watcher` message should suffice, but on Linux
+ // the recursive watch handle does not close properly. See
+ // but there seem to be
+ // other isues.
+ setTimeout(() => {
+ process.kill('SIGKILL');
+ }, 1000).unref();
+
+ try {
+ await process;
+ } catch {}
+ });
const results = run(args, options);
try {
let nextResult = results.next();
- while (true) { // eslint-disable-line no-constant-condition
+ while (!isDone) { // eslint-disable-line no-unmodified-loop-condition
const item = await Promise.race([nextResult, idlePromise, donePromise]); // eslint-disable-line no-await-in-loop
+ process ??= item.value?.process;
if (item.value) {
failedIdleAssertion ||= assertingIdle;
@@ -124,8 +140,8 @@ export const withFixture = fixture => async (t, task) => {
}
}
- if (item.done || isDone) {
- item.value?.process.send('abort-watcher');
+ if (item.done) {
+ await pendingState; // eslint-disable-line no-await-in-loop
break;
}
}