Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove snapshot files when a test file stops using snapshots #2623

Merged
merged 36 commits into from
Dec 31, 2020
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
bc722d5
Test snapshot removal and edge cases
ninevra Dec 7, 2020
b55285a
no-push: disable crashing lint rule
ninevra Dec 10, 2020
643678b
Remove snapshot files when tests stop using them
ninevra Dec 6, 2020
5e9035c
Test with watcher
ninevra Dec 12, 2020
84a0433
Report pruned snapshot files as touched
ninevra Dec 12, 2020
375d82d
Refactor saveSnapshotState()
ninevra Dec 12, 2020
9f2be0d
Apply same conditions to updating, pruning snapshots
ninevra Dec 14, 2020
2bf983f
Test that t.snapshot.skip() prevents updating snapshots
ninevra Dec 15, 2020
6d577a7
Revert undesired behavior
ninevra Dec 15, 2020
39d2860
Remove tests of undesired behavior
ninevra Dec 15, 2020
daa87ff
Revert "no-push: disable crashing lint rule"
ninevra Dec 15, 2020
5a3f3bb
Reimplement snapshot.skip() check as runner callback
ninevra Dec 16, 2020
2eaad19
Rename skippedSnapshots to skippingSnapshots
ninevra Dec 16, 2020
b1171fa
Test with snapshot.skip() in a discarded try()
ninevra Dec 16, 2020
1e151ec
Prefer snapshotting test results where practical
ninevra Dec 16, 2020
f4a4ab0
Unit-test skipSnapshot() hooks
ninevra Dec 16, 2020
8efaf26
Refactor test fixtures
ninevra Dec 16, 2020
50578d6
Force not-ci to fix a CI-dependent test failure
ninevra Dec 16, 2020
ef354e2
Revert "Rename skippedSnapshots to skippingSnapshots"
ninevra Dec 17, 2020
2ef3a53
Test that absent snapshots don't throw
ninevra Dec 18, 2020
f25dc52
Remove duplicate line number selection check
ninevra Dec 18, 2020
7018963
Use temporary copies of test fixtures
ninevra Dec 18, 2020
bc3adf9
Clean up, refactor test fixtures
ninevra Dec 18, 2020
bf7eeaf
Consolidate tests in one file
ninevra Dec 19, 2020
2e23b66
Refactor macro exports
ninevra Dec 19, 2020
8281c51
Merge branch 'master' into prune-snapshots-merge-master
ninevra Dec 19, 2020
b7b708d
Restyle watcher-rerun-unlink test fixture
ninevra Dec 20, 2020
d98f697
Document return type of cleanSnapshots()
ninevra Dec 20, 2020
6d022c7
Test that snapshots aren't spuriously removed
ninevra Dec 20, 2020
a5a81d7
Remove a redundant test
ninevra Dec 20, 2020
52c1bd5
Update snapshots
ninevra Dec 20, 2020
9775e6f
Avoid creating skipSnapshotHook variable
ninevra Dec 31, 2020
d14ef0a
Use sync operations in cleanSnapshots()
ninevra Dec 31, 2020
8774c3c
Merge branch 'master' into prune-snapshots
novemberborn Dec 31, 2020
76a93c5
Reuse file cleaning function
novemberborn Dec 31, 2020
23a43bb
Clarify AVA import in fixtures
novemberborn Dec 31, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 22 additions & 5 deletions lib/runner.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class Runner extends Emittery {

this.activeRunnables = new Set();
this.boundCompareTestSnapshot = this.compareTestSnapshot.bind(this);
this.skippedSnapshots = false;
this.boundSkipSnapshot = this.skipSnapshot.bind(this);
this.interrupted = false;
this.snapshots = null;
this.nextTaskIndex = 0;
Expand Down Expand Up @@ -199,8 +201,19 @@ class Runner extends Emittery {
return this.snapshots.compare(options);
}

saveSnapshotState() {
if (this.updateSnapshots && (this.runOnlyExclusive || this.skippingTests)) {
skipSnapshot() {
this.skippedSnapshots = true;
}

async saveSnapshotState() {
if (
this.updateSnapshots &&
(
this.runOnlyExclusive ||
this.skippingTests ||
this.skippedSnapshots
)
) {
return {cannotSave: true};
}

Expand All @@ -209,9 +222,11 @@ class Runner extends Emittery {
}

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!)
// were skipped. Perhaps emit a warning if this occurs?
return {touchedFiles: await snapshotManager.cleanSnapshots({
file: this.file,
fixedLocation: this.snapshotDir,
projectDir: this.projectDir
})};
}

return {};
Expand Down Expand Up @@ -297,6 +312,7 @@ class Runner extends Emittery {
task.implementation :
t => task.implementation.apply(null, [t].concat(task.args)),
compareTestSnapshot: this.boundCompareTestSnapshot,
skipSnapshot: this.boundSkipSnapshot,
updateSnapshots: this.updateSnapshots,
metadata: {...task.metadata, associatedTaskIndex},
powerAssert: this.powerAssert,
Expand Down Expand Up @@ -349,6 +365,7 @@ class Runner extends Emittery {
task.implementation :
t => task.implementation.apply(null, [t].concat(task.args)),
compareTestSnapshot: this.boundCompareTestSnapshot,
skipSnapshot: this.boundSkipSnapshot,
updateSnapshots: this.updateSnapshots,
metadata: task.metadata,
powerAssert: this.powerAssert,
Expand Down
39 changes: 38 additions & 1 deletion lib/snapshot-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -449,12 +449,49 @@ const determineSnapshotDir = mem(({file, fixedLocation, projectDir}) => {

exports.determineSnapshotDir = determineSnapshotDir;

function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) {
function determineSnapshotPaths({file, fixedLocation, projectDir}) {
const dir = determineSnapshotDir({file, fixedLocation, projectDir});
const relFile = path.relative(projectDir, resolveSourceFile(file));
const name = path.basename(relFile);
const reportFile = `${name}.md`;
const snapFile = `${name}.snap`;

return {
dir,
relFile,
snapFile,
reportFile
};
}

// Remove snapshot and report if they exist. Returns an array containing the
// paths of the touched files.
async function cleanSnapshots({file, fixedLocation, projectDir}) {
const {dir, snapFile, reportFile} = determineSnapshotPaths({file, fixedLocation, projectDir});

async function clean(file) {
try {
await fs.promises.unlink(file);
return [file];
} catch (error) {
if (error.code === 'ENOENT') {
return [];
}

throw error;
}
}

return [
...await clean(path.join(dir, snapFile)),
...await clean(path.join(dir, reportFile))
novemberborn marked this conversation as resolved.
Show resolved Hide resolved
];
}

exports.cleanSnapshots = cleanSnapshots;

function load({file, fixedLocation, projectDir, recordNewSnapshots, updating}) {
const {dir, relFile, snapFile, reportFile} = determineSnapshotPaths({file, fixedLocation, projectDir});
const snapPath = path.join(dir, snapFile);

let appendOnly = !updating;
Expand Down
4 changes: 4 additions & 0 deletions lib/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,11 @@ class Test {
return result;
};

const skipSnapshotHook = options.skipSnapshot || (() => {});
novemberborn marked this conversation as resolved.
Show resolved Hide resolved

this.skipSnapshot = () => {
skipSnapshotHook();

if (options.updateSnapshots) {
this.addFailedAssertion(new Error('Snapshot assertions cannot be skipped when updating snapshots'));
} else {
Expand Down
2 changes: 1 addition & 1 deletion lib/worker/subprocess.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ ipc.options.then(async options => {

runner.on('finish', async () => {
try {
const {cannotSave, touchedFiles} = runner.saveSnapshotState();
const {cannotSave, touchedFiles} = await runner.saveSnapshotState();
if (cannotSave) {
ipc.send({type: 'snapshot-error'});
} else if (touchedFiles) {
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@
"delay": "^4.4.0",
"esm": "^3.2.25",
"execa": "^5.0.0",
"fs-extra": "^9.0.1",
"get-stream": "^6.0.0",
"it-first": "^1.0.4",
"proxyquire": "^2.1.3",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
21 changes: 21 additions & 0 deletions test-tap/fixture/snapshots/watcher-rerun-unlink/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const test = require('../../../..');

if (process.env.TEMPLATE) {
test('test title', t => {
t.snapshot({foo: 'bar'});
t.snapshot({answer: 42});
t.pass();
});

test('another test', t => {
t.snapshot(new Map());
});
} else {
test('test title', t => {
t.pass();
});

test('another test', t => {
t.pass();
});
}
46 changes: 46 additions & 0 deletions test-tap/integration/watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,52 @@ test('watcher does not rerun test files when they write snapshot files', t => {
});
});

test('watcher does not rerun test files when they unlink snapshot files', t => {
// Run fixture as template to generate snapshots
execCli(
['--update-snapshots'],
{
dirname: 'fixture/snapshots/watcher-rerun-unlink',
env: {AVA_FORCE_CI: 'not-ci', TEMPLATE: 'true'}
},
err => {
t.ifError(err);

// Run fixture in watch mode; snapshots should be removed, and watcher should not rerun
let killed = false;

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

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

test('watcher does not rerun test files when ignored files change', t => {
let killed = false;

Expand Down
30 changes: 30 additions & 0 deletions test-tap/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,36 @@ test('snapshot assertion can be skipped', t => {
});
});

// Snapshots reused from test/assert.js
test('snapshot assertions call options.skipSnapshot when skipped', async t => {
const projectDir = path.join(__dirname, 'fixture');
const manager = snapshotManager.load({
file: path.join(projectDir, 'assert.js'),
projectDir,
fixedLocation: null,
updating: false
});

const skipSnapshot = sinon.spy();

const test = new Test({
compareTestSnapshot: options => manager.compare(options),
skipSnapshot,
updateSnapshots: false,
metadata: {},
title: 'passes',
fn(t) {
t.snapshot.skip({not: {a: 'match'}});
t.snapshot.skip({not: {b: 'match'}});
t.snapshot(React.createElement(HelloMessage, {name: 'Sindre'}));
}
});

await test.run();

t.true(skipSnapshot.calledTwice);
});

test('snapshot assertion cannot be skipped when updating snapshots', t => {
return new Test({
updateSnapshots: true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"ava": {
"snapshotDir": "fixedSnapshotDir"
}
}
22 changes: 22 additions & 0 deletions test/snapshot-removal/fixtures/fixed-snapshot-dir/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const test = require(process.env.AVA_PATH || 'ava');

if (process.env.TEMPLATE) {
test('some snapshots', t => {
t.snapshot('foo');
t.snapshot('bar');
t.pass();
});

test('another snapshot', t => {
t.snapshot('baz');
t.pass();
});
} else {
test('some snapshots', t => {
t.pass();
});

test('another snapshot', t => {
t.pass();
});
}
1 change: 1 addition & 0 deletions test/snapshot-removal/fixtures/no-snapshots/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
5 changes: 5 additions & 0 deletions test/snapshot-removal/fixtures/no-snapshots/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const test = require('ava');

test('without snapshots', t => {
t.pass();
});
1 change: 1 addition & 0 deletions test/snapshot-removal/fixtures/only-test/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
22 changes: 22 additions & 0 deletions test/snapshot-removal/fixtures/only-test/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const test = require(process.env.AVA_PATH || 'ava');

if (process.env.TEMPLATE) {
test('some snapshots', t => {
t.snapshot('foo');
t.snapshot('bar');
t.pass();
});

test('another snapshot', t => {
t.snapshot('baz');
t.pass();
});
} else {
test.only('some snapshots', t => {
t.pass();
});

test('another snapshot', t => {
t.pass();
});
}
1 change: 1 addition & 0 deletions test/snapshot-removal/fixtures/removal/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
22 changes: 22 additions & 0 deletions test/snapshot-removal/fixtures/removal/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const test = require(process.env.AVA_PATH || 'ava');

if (process.env.TEMPLATE) {
test('some snapshots', t => {
t.snapshot('foo');
t.snapshot('bar');
t.pass();
});

test('another snapshot', t => {
t.snapshot('baz');
t.pass();
});
} else {
test('some snapshots', t => {
t.pass();
});

test('another snapshot', t => {
t.pass();
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
23 changes: 23 additions & 0 deletions test/snapshot-removal/fixtures/skipped-snapshots-in-try/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const test = require(process.env.AVA_PATH || 'ava');

if (process.env.TEMPLATE) {
test('skipped snapshots in try', async t => {
const attempt = await t.try(tt => {
tt.snapshot('in try');
});

attempt.commit();

t.pass();
});
} else {
test('skipped snapshots in try', async t => {
const attempt = await t.try(tt => {
tt.snapshot.skip('in try');
});

attempt.discard();

t.pass();
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
23 changes: 23 additions & 0 deletions test/snapshot-removal/fixtures/skipped-snapshots/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const test = require(process.env.AVA_PATH || 'ava');

if (process.env.TEMPLATE) {
test('some snapshots', t => {
t.snapshot('foo');
t.snapshot('bar');
t.pass();
});

test('another snapshot', t => {
t.snapshot('baz');
t.pass();
});
} else {
test('some snapshots', t => {
t.snapshot.skip('foo');
t.pass();
});

test('another snapshot', t => {
t.pass();
});
}
1 change: 1 addition & 0 deletions test/snapshot-removal/fixtures/skipped-tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Loading