From 84459136697c4378d9b3e85640353352322f10bd Mon Sep 17 00:00:00 2001 From: "ah.jo" Date: Sat, 30 Nov 2024 02:58:02 +0900 Subject: [PATCH] Fix the seed option to apply per FixtureMonkey instance. --- .../v1.1.x-kor/release-notes/_index.md | 2 + docs/content/v1.1.x/release-notes/_index.md | 2 + .../api/context/MonkeyContext.java | 12 ++ .../api/context/MonkeyContextBuilder.java | 10 ++ .../fixturemonkey/api/random/Randoms.java | 10 +- .../extension/FixtureMonkeySeedExtension.java | 14 ++- .../extension/FixtureMonkeySeedTest.kt | 46 +++++++ .../fixturemonkey/FixtureMonkey.java | 3 +- .../fixturemonkey/FixtureMonkeyBuilder.java | 12 +- .../resolver/ArbitraryResolver.java | 3 +- .../resolver/ResolvedCombinableArbitrary.java | 119 +++++++++++------- 11 files changed, 179 insertions(+), 54 deletions(-) create mode 100644 fixture-monkey-junit-jupiter/src/test/kotlin/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedTest.kt diff --git a/docs/content/v1.1.x-kor/release-notes/_index.md b/docs/content/v1.1.x-kor/release-notes/_index.md index c875d29c2..8118919b1 100644 --- a/docs/content/v1.1.x-kor/release-notes/_index.md +++ b/docs/content/v1.1.x-kor/release-notes/_index.md @@ -9,6 +9,8 @@ sectionStart ## v.1.1.4 Fix not registering size API if decomposing. +Fix the `seed` option to apply per `FixtureMonkey` instance. + sectionEnd sectionStart diff --git a/docs/content/v1.1.x/release-notes/_index.md b/docs/content/v1.1.x/release-notes/_index.md index 84dc06313..ef45da0e5 100644 --- a/docs/content/v1.1.x/release-notes/_index.md +++ b/docs/content/v1.1.x/release-notes/_index.md @@ -9,6 +9,8 @@ sectionStart ## v.1.1.4 Fix not registering size API if decomposing. +Fix the `seed` option to apply per `FixtureMonkey` instance. + sectionEnd sectionStart diff --git a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/context/MonkeyContext.java b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/context/MonkeyContext.java index 099ea39d7..11748f44f 100644 --- a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/context/MonkeyContext.java +++ b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/context/MonkeyContext.java @@ -23,6 +23,8 @@ import java.util.List; import java.util.TreeMap; +import javax.annotation.Nullable; + import org.apiguardian.api.API; import org.apiguardian.api.API.Status; @@ -50,6 +52,9 @@ public final class MonkeyContext { private final ConcurrentLruCache> javaArbitrariesByProperty; private final ConcurrentLruCache generatorContextByRootProperty; private final List>> registeredArbitraryBuilders; + + @Nullable + private final Long seed; private final FixtureMonkeyOptions fixtureMonkeyOptions; public MonkeyContext( @@ -57,12 +62,14 @@ public MonkeyContext( ConcurrentLruCache> javaArbitrariesByProperty, ConcurrentLruCache generatorContextByRootProperty, List>> registeredArbitraryBuilders, + @Nullable Long seed, FixtureMonkeyOptions fixtureMonkeyOptions ) { this.arbitrariesByProperty = arbitrariesByProperty; this.javaArbitrariesByProperty = javaArbitrariesByProperty; this.generatorContextByRootProperty = generatorContextByRootProperty; this.registeredArbitraryBuilders = registeredArbitraryBuilders; + this.seed = seed; this.fixtureMonkeyOptions = fixtureMonkeyOptions; } @@ -101,6 +108,11 @@ public List>> getRegisteredArbitraryB return registeredArbitraryBuilders; } + @Nullable + public Long getSeed() { + return seed; + } + public FixtureMonkeyOptions getFixtureMonkeyOptions() { return fixtureMonkeyOptions; } diff --git a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/context/MonkeyContextBuilder.java b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/context/MonkeyContextBuilder.java index 97ffd9451..8fee55220 100644 --- a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/context/MonkeyContextBuilder.java +++ b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/context/MonkeyContextBuilder.java @@ -21,6 +21,8 @@ import java.util.ArrayList; import java.util.List; +import javax.annotation.Nullable; + import org.apiguardian.api.API; import org.apiguardian.api.API.Status; @@ -42,6 +44,8 @@ public final class MonkeyContextBuilder { private List>> registeredObjectBuilders; private int cacheSize = 2048; private int generatorContextSize = 1000; + @Nullable + private Long seed = null; public MonkeyContextBuilder(FixtureMonkeyOptions fixtureMonkeyOptions) { this.fixtureMonkeyOptions = fixtureMonkeyOptions; @@ -85,6 +89,11 @@ public MonkeyContextBuilder registeredObjectBuilder( return this; } + public MonkeyContextBuilder seed(@Nullable Long seed) { + this.seed = seed; + return this; + } + public MonkeyContext build() { if (arbitrariesByProperty == null) { arbitrariesByProperty = new ConcurrentLruCache<>(cacheSize); @@ -107,6 +116,7 @@ public MonkeyContext build() { javaArbitrariesByProperty, generatorContextByRootProperty, registeredObjectBuilders, + seed, fixtureMonkeyOptions ); } diff --git a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/random/Randoms.java b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/random/Randoms.java index 6acc5e93e..74d99a82e 100644 --- a/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/random/Randoms.java +++ b/fixture-monkey-api/src/main/java/com/navercorp/fixturemonkey/api/random/Randoms.java @@ -20,6 +20,8 @@ import java.util.Random; +import javax.annotation.Nullable; + import org.apiguardian.api.API; import org.apiguardian.api.API.Status; @@ -72,7 +74,8 @@ public static Random current() { : CURRENT.get(); } - public static long currentSeed() { + @Nullable + public static Long currentSeed() { return SEED.get(); } @@ -80,6 +83,11 @@ public static int nextInt(int bound) { return current().nextInt(bound); } + public static void clear() { + SEED.remove(); + CURRENT.remove(); + } + private static Random newRandom(final long seed) { return USE_JQWIK_ENGINE ? SourceOfRandomness.newRandom(seed) diff --git a/fixture-monkey-junit-jupiter/src/main/java/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedExtension.java b/fixture-monkey-junit-jupiter/src/main/java/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedExtension.java index f607d8e92..7d91946cf 100644 --- a/fixture-monkey-junit-jupiter/src/main/java/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedExtension.java +++ b/fixture-monkey-junit-jupiter/src/main/java/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedExtension.java @@ -19,6 +19,8 @@ import java.lang.reflect.Method; +import javax.annotation.Nullable; + import org.junit.jupiter.api.extension.AfterTestExecutionCallback; import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; import org.junit.jupiter.api.extension.ExtensionContext; @@ -30,10 +32,12 @@ public final class FixtureMonkeySeedExtension implements BeforeTestExecutionCallback, AfterTestExecutionCallback { private static final Logger LOGGER = LoggerFactory.getLogger(FixtureMonkeySeedExtension.class); + private final ThreadLocal previousSeed = new ThreadLocal<>(); @Override public void beforeTestExecution(ExtensionContext context) throws Exception { Seed seed = context.getRequiredTestMethod().getAnnotation(Seed.class); + previousSeed.set(Randoms.currentSeed()); if (seed != null) { setSeed(seed.value()); } @@ -49,13 +53,19 @@ public void afterTestExecution(ExtensionContext context) throws Exception { if (context.getExecutionException().isPresent()) { logSeedIfTestFailed(context); } + + setSeed(previousSeed.get()); } /** * Sets the seed for generating random numbers. **/ - private void setSeed(long seed) { - Randoms.create(String.valueOf(seed)); + private void setSeed(@Nullable Long seed) { + if (seed == null) { + Randoms.clear(); + } else { + Randoms.create(String.valueOf(seed)); + } } /** diff --git a/fixture-monkey-junit-jupiter/src/test/kotlin/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedTest.kt b/fixture-monkey-junit-jupiter/src/test/kotlin/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedTest.kt new file mode 100644 index 000000000..adbb8efb4 --- /dev/null +++ b/fixture-monkey-junit-jupiter/src/test/kotlin/com/navercorp/fixturemonkey/junit/jupiter/extension/FixtureMonkeySeedTest.kt @@ -0,0 +1,46 @@ +/* + * Fixture Monkey + * + * Copyright (c) 2021-present NAVER Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.navercorp.fixturemonkey.junit.jupiter.extension + +import com.navercorp.fixturemonkey.FixtureMonkey +import com.navercorp.fixturemonkey.api.random.Randoms +import com.navercorp.fixturemonkey.junit.jupiter.annotation.Seed +import org.assertj.core.api.BDDAssertions.then +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith + +@ExtendWith(FixtureMonkeySeedExtension::class) +class FixtureMonkeySeedTest { + @Test + fun withoutSeedAnnotationApplyNull() { + then(Randoms.currentSeed()).isNull() + } + + @Test + @Seed(1000L) + fun seedAnnotation() { + then(Randoms.currentSeed()).isEqualTo(1000L) + } + + companion object { + private val FIXTURE_MONKEY = FixtureMonkey.builder() + .seed(12345L) + .build() + } +} diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkey.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkey.java index 464bcac1d..0c517a2c8 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkey.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkey.java @@ -57,12 +57,13 @@ public FixtureMonkey( FixtureMonkeyOptions fixtureMonkeyOptions, ManipulatorOptimizer manipulatorOptimizer, List>>> registeredArbitraryBuilders, + MonkeyContext monkeyContext, MonkeyManipulatorFactory monkeyManipulatorFactory ) { this.fixtureMonkeyOptions = fixtureMonkeyOptions; this.manipulatorOptimizer = manipulatorOptimizer; - this.monkeyContext = MonkeyContext.builder(fixtureMonkeyOptions).build(); this.monkeyManipulatorFactory = monkeyManipulatorFactory; + this.monkeyContext = monkeyContext; initializeRegisteredArbitraryBuilders(registeredArbitraryBuilders); } diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java index d3fa6b80a..1898f9f01 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/FixtureMonkeyBuilder.java @@ -32,6 +32,7 @@ import com.navercorp.fixturemonkey.api.constraint.JavaConstraintGenerator; import com.navercorp.fixturemonkey.api.container.DecomposedContainerValueFactory; +import com.navercorp.fixturemonkey.api.context.MonkeyContext; import com.navercorp.fixturemonkey.api.generator.ArbitraryContainerInfoGenerator; import com.navercorp.fixturemonkey.api.generator.ArbitraryGenerator; import com.navercorp.fixturemonkey.api.generator.ContainerPropertyGenerator; @@ -51,7 +52,6 @@ import com.navercorp.fixturemonkey.api.plugin.Plugin; import com.navercorp.fixturemonkey.api.property.PropertyGenerator; import com.navercorp.fixturemonkey.api.property.PropertyNameResolver; -import com.navercorp.fixturemonkey.api.random.Randoms; import com.navercorp.fixturemonkey.api.type.Types; import com.navercorp.fixturemonkey.api.validator.ArbitraryValidator; import com.navercorp.fixturemonkey.buildergroup.ArbitraryBuilderCandidate; @@ -487,10 +487,6 @@ public FixtureMonkeyBuilder pushCustomizeValidOnly(TreeMatcher matcher, boolean return this; } - /** - * It is deprecated. Please use {@code @Seed} in fixture-monkey-junit-jupiter module. - */ - @Deprecated public FixtureMonkeyBuilder seed(long seed) { this.seed = seed; return this; @@ -504,11 +500,15 @@ public FixtureMonkey build() { fixtureMonkeyOptions.getDecomposedContainerValueFactory() ); - Randoms.create(String.valueOf(seed)); + MonkeyContext monkeyContext = MonkeyContext.builder(fixtureMonkeyOptions) + .seed(seed) + .build(); + return new FixtureMonkey( fixtureMonkeyOptions, manipulatorOptimizer, registeredArbitraryBuilders, + monkeyContext, monkeyManipulatorFactory ); } diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/resolver/ArbitraryResolver.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/resolver/ArbitraryResolver.java index f89840502..ef359b8f2 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/resolver/ArbitraryResolver.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/resolver/ArbitraryResolver.java @@ -98,7 +98,8 @@ public CombinableArbitrary resolve( }, fixtureMonkeyOptions.getGenerateMaxTries(), fixtureMonkeyOptions.getDefaultArbitraryValidator(), - builderContext::isValidOnly + builderContext::isValidOnly, + monkeyContext ); } } diff --git a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/resolver/ResolvedCombinableArbitrary.java b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/resolver/ResolvedCombinableArbitrary.java index 85d08d940..986bfe631 100644 --- a/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/resolver/ResolvedCombinableArbitrary.java +++ b/fixture-monkey/src/main/java/com/navercorp/fixturemonkey/resolver/ResolvedCombinableArbitrary.java @@ -22,15 +22,19 @@ import java.util.function.Predicate; import java.util.function.Supplier; +import javax.annotation.Nullable; + import org.apiguardian.api.API; import org.apiguardian.api.API.Status; import com.navercorp.fixturemonkey.api.arbitrary.CombinableArbitrary; +import com.navercorp.fixturemonkey.api.context.MonkeyContext; import com.navercorp.fixturemonkey.api.exception.ContainerSizeFilterMissException; import com.navercorp.fixturemonkey.api.exception.FixedValueFilterMissException; import com.navercorp.fixturemonkey.api.exception.RetryableFilterMissException; import com.navercorp.fixturemonkey.api.lazy.LazyArbitrary; import com.navercorp.fixturemonkey.api.property.RootProperty; +import com.navercorp.fixturemonkey.api.random.Randoms; import com.navercorp.fixturemonkey.api.validator.ArbitraryValidator; import com.navercorp.fixturemonkey.tree.ObjectTree; @@ -44,6 +48,7 @@ final class ResolvedCombinableArbitrary implements CombinableArbitrary { private final LazyArbitrary> arbitrary; private final ArbitraryValidator validator; private final Supplier validOnly; + private final MonkeyContext monkeyContext; private Exception lastException = null; @@ -53,7 +58,8 @@ public ResolvedCombinableArbitrary( Function> generateArbitrary, int generateMaxTries, ArbitraryValidator validator, - Supplier validOnly + Supplier validOnly, + MonkeyContext monkeyContext ) { this.rootProperty = rootProperty; this.objectTree = LazyArbitrary.lazy(regenerateTree); @@ -66,59 +72,71 @@ public ResolvedCombinableArbitrary( ); this.validator = validator; this.validOnly = validOnly; + this.monkeyContext = monkeyContext; } + @SuppressWarnings("unchecked") @Override public T combined() { - for (int i = 0; i < generateMaxTries; i++) { - try { - return arbitrary.getValue() - .filter(VALIDATION_ANNOTATION_FILTERING_COUNT, this.validateFilter(validOnly.get())) - .combined(); - } catch (ContainerSizeFilterMissException | RetryableFilterMissException ex) { - lastException = ex; - objectTree.clear(); - } catch (FixedValueFilterMissException ex) { - lastException = ex; - } finally { - arbitrary.clear(); + return (T)runInSeedScope( + monkeyContext.getSeed(), + () -> { + for (int i = 0; i < generateMaxTries; i++) { + try { + return arbitrary.getValue() + .filter(VALIDATION_ANNOTATION_FILTERING_COUNT, this.validateFilter(validOnly.get())) + .combined(); + } catch (ContainerSizeFilterMissException | RetryableFilterMissException ex) { + lastException = ex; + objectTree.clear(); + } catch (FixedValueFilterMissException ex) { + lastException = ex; + } finally { + arbitrary.clear(); + } + } + + throw new IllegalArgumentException( + String.format( + "Given type %s could not be generated." + + " Check the ArbitraryIntrospector used or the APIs used in the ArbitraryBuilder.", + rootProperty.getType() + ), + lastException + ); } - } - - throw new IllegalArgumentException( - String.format( - "Given type %s could not be generated." - + " Check the ArbitraryIntrospector used or the APIs used in the ArbitraryBuilder.", - rootProperty.getType() - ), - lastException ); } @Override public Object rawValue() { - for (int i = 0; i < generateMaxTries; i++) { - try { - return arbitrary.getValue() - .filter(VALIDATION_ANNOTATION_FILTERING_COUNT, this.validateFilter(validOnly.get())) - .rawValue(); - } catch (ContainerSizeFilterMissException | RetryableFilterMissException ex) { - lastException = ex; - objectTree.clear(); - } catch (FixedValueFilterMissException ex) { - lastException = ex; - } finally { - arbitrary.clear(); + return runInSeedScope( + monkeyContext.getSeed(), + () -> { + for (int i = 0; i < generateMaxTries; i++) { + try { + return arbitrary.getValue() + .filter(VALIDATION_ANNOTATION_FILTERING_COUNT, this.validateFilter(validOnly.get())) + .rawValue(); + } catch (ContainerSizeFilterMissException | RetryableFilterMissException ex) { + lastException = ex; + objectTree.clear(); + } catch (FixedValueFilterMissException ex) { + lastException = ex; + } finally { + arbitrary.clear(); + } + } + + throw new IllegalArgumentException( + String.format( + "Given type %s could not be generated." + + " Check the ArbitraryIntrospector used or the APIs used in the ArbitraryBuilder.", + rootProperty.getType() + ), + lastException + ); } - } - - throw new IllegalArgumentException( - String.format( - "Given type %s could not be generated." - + " Check the ArbitraryIntrospector used or the APIs used in the ArbitraryBuilder.", - rootProperty.getType() - ), - lastException ); } @@ -151,4 +169,19 @@ private Predicate validateFilter(boolean validOnly) { return true; }; } + + private static Object runInSeedScope(@Nullable Long seed, Supplier supplier) { + Long previousSeed = Randoms.currentSeed(); + if (seed != null) { + Randoms.create(String.valueOf(seed)); + } + + Object returned = supplier.get(); + + if (previousSeed != null) { + Randoms.create(String.valueOf(previousSeed)); + } + + return returned; + } }