Skip to content

Commit

Permalink
Rewrite watcher
Browse files Browse the repository at this point in the history
  • Loading branch information
novemberborn committed Nov 15, 2022
1 parent e3af70a commit 82dbdd5
Show file tree
Hide file tree
Showing 12 changed files with 946 additions and 491 deletions.
8 changes: 3 additions & 5 deletions docs/recipes/watch-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ $ npx ava --watch

Please note that integrated debugging and the TAP reporter are unavailable when using watch mode.

## Requirements
## Limitations

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 uses `fs.watch()`. On supported platforms, `recursive` mode is used. When not supported, `fs.watch()` is used with individual directories, based on the dependency tracking (see below). This is an approximation. `recursive` mode is available with Node.js 19.1 on Linux, MacOS and Windows so we will not attempt to improve our fallback implementation.

## Ignoring changes

Expand All @@ -30,7 +30,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.

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.
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.

## Watch mode and the `.only` modifier

Expand Down Expand Up @@ -60,8 +60,6 @@ $ DEBUG=ava:watcher npx ava --watch

Watch mode is relatively new and there might be some rough edges. Please [report](https://github.com/avajs/ava/issues) any issues you encounter. Thanks!

[`chokidar`]: https://github.com/paulmillr/chokidar
[Install Troubleshooting]: https://github.com/paulmillr/chokidar#install-troubleshooting
[`ignore-by-default`]: https://github.com/novemberborn/ignore-by-default
[`.only` modifier]: ../01-writing-tests.md#running-specific-tests
[config]: ../06-configuration.md
6 changes: 3 additions & 3 deletions lib/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -200,15 +200,14 @@ export default class Api extends Emittery {

await this.emit('run', {
bailWithoutReporting: debugWithoutSpecificFile,
clearLogOnNextRun: runtimeOptions.clearLogOnNextRun === true,
debug: Boolean(this.options.debug),
failFastEnabled: failFast,
filePathPrefix: getFilePathPrefix(selectedFiles),
files: selectedFiles,
matching: apiOptions.match.length > 0,
previousFailures: runtimeOptions.previousFailures || 0,
runOnlyExclusive: runtimeOptions.runOnlyExclusive === true,
runVector: runtimeOptions.runVector || 0,
firstRun: runtimeOptions.firstRun ?? true,
status: runStatus,
});

Expand Down Expand Up @@ -306,7 +305,8 @@ export default class Api extends Emittery {

// Allow shared workers to clean up before the run ends.
await Promise.all(deregisteredSharedWorkers);
scheduler.storeFailedTestFiles(runStatus, this.options.cacheEnabled === false ? null : this._createCacheDir());
const files = scheduler.storeFailedTestFiles(runStatus, this.options.cacheEnabled === false ? null : this._createCacheDir());
runStatus.emitStateChange({type: 'touched-files', files});
} catch (error) {
if (error && error.name === 'AggregateError') {
for (const error_ of error.errors) {
Expand Down
6 changes: 3 additions & 3 deletions lib/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ 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';
import {start as startWatcher} from './watcher.js';

function exit(message) {
console.error(`\n ${chalk.red(figures.cross)} ${message}`);
Expand Down Expand Up @@ -469,15 +469,15 @@ export default async function loadCli() { // eslint-disable-line complexity
});

if (combined.watch) {
const watcher = new Watcher({
startWatcher({
api,
filter,
globs,
projectDir,
providers,
reporter,
stdin: process.stdin,
});
watcher.observeStdin(process.stdin);
} else {
let debugWithoutSpecificFile = false;
api.on('run', plan => {
Expand Down
10 changes: 1 addition & 9 deletions lib/glob-helpers.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ const defaultPicomatchIgnorePatterns = [
...defaultIgnorePatterns.map(pattern => `${pattern}/**/*`),
];

const defaultMatchNoIgnore = picomatch(defaultPicomatchIgnorePatterns);

const matchingCache = new WeakMap();
const processMatchingPatterns = input => {
let result = matchingCache.get(input);
Expand Down Expand Up @@ -45,15 +43,9 @@ const processMatchingPatterns = input => {

exports.processMatchingPatterns = processMatchingPatterns;

const matchesIgnorePatterns = (file, patterns) => {
const {matchNoIgnore} = processMatchingPatterns(patterns);
return matchNoIgnore(file) || defaultMatchNoIgnore(file);
};

function classify(file, {cwd, extensions, filePatterns, ignoredByWatcherPatterns}) {
function classify(file, {cwd, extensions, filePatterns}) {
file = normalizeFileForMatching(cwd, file);
return {
isIgnoredByWatcher: matchesIgnorePatterns(file, ignoredByWatcherPatterns),
isTest: hasExtension(extensions, file) && !isHelperish(file) && filePatterns.length > 0 && matches(file, filePatterns),
};
}
Expand Down
7 changes: 5 additions & 2 deletions lib/globs.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fs from 'node:fs';
import path from 'node:path';

import {globby, globbySync} from 'globby';
import picomatch from 'picomatch';

import {
defaultIgnorePatterns,
Expand Down Expand Up @@ -126,11 +127,13 @@ export async function findTests({cwd, extensions, filePatterns}) {
return files.filter(file => !path.basename(file).startsWith('_'));
}

export function getChokidarIgnorePatterns({ignoredByWatcherPatterns}) {
return [
export function buildIgnoreMatcher({ignoredByWatcherPatterns}) {
const patterns = [
...defaultIgnorePatterns.map(pattern => `${pattern}/**/*`),
...ignoredByWatcherPatterns.filter(pattern => !pattern.startsWith('!')),
];

return picomatch(patterns, {dot: true});
}

export function applyTestFileFilter({ // eslint-disable-line complexity
Expand Down
2 changes: 1 addition & 1 deletion lib/reporters/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export default class Reporter {
this.consumeStateChange(evt);
});

if (this.watching && plan.runVector > 1) {
if (this.watching && !plan.firstRun) {
this.lineWriter.write(chalk.gray.dim('\u2500'.repeat(this.lineWriter.columns)) + os.EOL);
}

Expand Down
15 changes: 14 additions & 1 deletion lib/scheduler.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,22 @@ const scheduler = {
return;
}

const filename = path.join(cacheDir, FILENAME);
// Given that we're writing to a cache directory, consider this file
// temporary.
const temporaryFiles = [filename];
try {
writeFileAtomic.sync(path.join(cacheDir, FILENAME), JSON.stringify(runStatus.getFailedTestFiles()));
writeFileAtomic.sync(filename, JSON.stringify(runStatus.getFailedTestFiles()), {
tmpfileCreated(tmpfile) {
temporaryFiles.push(tmpfile);
},
});
} catch {}

return {
changedFiles: [],
temporaryFiles,
};
},

// Order test-files, so that files with failing tests come first
Expand Down
Loading

0 comments on commit 82dbdd5

Please sign in to comment.