From 344ff3322c81f26c881e818fb1e38554225aaf72 Mon Sep 17 00:00:00 2001 From: Mark Wubben Date: Sun, 23 Oct 2022 16:12:37 +0200 Subject: [PATCH] Require opt-in to AVA 5's watcher, remove chokidar from dependencies --- docs/recipes/watch-mode.md | 17 ++++++++-- lib/{watcher.js => ava5-watcher.js} | 9 +++-- lib/cli.js | 29 ++++++++++------ lib/runner.js | 2 +- ...-tracker.js => ava5-dependency-tracker.js} | 0 lib/worker/base.js | 17 +++++++--- package-lock.json | 13 +++++-- package.json | 7 ++-- test-tap/api.js | 34 ------------------- 9 files changed, 70 insertions(+), 58 deletions(-) rename lib/{watcher.js => ava5-watcher.js} (97%) rename lib/worker/{dependency-tracker.js => ava5-dependency-tracker.js} (100%) diff --git a/docs/recipes/watch-mode.md b/docs/recipes/watch-mode.md index 26a3adabe..3b6935f20 100644 --- a/docs/recipes/watch-mode.md +++ b/docs/recipes/watch-mode.md @@ -4,6 +4,15 @@ Translations: [Français](https://github.com/avajs/ava-docs/blob/main/fr_FR/docs AVA comes with an intelligent watch mode. It watches for files to change and runs just those tests that are affected. +AVA 6 is introducing a new watch mode that relies on recurse file watching in Node.js. To use the old watch mode, set the `watcher` configuration to `ava5+chokidar` and install [`chokidar`] alongside AVA: + +`ava.config.mjs`: +```js +export default { + watcher: 'ava5+chokidar' +} +``` + ## Running tests with watch mode enabled You can enable watch mode using the `--watch` or `-w` flags: @@ -16,7 +25,9 @@ Please note that integrated debugging and the TAP reporter are unavailable when ## Requirements -AVA 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. +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. + +The same applies with AVA 6 when using the `ava5+chokidar` watcher. However you'll need to install `chokidar` separately. ## Ignoring changes @@ -30,7 +41,9 @@ 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. -Dependency tracking works for required modules. 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. Files accessed using the `fs` module are not tracked. +AVA 5 (and the `ava5+chokidar` watcher in AVA 6) 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. + +Files accessed using the `fs` module are not tracked. ## Watch mode and the `.only` modifier diff --git a/lib/watcher.js b/lib/ava5-watcher.js similarity index 97% rename from lib/watcher.js rename to lib/ava5-watcher.js index 63f5cea7c..c3f4dca92 100644 --- a/lib/watcher.js +++ b/lib/ava5-watcher.js @@ -169,11 +169,16 @@ export default class Watcher { trackTestDependencies(api) { api.on('run', plan => { plan.status.on('stateChange', evt => { - if (evt.type !== 'dependencies') { + let dependencies; + if (evt.type === 'dependencies') { + dependencies = evt.dependencies; + } else if (evt.type === 'accessed-snapshots') { + dependencies = [evt.filename]; + } else { return; } - const dependencies = evt.dependencies.filter(filePath => { + dependencies = dependencies.filter(filePath => { const {isIgnoredByWatcher} = classify(filePath, this.globs); return !isIgnoredByWatcher; }); diff --git a/lib/cli.js b/lib/cli.js index b42112483..303dda64a 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -25,7 +25,6 @@ import pkg from './pkg.cjs'; import providerManager from './provider-manager.js'; import DefaultReporter from './reporters/default.js'; import TapReporter from './reporters/tap.js'; -import Watcher from './watcher.js'; function exit(message) { console.error(`\n ${chalk.red(figures.cross)} ${message}`); @@ -415,6 +414,7 @@ export default async function loadCli() { // eslint-disable-line complexity concurrency: combined.concurrency || 0, workerThreads: combined.workerThreads !== false, debug, + enableAva5DependencyTracking: argv.watch && conf.watcher === 'ava5+chokidar', environmentVariables, experiments, extensions, @@ -476,15 +476,24 @@ export default async function loadCli() { // eslint-disable-line complexity }); if (argv.watch) { - const watcher = new Watcher({ - api, - filter, - globs, - projectDir, - providers, - reporter, - }); - watcher.observeStdin(process.stdin); + if (Object.hasOwn(conf, 'watcher')) { + if (conf.watcher === 'ava5+chokidar') { + const {default: Watcher} = await import('./ava5-watcher.js'); + const watcher = new Watcher({ + api, + filter, + globs, + projectDir, + providers, + reporter, + }); + watcher.observeStdin(process.stdin); + } else { + exit('The "watcher" option must be set to "ava5+chokidar"'); + } + } else { + exit('TODO'); + } } else { let debugWithoutSpecificFile = false; api.on('run', plan => { diff --git a/lib/runner.js b/lib/runner.js index bb5dd647e..e5e878376 100644 --- a/lib/runner.js +++ b/lib/runner.js @@ -209,7 +209,7 @@ export default class Runner extends Emittery { updating: this.updateSnapshots, }); if (snapshots.snapPath !== undefined) { - this.emit('dependency', snapshots.snapPath); + this.emit('accessed-snapshots', snapshots.snapPath); } this._snapshots = snapshots; diff --git a/lib/worker/dependency-tracker.js b/lib/worker/ava5-dependency-tracker.js similarity index 100% rename from lib/worker/dependency-tracker.js rename to lib/worker/ava5-dependency-tracker.js diff --git a/lib/worker/base.js b/lib/worker/base.js index 63c8a7db5..9db2812f6 100644 --- a/lib/worker/base.js +++ b/lib/worker/base.js @@ -14,8 +14,9 @@ import providerManager from '../provider-manager.js'; import Runner from '../runner.js'; import serializeError from '../serialize-error.js'; +// TODO: Delete along with ava5+chokidar watcher. +import dependencyTracking from './ava5-dependency-tracker.js'; import channel from './channel.cjs'; -import dependencyTracking from './dependency-tracker.js'; import lineNumberSelection from './line-numbers.js'; import {set as setOptions} from './options.cjs'; import {flags, refs, sharedWorkerTeardowns} from './state.cjs'; @@ -99,7 +100,11 @@ const run = async options => { runner.interrupt(); }); - runner.on('dependency', dependencyTracking.track); + runner.on('accessed-snapshots', filename => channel.send({type: 'accessed-snapshots', filename})); + if (options.enableAva5DependencyTracking) { + runner.on('dependency', dependencyTracking.track); + } + runner.on('stateChange', state => channel.send(state)); runner.on('error', error => { @@ -229,9 +234,11 @@ const run = async options => { } } - // Install dependency tracker after the require configuration has been evaluated - // to make sure we also track dependencies with custom require hooks - dependencyTracking.install(require.extensions, testPath); + if (options.enableAva5DependencyTracking) { + // Install dependency tracker after the require configuration has been evaluated + // to make sure we also track dependencies with custom require hooks + dependencyTracking.install(require.extensions, testPath); + } if (options.debug && options.debug.port !== undefined && options.debug.host !== undefined) { // If an inspector was active when the main process started, and is diff --git a/package-lock.json b/package-lock.json index d14f9e3de..0bbc841c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,6 @@ "callsites": "^4.0.0", "cbor": "^8.1.0", "chalk": "^5.2.0", - "chokidar": "^3.5.3", "chunkd": "^2.0.1", "ci-info": "^3.8.0", "ci-parallel-vars": "^1.0.1", @@ -76,11 +75,15 @@ "node": "^16.18 || ^18.16 || ^20.3" }, "peerDependencies": { - "@ava/typescript": "*" + "@ava/typescript": "*", + "chokidar": "^3.5.3" }, "peerDependenciesMeta": { "@ava/typescript": { "optional": true + }, + "chokidar": { + "optional": true } } }, @@ -1509,6 +1512,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "devOptional": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -1684,6 +1688,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "devOptional": true, "engines": { "node": ">=8" } @@ -1995,6 +2000,7 @@ "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "devOptional": true, "funding": [ { "type": "individual", @@ -4578,6 +4584,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "devOptional": true, "dependencies": { "binary-extensions": "^2.0.0" }, @@ -6159,6 +6166,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -7414,6 +7422,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "devOptional": true, "dependencies": { "picomatch": "^2.2.1" }, diff --git a/package.json b/package.json index 07101303d..3cc5c5a4d 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,6 @@ "callsites": "^4.0.0", "cbor": "^8.1.0", "chalk": "^5.2.0", - "chokidar": "^3.5.3", "chunkd": "^2.0.1", "ci-info": "^3.8.0", "ci-parallel-vars": "^1.0.1", @@ -142,11 +141,15 @@ "zen-observable": "^0.10.0" }, "peerDependencies": { - "@ava/typescript": "*" + "@ava/typescript": "*", + "chokidar": "^3.5.3" }, "peerDependenciesMeta": { "@ava/typescript": { "optional": true + }, + "chokidar": { + "optional": true } }, "volta": { diff --git a/test-tap/api.js b/test-tap/api.js index 0697b2257..6cc7daf89 100644 --- a/test-tap/api.js +++ b/test-tap/api.js @@ -418,40 +418,6 @@ for (const opt of options) { }); }); - test(`emits dependencies for test files - workerThreads: ${opt.workerThreads}`, async t => { - t.plan(8); - - const api = await apiCreator({ - ...opt, - files: ['test-tap/fixture/with-dependencies/*test*.cjs'], - require: [path.resolve('test-tap/fixture/with-dependencies/require-custom.cjs')], - }); - - const testFiles = new Set([ - path.resolve('test-tap/fixture/with-dependencies/no-tests.cjs'), - path.resolve('test-tap/fixture/with-dependencies/test.cjs'), - path.resolve('test-tap/fixture/with-dependencies/test-failure.cjs'), - path.resolve('test-tap/fixture/with-dependencies/test-uncaught-exception.cjs'), - ]); - - const sourceFiles = [ - path.resolve('test-tap/fixture/with-dependencies/dep-1.js'), - path.resolve('test-tap/fixture/with-dependencies/dep-2.js'), - path.resolve('test-tap/fixture/with-dependencies/dep-3.custom'), - ]; - - api.on('run', plan => { - plan.status.on('stateChange', evt => { - if (evt.type === 'dependencies') { - t.ok(testFiles.has(evt.testFile)); - t.strictSame(evt.dependencies.filter(dep => !dep.endsWith('.snap')).slice(-3), sourceFiles); - } - }); - }); - - return api.run(); - }); - test(`verify test count - workerThreads: ${opt.workerThreads}`, async t => { t.plan(4);