From 316ffe12cfa29a893fb721403caee6cb62a264f4 Mon Sep 17 00:00:00 2001 From: Christian Rackerseder Date: Wed, 20 Dec 2023 17:49:42 +0100 Subject: [PATCH 01/11] Fix typo in README --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 35faa09ff..77015cf17 100644 --- a/readme.md +++ b/readme.md @@ -4,7 +4,7 @@ # AVA logo -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. From 9f3bf7e4d3e32f212aa45dc2f5899f24462721d8 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 30 Dec 2023 20:58:44 +0100 Subject: [PATCH 02/11] Remove references to older AVA versions * Remove obsolete recipes ES modules is supported out of the box. The React recipe applies to AVA 3. * Remove code guards against obsolete API usage * Remove references to older AVA versions from documentation --- docs/01-writing-tests.md | 2 +- docs/03-assertions.md | 12 +- docs/recipes/es-modules.md | 3 - docs/recipes/react.md | 189 ------------------------- docs/recipes/typescript.md | 4 +- docs/recipes/watch-mode.md | 12 +- lib/assert.js | 7 - lib/runner.js | 6 +- lib/test.js | 6 +- readme.md | 2 +- test-tap/assert.js | 13 -- test-tap/reporters/tap.regular.v18.log | 4 +- test-tap/reporters/tap.regular.v20.log | 4 +- test-tap/reporters/tap.regular.v21.log | 4 +- test/multiple-implementations/test.js | 4 +- 15 files changed, 21 insertions(+), 251 deletions(-) delete mode 100644 docs/recipes/es-modules.md delete mode 100644 docs/recipes/react.md diff --git a/docs/01-writing-tests.md b/docs/01-writing-tests.md index abec90c38..35c4a9022 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`. 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/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( -
- bar -
- bar -
- ); - 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/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/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/readme.md b/readme.md index 77015cf17..fd7161e04 100644 --- a/readme.md +++ b/readme.md @@ -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/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/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/); }); From 783f62b94df69f388b4ae773fbde0a84f06671e2 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 30 Dec 2023 21:21:43 +0100 Subject: [PATCH 03/11] Only test reporters on Linux (CI) These tests are more likely to be flaky on Mac and Windows. --- scripts/ci.sh | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/ci.sh b/scripts/ci.sh index 390570328..6eb746fd4 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 + +# 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 From 70a6e257b35d3aab46d5159e611fb4a33ae172f6 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 30 Dec 2023 21:24:39 +0100 Subject: [PATCH 04/11] Ensure watcher tests exit cleanly Always await the last pending state when the watch runs have completed. Use a teardown hook to ensure the watcher is aborted and has exited before ending the test. Ensure the item is always an object, even if it didn't come from the generator. --- test/watch-mode/helpers/watch.js | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/test/watch-mode/helpers/watch.js b/test/watch-mode/helpers/watch.js index ab553cdae..b000470e9 100644 --- a/test/watch-mode/helpers/watch.js +++ b/test/watch-mode/helpers/watch.js @@ -94,9 +94,7 @@ export const withFixture = fixture => async (t, task) => { t.fail('Watcher performed a test run while it should have been idle'); } } - - return {}; - }); + }).then(() => ({})); idlePromise = promise; await promise; @@ -104,12 +102,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 +142,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; } } From 808a5613c960c8470522147aa0c82f31439a0445 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Thu, 11 Jan 2024 21:44:57 +0100 Subject: [PATCH 05/11] Change recursive watch mode test detection to be non-persistent Otherwise, on Linux, it seems the abort signal is disregarded. --- lib/watcher.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From fde8671abdb729e9d7e7a3e29aeaa01937e8611a Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 30 Dec 2023 21:26:49 +0100 Subject: [PATCH 06/11] Test with AVA 6 --- ava.config.js | 4 +- package-lock.json | 407 ++----------------------------- scripts/ci.sh | 6 +- test/watch-mode/helpers/watch.js | 6 +- 4 files changed, 30 insertions(+), 393 deletions(-) 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/package-lock.json b/package-lock.json index aee814ebf..d5555e8f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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/scripts/ci.sh b/scripts/ci.sh index 6eb746fd4..2838c77e7 100755 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -4,9 +4,9 @@ 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 +# 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 diff --git a/test/watch-mode/helpers/watch.js b/test/watch-mode/helpers/watch.js index b000470e9..32c00ced1 100644 --- a/test/watch-mode/helpers/watch.js +++ b/test/watch-mode/helpers/watch.js @@ -80,15 +80,13 @@ 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'); From 35f6c86bcddcd251910775f13c52aded6bbed4e5 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Thu, 11 Jan 2024 21:59:28 +0100 Subject: [PATCH 07/11] Fix potential bug with watch mode when no failed test files are written Don't emit the 'touched-files' event, since the watcher assumes that comes with a files value. Fixes #3285 --- lib/api.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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)}); } From cc8b8397247a843a178cf732274823b13787c1f6 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Tue, 2 Jan 2024 22:26:44 +0100 Subject: [PATCH 08/11] Ensure AVA exits with code 1 after an unexpected process.exit() in a test worker It prints the error, so it should fail as such. --- lib/run-status.js | 5 +++++ 1 file changed, 5 insertions(+) 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; From 0a050243ea13df959e6c1536b2513b5fe3557690 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sat, 30 Dec 2023 22:09:34 +0100 Subject: [PATCH 09/11] Implement registerCompletionHandler() Register a function to be called when AVA has completed a test run without uncaught exceptions or unhandled rejections. Fixes #3279. * * Completion handlers are invoked in order of registration. Results are not awaited. --- docs/01-writing-tests.md | 2 +- docs/07-test-timeouts.md | 2 +- docs/08-common-pitfalls.md | 31 +++++++++++++++++++ entrypoints/main.d.mts | 8 +++++ entrypoints/main.mjs | 1 + lib/worker/base.js | 14 ++++++--- lib/worker/completion-handlers.js | 13 ++++++++ lib/worker/state.cjs | 1 + test/completion-handlers/fixtures/exit0.js | 7 +++++ test/completion-handlers/fixtures/one.js | 9 ++++++ .../completion-handlers/fixtures/package.json | 8 +++++ test/completion-handlers/fixtures/two.js | 10 ++++++ test/completion-handlers/test.js | 17 ++++++++++ 13 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 lib/worker/completion-handlers.js create mode 100644 test/completion-handlers/fixtures/exit0.js create mode 100644 test/completion-handlers/fixtures/one.js create mode 100644 test/completion-handlers/fixtures/package.json create mode 100644 test/completion-handlers/fixtures/two.js create mode 100644 test/completion-handlers/test.js diff --git a/docs/01-writing-tests.md b/docs/01-writing-tests.md index 35c4a9022..84836a1a5 100644 --- a/docs/01-writing-tests.md +++ b/docs/01-writing-tests.md @@ -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/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 [![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](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/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/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/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'])); +}); From c3e2c72e0a99738cf4720027243de98ce5bb2d69 Mon Sep 17 00:00:00 2001 From: Max Isom Date: Sun, 21 Jan 2024 13:54:42 -0600 Subject: [PATCH 10/11] Fix ava/internal ESM type module --- entrypoints/internal.d.mts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From aae39b20ba3ef80e5bedb1e5882432a3cd7c44eb Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 21 Jan 2024 21:03:08 +0100 Subject: [PATCH 11/11] 6.1.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index d5555e8f2..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", 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",