Skip to content

Commit

Permalink
Track snapshot files touched during test run, ignore in watcher
Browse files Browse the repository at this point in the history
Workers can emit which files were touched during the test run. This is
used to communicate to the watcher which snapshot file events to ignore
from the current test run, stopping the watcher from running the same
tests repeatedly.

Note that this is only an issue when the `sources` glob has been
customized by the user. The default glob excludes snapshot files.

Files are ignored only once, so subsequent edits of snapshot files
(e.g. by reverting a commit) will cause tests to be rerun.
  • Loading branch information
novemberborn committed Jun 25, 2017
1 parent 6d3c279 commit 50b60a1
Show file tree
Hide file tree
Showing 9 changed files with 80 additions and 10 deletions.
1 change: 1 addition & 0 deletions lib/run-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ class RunStatus extends EventEmitter {
}
handleTeardown(data) {
this.emit('dependencies', data.file, data.dependencies, this);
this.emit('touchedFiles', data.touchedFiles);
}
handleStats(stats) {
this.emit('stats', stats, this);
Expand Down
5 changes: 4 additions & 1 deletion lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,10 @@ class Runner extends EventEmitter {

saveSnapshotState() {
if (this.snapshots) {
this.snapshots.save();
const files = this.snapshots.save();
if (files) {
this.emit('touched', files);
}
} else if (this.updateSnapshots) {
// TODO: There may be unused snapshot files if no test caused the
// snapshots to be loaded. Prune them. But not if tests (including hooks!)
Expand Down
10 changes: 6 additions & 4 deletions lib/snapshot-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ const fs = require('fs');
const path = require('path');
const zlib = require('zlib');

const writeFileAtomic = require('@ava/write-file-atomic');
const concordance = require('concordance');
const indentString = require('indent-string');
const makeDir = require('make-dir');
const md5Hex = require('md5-hex');
const writeFileAtomic = require('write-file-atomic');

const concordanceOptions = require('./concordance-options').snapshotManager;

Expand Down Expand Up @@ -333,7 +333,7 @@ class Manager {

save() {
if (!this.hasChanges) {
return;
return null;
}

const snapPath = path.join(this.dir, this.snapFile);
Expand All @@ -346,8 +346,10 @@ class Manager {
generateReport(this.relFile, this.snapFile, this.reportEntries);

makeDir.sync(this.dir);
writeFileAtomic.sync(snapPath, buffer);
writeFileAtomic.sync(reportPath, reportBuffer);
const tmpSnapPath = writeFileAtomic.sync(snapPath, buffer);
const tmpReportPath = writeFileAtomic.sync(reportPath, reportBuffer);

return [tmpSnapPath, tmpReportPath, snapPath, reportPath];
}
}

Expand Down
12 changes: 10 additions & 2 deletions lib/test-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,17 @@ const testPath = opts.file;
const dependencies = [];
adapter.installDependencyTracking(dependencies, testPath);

const touchedFiles = new Set();

// Set when main.js is required (since test files should have `require('ava')`).
let runner = null;
exports.setRunner = newRunner => {
runner = newRunner;
runner.on('touched', files => {
for (const file of files) {
touchedFiles.add(file);
}
});
};

require(testPath);
Expand Down Expand Up @@ -123,8 +130,9 @@ process.on('ava-teardown', () => {

// Include dependencies in the final teardown message. This ensures the full
// set of dependencies is included no matter how the process exits, unless
// it flat out crashes.
adapter.send('teardown', {dependencies});
// it flat out crashes. Also include any files that AVA touched during the
// test run. This allows the watcher to ignore modifications to those files.
adapter.send('teardown', {dependencies, touchedFiles: Array.from(touchedFiles)});
});

process.on('ava-exit', () => {
Expand Down
22 changes: 21 additions & 1 deletion lib/watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class Watcher {
}
}

this.touchedFiles.clear();
this.busy = api.run(specificFiles || files, {runOnlyExclusive})
.then(runStatus => {
runStatus.previousFailCount = this.sumPreviousFailures(currentVector);
Expand All @@ -130,6 +131,9 @@ class Watcher {
this.testDependencies = [];
this.trackTestDependencies(api, sources);

this.touchedFiles = new Set();
this.trackTouchedFiles(api);

this.filesWithExclusiveTests = [];
this.trackExclusivity(api);

Expand Down Expand Up @@ -184,6 +188,15 @@ class Watcher {
this.testDependencies.push(new TestDependency(file, sources));
}
}
trackTouchedFiles(api) {
api.on('test-run', runStatus => {
runStatus.on('touchedFiles', files => {
for (const file of files) {
this.touchedFiles.add(nodePath.relative(process.cwd(), file));
}
});
});
}
trackExclusivity(api) {
api.on('stats', stats => {
this.updateExclusivity(stats.file, stats.hasExclusive);
Expand Down Expand Up @@ -284,7 +297,14 @@ class Watcher {
const dirtyStates = this.dirtyStates;
this.dirtyStates = {};

const dirtyPaths = Object.keys(dirtyStates);
const dirtyPaths = Object.keys(dirtyStates).filter(path => {
if (this.touchedFiles.has(path)) {
debug('Ignoring known touched file %s', path);
this.touchedFiles.delete(path);
return false;
}
return true;
});
const dirtyTests = dirtyPaths.filter(this.avaFiles.isTest);
const dirtySources = diff(dirtyPaths, dirtyTests);
const addedOrChangedTests = dirtyTests.filter(path => dirtyStates[path] !== 'unlink');
Expand Down
5 changes: 5 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@
"dependencies": {
"@ava/babel-preset-stage-4": "^1.1.0",
"@ava/babel-preset-transform-test-files": "^3.0.0",
"@ava/write-file-atomic": "^2.2.0",
"@concordance/react": "^0.2.0",
"ansi-escapes": "^2.0.0",
"ansi-styles": "^3.1.0",
Expand Down Expand Up @@ -170,8 +171,7 @@
"time-require": "^0.1.2",
"trim-off-newlines": "^1.0.1",
"unique-temp-dir": "^1.0.0",
"update-notifier": "^2.1.0",
"write-file-atomic": "^1.3.4"
"update-notifier": "^2.1.0"
},
"devDependencies": {
"cli-table2": "^0.2.0",
Expand Down
26 changes: 26 additions & 0 deletions test/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,32 @@ test('watcher reruns test files when source dependencies change', t => {
});
});

test('watcher does not rerun test files when they write snapshot files', t => {
let killed = false;

const child = execCli(['--verbose', '--watch', '--update-snapshots', 'test.js'], {dirname: 'fixture/snapshots'}, err => {
t.ok(killed);
t.ifError(err);
t.end();
});

let buffer = '';
let passedFirst = false;
child.stderr.on('data', str => {
buffer += str;
if (/2 tests passed/.test(buffer) && !passedFirst) {
buffer = '';
passedFirst = true;
setTimeout(() => {
child.kill();
killed = true;
}, 500);
} else if (passedFirst && !killed) {
t.is(buffer.replace(/\s/g, ''), '');
}
});
});

test('`"tap": true` config is ignored when --watch is given', t => {
let killed = false;

Expand Down
5 changes: 5 additions & 0 deletions test/fixture/snapshots/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"ava": {
"source": "**/*"
}
}

0 comments on commit 50b60a1

Please sign in to comment.