From c61b273f8d3a778b32018859b318b1d19abd375c Mon Sep 17 00:00:00 2001 From: Octavia Togami Date: Fri, 6 Sep 2024 23:52:24 -0700 Subject: [PATCH] Add property-based testing for SideEffectSet --- .gitignore | 1 + .../kotlin/buildlogic.common-java.gradle.kts | 5 +- gradle/libs.versions.toml | 2 + .../worldedit/util/SideEffectSetTest.java | 141 ++++++------------ .../test/resources/junit-platform.properties | 2 + 5 files changed, 56 insertions(+), 95 deletions(-) diff --git a/.gitignore b/.gitignore index 8e37aad62..dfea5f9e5 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ target forge-download out run +.jqwik-database /dependency-reduced-pom.xml *-private.sh diff --git a/build-logic/src/main/kotlin/buildlogic.common-java.gradle.kts b/build-logic/src/main/kotlin/buildlogic.common-java.gradle.kts index d2595fee3..0167e8a3c 100644 --- a/build-logic/src/main/kotlin/buildlogic.common-java.gradle.kts +++ b/build-logic/src/main/kotlin/buildlogic.common-java.gradle.kts @@ -30,7 +30,9 @@ } tasks.withType().configureEach { - useJUnitPlatform() + useJUnitPlatform { + includeEngines("junit-jupiter", "jqwik") + } } dependencies { @@ -38,6 +40,7 @@ "testImplementation"(platform(stringyLibs.getLibrary("junit-bom"))) "testImplementation"(stringyLibs.getLibrary("junit-jupiter-api")) "testImplementation"(stringyLibs.getLibrary("junit-jupiter-params")) + "testImplementation"(stringyLibs.getLibrary("jqwik")) "testImplementation"(platform(stringyLibs.getLibrary("mockito-bom"))) "testImplementation"(stringyLibs.getLibrary("mockito-core")) "testImplementation"(stringyLibs.getLibrary("mockito-junit-jupiter")) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d0e1925b9..69c0652ba 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -66,6 +66,8 @@ junit-jupiter-api.module = "org.junit.jupiter:junit-jupiter-api" junit-jupiter-params.module = "org.junit.jupiter:junit-jupiter-params" junit-jupiter-engine.module = "org.junit.jupiter:junit-jupiter-engine" +jqwik = "net.jqwik:jqwik:1.9.0" + mockito-bom = "org.mockito:mockito-bom:5.11.0" mockito-core.module = "org.mockito:mockito-core" mockito-junit-jupiter.module = "org.mockito:mockito-junit-jupiter" diff --git a/worldedit-core/src/test/java/com/sk89q/worldedit/util/SideEffectSetTest.java b/worldedit-core/src/test/java/com/sk89q/worldedit/util/SideEffectSetTest.java index a7e0954d9..d1eb0ea42 100644 --- a/worldedit-core/src/test/java/com/sk89q/worldedit/util/SideEffectSetTest.java +++ b/worldedit-core/src/test/java/com/sk89q/worldedit/util/SideEffectSetTest.java @@ -19,107 +19,60 @@ package com.sk89q.worldedit.util; -import com.google.common.base.Preconditions; -import com.google.common.collect.Maps; -import org.junit.jupiter.api.Test; +import net.jqwik.api.ForAll; +import net.jqwik.api.Property; -import java.util.Arrays; -import java.util.EnumSet; import java.util.Map; -import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; - -import static org.junit.jupiter.api.Assertions.assertEquals; public class SideEffectSetTest { - private static void assertAppliesWithState(Map expected, SideEffectSet set) { - Preconditions.checkArgument( - expected.keySet().containsAll(EnumSet.allOf(SideEffect.class)), - "Expected map must contain all side effects" - ); + @Property + public boolean stateOrDefaultIsCorrect( + @ForAll Map stateMap, + @ForAll SideEffect effectToTest + ) { + SideEffectSet set = new SideEffectSet(stateMap); + return set.getState(effectToTest) == stateMap.getOrDefault(effectToTest, effectToTest.getDefaultValue()); + } - Set appliedSet = expected.entrySet().stream() - .filter(e -> e.getValue() != SideEffect.State.OFF) - .map(Map.Entry::getKey) - .collect(Collectors.toSet()); - assertEquals(appliedSet, set.getSideEffectsToApply()); - assertEquals(!appliedSet.isEmpty(), set.doesApplyAny()); + @Property + public boolean shouldApplyUnlessOff( + @ForAll Map stateMap, + @ForAll SideEffect effectToTest + ) { + SideEffectSet set = new SideEffectSet(stateMap); + return set.shouldApply(effectToTest) + == (stateMap.getOrDefault(effectToTest, effectToTest.getDefaultValue()) != SideEffect.State.OFF); + } + + @Property + public boolean withChangesState( + @ForAll Map stateMap, + @ForAll SideEffect effectToTest, + @ForAll SideEffect.State stateToSet + ) { + SideEffectSet set = new SideEffectSet(stateMap).with(effectToTest, stateToSet); + return set.getState(effectToTest) == stateToSet; + } + + @Property + public boolean anyShouldApplyEqualsDoesApplyAny(@ForAll Map stateMap) { + SideEffectSet set = new SideEffectSet(stateMap); + boolean anyShouldApply = false; for (SideEffect effect : SideEffect.values()) { - assertEquals( - appliedSet.contains(effect), set.shouldApply(effect), "Does not apply expected effect: " + effect - ); - assertEquals( - expected.get(effect), set.getState(effect), - "Does not have expected state for effect: " + effect - ); - } - } - - private static Map initStateMap(Function stateFunction) { - return Arrays.stream(SideEffect.values()).collect(Collectors.toMap(Function.identity(), stateFunction)); - } - - @Test - public void defaults() { - assertAppliesWithState( - initStateMap(SideEffect::getDefaultValue), - SideEffectSet.defaults() - ); - } - - @Test - public void noneExposed() { - assertAppliesWithState( - initStateMap(effect -> { - if (effect.isExposed()) { - return SideEffect.State.OFF; - } else { - return effect.getDefaultValue(); - } - }), - SideEffectSet.none() - ); - } - - @Test - public void allOn() { - Map expected = initStateMap(effect -> SideEffect.State.ON); - assertAppliesWithState( - expected, - new SideEffectSet(expected) - ); - } - - @Test - public void allDelayed() { - Map expected = initStateMap(effect -> SideEffect.State.DELAYED); - assertAppliesWithState( - expected, - new SideEffectSet(expected) - ); - } - - @Test - public void allOff() { - Map expected = initStateMap(effect -> SideEffect.State.OFF); - assertAppliesWithState( - expected, - new SideEffectSet(expected) - ); - } - - @Test - public void with() { - Map expected = initStateMap(SideEffect::getDefaultValue); - SideEffectSet set = SideEffectSet.defaults(); - - for (SideEffect effect : SideEffect.values()) { - for (SideEffect.State state : SideEffect.State.values()) { - expected = Maps.transformEntries(expected, (e, s) -> e == effect ? state : s); - set = set.with(effect, state); - assertAppliesWithState(expected, set); + if (set.shouldApply(effect)) { + anyShouldApply = true; + break; } } + return anyShouldApply == set.doesApplyAny(); + } + + @Property + public boolean shouldApplyEqualsApplySetContains( + @ForAll Map stateMap, + @ForAll SideEffect effectToTest + ) { + SideEffectSet set = new SideEffectSet(stateMap); + return set.shouldApply(effectToTest) == set.getSideEffectsToApply().contains(effectToTest); } } diff --git a/worldedit-core/src/test/resources/junit-platform.properties b/worldedit-core/src/test/resources/junit-platform.properties index ee7c4fad3..49883e6ba 100644 --- a/worldedit-core/src/test/resources/junit-platform.properties +++ b/worldedit-core/src/test/resources/junit-platform.properties @@ -3,3 +3,5 @@ junit.jupiter.execution.parallel.mode.default=concurrent junit.jupiter.execution.parallel.mode.classes.default=same_thread junit.jupiter.execution.parallel.config.strategy=dynamic junit.jupiter.execution.parallel.config.dynamic.factor=4 + +jqwik.tries.default = 10000