Skip to content

Commit

Permalink
List and clean obsolete snapshots (#113)
Browse files Browse the repository at this point in the history
* Add snapshot summary and detect/remove obsolete snapshots

* Add `--clean-snapshot`/--wipe-snapshot` flag

* Check for obsolete snapshots only when all tests are green

* Fix wrong name in function object

* Add basic documentation of `--clean-snapshot`

* Update docs

* Add version label
  • Loading branch information
lukfor authored Sep 2, 2023
1 parent d7ea684 commit 27089df
Show file tree
Hide file tree
Showing 17 changed files with 389 additions and 119 deletions.
19 changes: 18 additions & 1 deletion docs/docs/assertions/snapshots.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Snapshots
:octicons-tag-24: 0.7.0 ·
:octicons-tag-24: 0.7.0

Snapshots are a very useful tool whenever you want to make sure your output channels or output files not change unexpectedly. This feature is highly inspired by [Jest](https://jestjs.io/).

Expand Down Expand Up @@ -38,6 +38,23 @@ When a snapshot test is failing due to an intentional implementation change, you
nf-test test tests/main.nf.test --update-snapshot
```

## Cleaning Obsolete Snapshots

:octicons-tag-24: 0.8.0

Over time, snapshots can become outdated, leading to inconsistencies in your testing process. To help you manage obsolete snapshots, nf-test generates a list of these obsolete keys.
This list provides transparency into which snapshots are no longer needed and can be safely removed.

Running your tests with the `--clean-snapshot`or `--wipe-snapshot` option removes the obsolete snapshots from the snapshot file.
This option is useful when you want to maintain the structure of your snapshot file but remove unused entries.
It ensures that your snapshot file only contains the snapshots required for your current tests, reducing file bloat and improving test performance.

```
nf-test test tests/main.nf.test --clean-snapshot
```

>:bulb: Obsolete snapshots can only be detected when running all tests in a test file simultaneously, and when all tests pass. If you run a single test or if tests are skipped, nf-test cannot detect obsolete snapshots.
## More Examples

It is also possible to include multiple objects into one snapshot:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public class RunTestsCommand extends AbstractCommand {
@Option(names = { "--update-snapshot",
"--updateSnapshot" }, description = "Use this flag to re-record every snapshot that fails during this test run.", required = false, showDefaultValue = Visibility.ALWAYS)
private boolean updateSnapshot = false;

@Option(names = { "--clean-snapshot", "--cleanSnapshot", "--wipe-snapshot",
"--wipeSnapshot" }, description = "Removes all obsolete snapshots.", required = false, showDefaultValue = Visibility.ALWAYS)
private boolean cleanSnapshot = false;

@Option(names = {
"--lib" }, description = "Library extension path", required = false, showDefaultValue = Visibility.ALWAYS)
private String lib = "";
Expand Down Expand Up @@ -148,6 +153,7 @@ public Integer execute() throws Exception {
engine.setDebug(debug);
engine.setWorkDir(workDir);
engine.setUpdateSnapshot(updateSnapshot);
engine.setCleanSnapshot(cleanSnapshot);
engine.setLibDir(libDir);
engine.setPluginManager(manager);

Expand Down
38 changes: 38 additions & 0 deletions src/main/java/com/askimed/nf/test/core/AbstractTestSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.Vector;

import com.askimed.nf.test.config.Config;
import com.askimed.nf.test.lang.extensions.SnapshotFile;

public abstract class AbstractTestSuite implements ITestSuite {

Expand All @@ -26,6 +27,10 @@ public abstract class AbstractTestSuite implements ITestSuite {

private String directory = "";

private SnapshotFile snapshotFile;

private boolean failedTests = false;

private List<String> tags = new Vector<String>();

@Override
Expand Down Expand Up @@ -135,6 +140,39 @@ public ITaggable getParent() {
return null;
}

@Override
public boolean hasSkippedTests() {
for (ITest test : getTests()) {
if (test.isSkipped()) {
return true;
}
}
return false;
}

@Override
public SnapshotFile getSnapshot() {
if (snapshotFile == null) {
snapshotFile = SnapshotFile.loadByTestSuite(this);
}
return snapshotFile;
}

@Override
public boolean hasSnapshotLoaded() {
return (snapshotFile != null);
}

@Override
public void setFailedTests(boolean failedTests) {
this.failedTests = failedTests;
}

@Override
public boolean hasFailedTests() {
return failedTests;
}

protected String makeAbsolute(String path) {
return new File(directory, path).getAbsolutePath();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

package com.askimed.nf.test.core;

import com.askimed.nf.test.lang.extensions.SnapshotFile;
import com.askimed.nf.test.util.AnsiColors;
import com.askimed.nf.test.util.AnsiText;

Expand All @@ -16,6 +17,12 @@ public class AnsiTestExecutionListener implements ITestExecutionListener {

private boolean debug = false;

private int updatedSnapshots;

private int createdSnapshots;

private int obsoleteSnapshots;

public static final int TEST_PADDING = 2;

@Override
Expand All @@ -33,6 +40,22 @@ public void testPlanExecutionFinished() {
double executionTime = ((end - start) / 1000.0);

System.out.println();

if ((updatedSnapshots + createdSnapshots + obsoleteSnapshots) > 0) {
System.out.println();
System.out.println("Snapshot Summary:");
}
if (updatedSnapshots > 0) {
System.out.println(AnsiText.padding(updatedSnapshots + " updated", TEST_PADDING));
}
if (createdSnapshots > 0) {
System.out.println(AnsiText.padding(createdSnapshots + " created", TEST_PADDING));
}

if (obsoleteSnapshots > 0) {
System.out.println(AnsiColors.yellow(AnsiText.padding(obsoleteSnapshots + " obsolete", TEST_PADDING)));
}

System.out.println();

if (failed > 0) {
Expand Down Expand Up @@ -63,6 +86,46 @@ public void testSuiteExecutionStarted(ITestSuite testSuite) {
@Override
public void testSuiteExecutionFinished(ITestSuite testSuite) {

if (!testSuite.hasSnapshotLoaded()) {
return;
}

SnapshotFile snapshot = testSuite.getSnapshot();
if ((snapshot.getUpdatedSnapshots().size() + snapshot.getCreatedSnapshots().size()
+ snapshot.getObsoleteSnapshots().size()) > 0 || testSuite.hasSkippedTests()) {
System.out.println(AnsiText.padding("Snapshots:", TEST_PADDING));
}
if (snapshot.getUpdatedSnapshots().size() > 0) {
System.out.println(AnsiText.padding(
snapshot.getUpdatedSnapshots().size() + " updated " + snapshot.getUpdatedSnapshots(),
2 * TEST_PADDING));
}
if (snapshot.getCreatedSnapshots().size() > 0) {
System.out.println(AnsiText.padding(
snapshot.getCreatedSnapshots().size() + " created " + snapshot.getCreatedSnapshots(),
2 * TEST_PADDING));
}

updatedSnapshots += snapshot.getUpdatedSnapshots().size();
createdSnapshots += snapshot.getCreatedSnapshots().size();

// if we have at least one skipped test, we can not
// determine if a snapshot is obsolete.
if (testSuite.hasSkippedTests() || testSuite.hasFailedTests()) {
System.out.println(AnsiText.padding(
"Obsolete snapshots can only be checked if all tests of a file are executed successful.",
2 * TEST_PADDING));
return;
}

if (snapshot.getObsoleteSnapshots().size() > 0) {
System.out.println(AnsiColors.yellow(AnsiText.padding(
snapshot.getObsoleteSnapshots().size() + " obsolete " + snapshot.getObsoleteSnapshots(),
2 * TEST_PADDING)));
}

obsoleteSnapshots += snapshot.getObsoleteSnapshots().size();

}

@Override
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/askimed/nf/test/core/ITestSuite.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.List;

import com.askimed.nf.test.config.Config;
import com.askimed.nf.test.lang.extensions.SnapshotFile;

public interface ITestSuite extends ITaggable {

Expand All @@ -22,5 +23,15 @@ public interface ITestSuite extends ITaggable {
public String getDirectory();

public void configure(Config config);

public boolean hasSkippedTests();

public void setFailedTests(boolean b);

public boolean hasFailedTests();

public SnapshotFile getSnapshot();

public boolean hasSnapshotLoaded();

}
16 changes: 16 additions & 0 deletions src/main/java/com/askimed/nf/test/core/TestExecutionEngine.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import java.util.Vector;

import com.askimed.nf.test.lang.TestSuiteBuilder;
import com.askimed.nf.test.lang.extensions.SnapshotFile;
import com.askimed.nf.test.plugins.PluginManager;
import com.askimed.nf.test.util.AnsiColors;
import com.askimed.nf.test.util.AnsiText;
Expand Down Expand Up @@ -35,6 +36,8 @@ public class TestExecutionEngine {

private boolean updateSnapshot = false;

private boolean cleanSnapshot = false;

private String libDir = "";

private PluginManager pluginManager = null;
Expand Down Expand Up @@ -75,6 +78,10 @@ public void setUpdateSnapshot(boolean updateSnapshot) {
this.updateSnapshot = updateSnapshot;
}

public void setCleanSnapshot(boolean cleanSnapshot) {
this.cleanSnapshot = cleanSnapshot;
}

public void setTagQuery(TagQuery tagQuery) {
this.tagQuery = tagQuery;
}
Expand Down Expand Up @@ -199,6 +206,7 @@ public int execute() throws Throwable {
result.setThrowable(e);
result.setErrorReport(test.getErrorReport());
failed = true;
testSuite.setFailedTests(true);

}
test.cleanup();
Expand All @@ -207,6 +215,14 @@ public int execute() throws Throwable {

}

// Remove obsolete snapshots when no test was skipped and no test failed.
if (cleanSnapshot && !testSuite.hasSkippedTests() && !testSuite.hasFailedTests()
&& testSuite.hasSnapshotLoaded()) {
SnapshotFile snapshot = testSuite.getSnapshot();
snapshot.removeObsoleteSnapshots();
snapshot.save();
}

listener.testSuiteExecutionFinished(testSuite);

}
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/askimed/nf/test/lang/WorkflowMeta.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,10 @@ public void setName(String name) {
public String getName() {
return name;
}

@Override
public String toString() {
return name;
}

}
21 changes: 11 additions & 10 deletions src/main/java/com/askimed/nf/test/lang/extensions/Snapshot.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.askimed.nf.test.lang.extensions;

import java.util.Date;
import java.io.IOException;

import com.askimed.nf.test.core.ITest;

Expand All @@ -14,28 +14,29 @@ public class Snapshot {

public Snapshot(Object actual, ITest test) {
this.actual = actual;
this.file = SnapshotFile.loadByTestSuite(test.getTestSuite());
this.file = test.getTestSuite().getSnapshot();
this.test = test;
}

public boolean match() {
public boolean match() throws IOException {
return match(test.getName());
}

public boolean match(String id) {
public boolean match(String id) throws IOException {
SnapshotFileItem expected = file.getSnapshot(id);
//new snapshot --> create snapshot
if (expected == null) {
file.updateSnapshot(id, actual);
file.createSnapshot(id, actual);
file.save();
return true;
}

try {
return new SnapshotFileItem(new Date(), actual).equals(expected);
//compare actual snapshot with expected
return new SnapshotFileItem(actual).equals(expected);
} catch (Exception e) {
// test failes
// test failes and flag set --> update snapshot
if (test.isUpdateSnapshot()) {
// udpate snapshot
file.updateSnapshot(id, actual);
file.save();
return true;
Expand All @@ -45,9 +46,9 @@ public boolean match(String id) {
}

}

public void view() {

}

}
Loading

0 comments on commit 27089df

Please sign in to comment.