Skip to content

Commit

Permalink
Add simple API to plugin test-framework specific behavior
Browse files Browse the repository at this point in the history
Closes #60
  • Loading branch information
skuzzle committed Feb 28, 2023
1 parent 7e78d38 commit 5c88b0d
Show file tree
Hide file tree
Showing 11 changed files with 158 additions and 94 deletions.
6 changes: 6 additions & 0 deletions readme/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@

* [#67](https://github.com/skuzzle/snapshot-tests/issues/67): Fix inconsistent naming of structured data modules


### Internal

* [#60](https://github.com/skuzzle/snapshot-tests/issues/60): Internal API for plugging in test-framework specific behavior


### Build

* [#80](https://github.com/skuzzle/snapshot-tests/issues/80): Use spotless and reformat whole code base
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import de.skuzzle.test.snapshots.ContextFiles;
Expand All @@ -20,6 +21,11 @@
final class LocalResultCollector {

private final List<SnapshotTestResult> results = new ArrayList<>();
private final Function<String, Throwable> assumptionFailedConstructor;

LocalResultCollector(Function<String, Throwable> assumptionFailedConstructor) {
this.assumptionFailedConstructor = assumptionFailedConstructor;
}

public void recordSnapshotTestResult(SnapshotTestResult result) {
this.results.add(Arguments.requireNonNull(result));
Expand Down Expand Up @@ -82,8 +88,7 @@ private Throwable failIfUpdatedForcefully() {

private Throwable abortIfNoneFailedAndAtLeastOneWasDisabled() {
if (wasAtLeastOneDisabledAndAllOthersSuccessful()) {
return AssumptionExceptionDetector.assumptionFailed("Assertion was disabled")
.orElse(null);
return assumptionFailedConstructor.apply("Assertion was disabled");
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package de.skuzzle.test.snapshots.impl;

import java.util.function.Function;

import de.skuzzle.test.snapshots.SnapshotTestResult;
import de.skuzzle.test.snapshots.validation.Arguments;

Expand All @@ -22,7 +24,9 @@ private ResultRecorder(LocalResultCollector localResultCollector, SnapshotTestCo
}

static ResultRecorder forFreshTestMethod(SnapshotTestContext context) {
return new ResultRecorder(new LocalResultCollector(), context);
final Function<String, Throwable> assumptionFailedConstructor = context
.testFrameworkSupport()::assumptionFailed;
return new ResultRecorder(new LocalResultCollector(assumptionFailedConstructor), context);
}

public void recordSnapshotTestResult(SnapshotTestResult result) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,27 @@ public final class SnapshotTestContext {

private final DynamicOrphanedSnapshotsDetector dynamicOrphanedSnapshotsDetector = new DynamicOrphanedSnapshotsDetector();
private final SnapshotConfiguration snapshotConfiguration;
private final TestFrameworkSupport testFrameworkSupport;

private SnapshotDslImpl currentSnapshotTest;

private SnapshotTestContext(SnapshotConfiguration snapshotConfiguration) {
private SnapshotTestContext(SnapshotConfiguration snapshotConfiguration,
TestFrameworkSupport testFrameworkSupport) {
this.snapshotConfiguration = Arguments.requireNonNull(snapshotConfiguration,
"snapshotConfiguration must not be null");
this.testFrameworkSupport = Arguments.requireNonNull(testFrameworkSupport,
"testFrameworkSupport must not be null");
}

@Deprecated(since = "1.7.0")
public static SnapshotTestContext forTestClass(Class<?> testClass) {
public static SnapshotTestContext forTestClass(Class<?> testClass, TestFrameworkSupport testFrameworkSupport) {
final SnapshotConfiguration configuration = DefaultSnapshotConfiguration.forTestClass(testClass);
return new SnapshotTestContext(configuration);
return new SnapshotTestContext(configuration, testFrameworkSupport);
}

public static SnapshotTestContext forConfiguration(SnapshotConfiguration snapshotConfiguration) {
return new SnapshotTestContext(snapshotConfiguration);
public static SnapshotTestContext forConfiguration(SnapshotConfiguration snapshotConfiguration,
TestFrameworkSupport testFrameworkSupport) {
return new SnapshotTestContext(snapshotConfiguration, testFrameworkSupport);
}

/**
Expand All @@ -71,6 +76,18 @@ public SnapshotConfiguration snapshotConfiguration() {
return snapshotConfiguration;
}

/**
* Returns the TestFrameworkSupport that encapsulates test framework specific
* behavior.
*
* @return Thes {@link TestFrameworkSupport}.
* @since 1.10.0
*/
@API(status = Status.INTERNAL, since = "1.10.0")
public TestFrameworkSupport testFrameworkSupport() {
return testFrameworkSupport;
}

/**
* Determines whether the parameters with the given type are eligible for injecting
* the object that is created by {@link #createSnapshotTestFor(Method)}.
Expand Down Expand Up @@ -158,7 +175,7 @@ public Collection<Path> detectOrCleanupOrphanedSnapshots() {
.detectOrphans(globalSnapshotDirectory)
.peek(collector::addRawResult);

final Stream<OrphanDetectionResult> staticOrphans = new StaticOrphanedSnapshotDetector()
final Stream<OrphanDetectionResult> staticOrphans = new StaticOrphanedSnapshotDetector(testFrameworkSupport)
.detectOrphans(DirectoryResolver.BASE)
.peek(collector::addRawResult);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.io.IOException;
import java.io.UncheckedIOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
Expand Down Expand Up @@ -33,6 +32,12 @@
*/
final class StaticOrphanedSnapshotDetector {

private final TestFrameworkSupport testFrameworkSupport;

StaticOrphanedSnapshotDetector(TestFrameworkSupport testFrameworkSupport) {
this.testFrameworkSupport = testFrameworkSupport;
}

/**
* Statically (=without executing the tests) detects all orphaned files within the
* given root and its child directories.
Expand All @@ -44,7 +49,7 @@ public Stream<OrphanDetectionResult> detectOrphans(Path root) {
try (var files = UncheckedIO.walk(root)) {
return files
.filter(InternalSnapshotNaming::isSnapshotFile)
.map(SnapshotFileAndPath::readFrom)
.map(path -> SnapshotFileAndPath.readFrom(path, testFrameworkSupport))
.map(SnapshotFileAndPath::toOrphanDetectionResult)
.collect(Collectors.toList())
.stream();
Expand All @@ -54,16 +59,18 @@ public Stream<OrphanDetectionResult> detectOrphans(Path root) {
private static final class SnapshotFileAndPath {
private final Path path;
private final SnapshotFile snapshotFile;
private final TestFrameworkSupport testFrameworkSupport;

public SnapshotFileAndPath(Path path, SnapshotFile snapshotFile) {
private SnapshotFileAndPath(Path path, SnapshotFile snapshotFile, TestFrameworkSupport testFrameworkSupport) {
this.path = path;
this.snapshotFile = snapshotFile;
this.testFrameworkSupport = testFrameworkSupport;
}

static SnapshotFileAndPath readFrom(Path path) {
static SnapshotFileAndPath readFrom(Path path, TestFrameworkSupport testFrameworkSupport) {
try {
final SnapshotFile snapshotFile = SnapshotFile.fromSnapshotFile(path);
return new SnapshotFileAndPath(path, snapshotFile);
return new SnapshotFileAndPath(path, snapshotFile, testFrameworkSupport);
} catch (final IOException e) {
throw new UncheckedIOException(e);
}
Expand Down Expand Up @@ -120,7 +127,7 @@ private Optional<Method> testMethodIn(Class<?> testClass) {
final var methodName = snapshotFile.header().get(SnapshotHeader.TEST_METHOD);
return Arrays.stream(testClass.getDeclaredMethods())
.filter(method -> method.getName().equals(methodName))
.filter(this::isSnapshotTest)
.filter(method -> isSnapshotTest(testClass, method))
.findAny();
}

Expand All @@ -131,14 +138,8 @@ private boolean isDynamicDirectory() {
return snapshotFile.header().getBoolean(SnapshotHeader.DYNAMIC_DIRECTORY, true);
}

private boolean isSnapshotTest(Method method) {
return !Modifier.isStatic(method.getModifiers())
&& !Modifier.isPrivate(method.getModifiers());

// This condition breaks static orphan detection for JUnit4 because we do not
// have parameters there
// && Arrays.stream(method.getParameterTypes())
// .anyMatch(parameterType -> Snapshot.class.isAssignableFrom(parameterType));
private boolean isSnapshotTest(Class<?> testClass, Method method) {
return testFrameworkSupport.isSnapshotTest(testClass, method);
}

}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package de.skuzzle.test.snapshots.impl;

import org.apiguardian.api.API;

import java.lang.reflect.Method;

/**
* Abstraction to provide test framework specific behavior to the snapshot-test core
*
* @since 1.10.0
*/
@API(status = API.Status.INTERNAL, since = "1.10.0")
public interface TestFrameworkSupport {

/**
* Tries to determine whether the given method is (still) a snapshot test. This is used during static orphan
* detection in order to determine whether the method mentioned in an existing snapshot's header pertains to an
* existing snapshot test method.
* <p>
* It might not always be possible to statically determine whether a test method uses snapshot assertions. In this
* case the method should return true in order to prevent false positives during orphan detection.
* </p>
*
* @param testClass The test class.
* @param testMethod The test method.
* @return Whether the given test method uses snapshot assertions.
*/
boolean isSnapshotTest(Class<?> testClass, Method testMethod);

/**
* Creates a throwable that will mark the repsective test as skipped.
*
* @param message The message for the throwable.
* @return The throwable.
*/
Throwable assumptionFailed(String message);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package de.skuzzle.test.snapshots.junit5;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;

import de.skuzzle.test.snapshots.Snapshot;
import de.skuzzle.test.snapshots.impl.TestFrameworkSupport;

import org.opentest4j.TestAbortedException;

final class JUnit5TestFrameworkSupport implements TestFrameworkSupport {

static final TestFrameworkSupport INSTANCE = new JUnit5TestFrameworkSupport();

private JUnit5TestFrameworkSupport() {
// hidden
}

@Override
public boolean isSnapshotTest(Class<?> testClass, Method testMethod) {
return !Modifier.isStatic(testMethod.getModifiers())
&& !Modifier.isPrivate(testMethod.getModifiers())
&& Arrays.stream(testMethod.getParameterTypes()).anyMatch(Snapshot.class::isAssignableFrom);
}

@Override
public Throwable assumptionFailed(String message) {
return new TestAbortedException(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ public static SnapshotTestContext fromExtensionContext(ExtensionContext extensio
public static SnapshotTestContext create(ExtensionContext extensionContext) {
final var testClass = extensionContext.getRequiredTestClass();
final var snapshotConfiguration = SnapshotConfiguration.defaultConfigurationFor(testClass);
final var snapshotTestContext = SnapshotTestContext.forConfiguration(snapshotConfiguration);
final var snapshotTestContext = SnapshotTestContext.forConfiguration(snapshotConfiguration,
JUnit5TestFrameworkSupport.INSTANCE);
extensionContext.getStore(NAMESPACE).put(KEY_SELF, snapshotTestContext);
return snapshotTestContext;
}
Expand All @@ -68,8 +69,9 @@ public static SnapshotTestContext create(ExtensionContext extensionContext) {
@Deprecated(since = "1.7.0")
public static SnapshotTestContext createLegacy(ExtensionContext extensionContext) {
final var testClass = extensionContext.getRequiredTestClass();
final SnapshotConfiguration snapshotConfiguration = SnapshotConfiguration.legacyConfigurationFor(testClass);
final var snapshotTestContext = SnapshotTestContext.forConfiguration(snapshotConfiguration);
final var snapshotConfiguration = SnapshotConfiguration.legacyConfigurationFor(testClass);
final var snapshotTestContext = SnapshotTestContext.forConfiguration(snapshotConfiguration,
JUnit5TestFrameworkSupport.INSTANCE);
extensionContext.getStore(NAMESPACE).put(KEY_SELF, snapshotTestContext);
return snapshotTestContext;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package de.skuzzle.test.snapshots.junit4;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import de.skuzzle.test.snapshots.impl.TestFrameworkSupport;

import org.junit.AssumptionViolatedException;

final class JUnit4TestFrameworkSupport implements TestFrameworkSupport {
static final TestFrameworkSupport INSTANCE = new JUnit4TestFrameworkSupport();

private JUnit4TestFrameworkSupport() {
// hidden
}

@Override
public boolean isSnapshotTest(Class<?> testClass, Method testMethod) {
return !Modifier.isStatic(testMethod.getModifiers())
&& !Modifier.isPrivate(testMethod.getModifiers());
}

@Override
public Throwable assumptionFailed(String message) {
return new AssumptionViolatedException(message);
}
}
Loading

0 comments on commit 5c88b0d

Please sign in to comment.