diff --git a/easy-random-core/src/main/java/org/jeasy/random/FieldPopulator.java b/easy-random-core/src/main/java/org/jeasy/random/FieldPopulator.java index 90918f422..84d1e9cf2 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/FieldPopulator.java +++ b/easy-random-core/src/main/java/org/jeasy/random/FieldPopulator.java @@ -23,6 +23,7 @@ */ package org.jeasy.random; +import java.util.List; import org.jeasy.random.api.ContextAwareRandomizer; import org.jeasy.random.api.Randomizer; import org.jeasy.random.api.RandomizerProvider; @@ -32,7 +33,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Type; -import static org.jeasy.random.util.CollectionUtils.randomElementOf; import static org.jeasy.random.util.ReflectionUtils.*; /** @@ -123,27 +123,26 @@ private Object generateRandomValue(final Field field, final RandomizationContext Class fieldType = field.getType(); Type fieldGenericType = field.getGenericType(); - Object value; if (isArrayType(fieldType)) { - value = arrayPopulator.getRandomArray(fieldType, context); + return arrayPopulator.getRandomArray(fieldType, context); } else if (isCollectionType(fieldType)) { - value = collectionPopulator.getRandomCollection(field, context); + return collectionPopulator.getRandomCollection(field, context); } else if (isMapType(fieldType)) { - value = mapPopulator.getRandomMap(field, context); + return mapPopulator.getRandomMap(field, context); } else if (isOptionalType(fieldType)) { - value = optionalPopulator.getRandomOptional(field, context); + return optionalPopulator.getRandomOptional(field, context); } else { if (context.getParameters().isScanClasspathForConcreteTypes() && isAbstract(fieldType) && !isEnumType(fieldType) /*enums can be abstract, but can not inherit*/) { - Class randomConcreteSubType = randomElementOf(filterSameParameterizedTypes(getPublicConcreteSubTypesOf(fieldType), fieldGenericType)); - if (randomConcreteSubType == null) { + List> parameterizedTypes = filterSameParameterizedTypes(getPublicConcreteSubTypesOf(fieldType), fieldGenericType); + if (parameterizedTypes.isEmpty()) { throw new ObjectCreationException("Unable to find a matching concrete subtype of type: " + fieldType); } else { - value = easyRandom.doPopulateBean(randomConcreteSubType, context); + Class randomConcreteSubType = parameterizedTypes.get(easyRandom.nextInt(parameterizedTypes.size())); + return easyRandom.doPopulateBean(randomConcreteSubType, context); } } else { - value = easyRandom.doPopulateBean(fieldType, context); + return easyRandom.doPopulateBean(fieldType, context); } } - return value; } } diff --git a/easy-random-core/src/main/java/org/jeasy/random/ObjenesisObjectFactory.java b/easy-random-core/src/main/java/org/jeasy/random/ObjenesisObjectFactory.java index 4caeb5569..aa7bca8a6 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/ObjenesisObjectFactory.java +++ b/easy-random-core/src/main/java/org/jeasy/random/ObjenesisObjectFactory.java @@ -23,6 +23,8 @@ */ package org.jeasy.random; +import java.util.List; +import java.util.Random; import org.jeasy.random.api.ObjectFactory; import org.jeasy.random.api.RandomizerContext; import org.objenesis.Objenesis; @@ -30,7 +32,6 @@ import java.lang.reflect.Constructor; -import static org.jeasy.random.util.CollectionUtils.randomElementOf; import static org.jeasy.random.util.ReflectionUtils.getPublicConcreteSubTypesOf; import static org.jeasy.random.util.ReflectionUtils.isAbstract; @@ -44,13 +45,19 @@ public class ObjenesisObjectFactory implements ObjectFactory { private final Objenesis objenesis = new ObjenesisStd(); + private Random random; + @Override public T createInstance(Class type, RandomizerContext context) { + if (random == null) { + random = new Random(context.getParameters().getSeed()); + } if (context.getParameters().isScanClasspathForConcreteTypes() && isAbstract(type)) { - Class randomConcreteSubType = randomElementOf(getPublicConcreteSubTypesOf((type))); - if (randomConcreteSubType == null) { + List> publicConcreteSubTypes = getPublicConcreteSubTypesOf(type); + if (publicConcreteSubTypes.isEmpty()) { throw new InstantiationError("Unable to find a matching concrete subtype of type: " + type + " in the classpath"); } else { + Class randomConcreteSubType = publicConcreteSubTypes.get(random.nextInt(publicConcreteSubTypes.size())); return (T) createNewInstance(randomConcreteSubType); } } else { diff --git a/easy-random-core/src/main/java/org/jeasy/random/RandomizationContext.java b/easy-random-core/src/main/java/org/jeasy/random/RandomizationContext.java index e4ad3e996..537eeac1b 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/RandomizationContext.java +++ b/easy-random-core/src/main/java/org/jeasy/random/RandomizationContext.java @@ -46,6 +46,8 @@ class RandomizationContext implements RandomizerContext { private final Class type; + private final Random random; + private Object rootObject; RandomizationContext(final Class type, final EasyRandomParameters parameters) { @@ -53,6 +55,7 @@ class RandomizationContext implements RandomizerContext { populatedBeans = new IdentityHashMap<>(); stack = new Stack<>(); this.parameters = parameters; + this.random = new Random(parameters.getSeed()); } void addPopulatedBean(final Class type, Object object) { @@ -69,7 +72,7 @@ void addPopulatedBean(final Class type, Object object) { Object getPopulatedBean(final Class type) { int actualPoolSize = populatedBeans.get(type).size(); - int randomIndex = actualPoolSize > 1 ? nextInt(0, actualPoolSize) : 0; + int randomIndex = actualPoolSize > 1 ? random.nextInt(actualPoolSize) : 0; return populatedBeans.get(type).get(randomIndex); } @@ -104,10 +107,6 @@ private List toLowerCase(final List strings) { return strings.stream().map(String::toLowerCase).collect(toList()); } - private int nextInt(int startInclusive, int endExclusive) { - return startInclusive + new Random().nextInt(endExclusive - startInclusive); - } - void setRandomizedObject(Object randomizedObject) { if (this.rootObject == null) { this.rootObject = randomizedObject; diff --git a/easy-random-core/src/main/java/org/jeasy/random/util/CollectionUtils.java b/easy-random-core/src/main/java/org/jeasy/random/util/CollectionUtils.java index 01b9f1db6..93f7ec946 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/util/CollectionUtils.java +++ b/easy-random-core/src/main/java/org/jeasy/random/util/CollectionUtils.java @@ -32,7 +32,9 @@ * This class is intended for internal use only. * * @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) + * @deprecated This class is deprecated since v4.3 and will be removed in v5.0 */ +@Deprecated public final class CollectionUtils { private CollectionUtils() { diff --git a/easy-random-core/src/test/java/org/jeasy/random/RepeatableRandomTest.java b/easy-random-core/src/test/java/org/jeasy/random/RepeatableRandomTest.java new file mode 100644 index 000000000..b74e37e96 --- /dev/null +++ b/easy-random-core/src/test/java/org/jeasy/random/RepeatableRandomTest.java @@ -0,0 +1,170 @@ +/* + * The MIT License + * + * Copyright (c) 2020, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.jeasy.random; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.ThreadLocalRandom; +import org.junit.jupiter.api.Test; + +public class RepeatableRandomTest { + + @Test + public void validateEqualsAndHashCodeSameRandomInstancePoolSize1() { + for (int i = 0; i < 3; i++) { + validateEqualsAndHashCodeSameRandomInstanceImpl(1); + } + } + + @Test + public void validateEqualsAndHashCodeSameRandomInstancePoolSize3() { + for (int i = 0; i < 3; i++) { + validateEqualsAndHashCodeSameRandomInstanceImpl(3); + } + } + + private void validateEqualsAndHashCodeSameRandomInstanceImpl(int poolSize) { + long seed = ThreadLocalRandom.current().nextLong(); + + Object instance1 = randomInstance(Pojo.class, seed, poolSize); + // same seed - hence same object expected + Object instance2 = randomInstance(Pojo.class, seed, poolSize); + + assertThat(instance1) + .isEqualTo(instance2); + assertThat(instance1.hashCode()) + .isEqualTo(instance2.hashCode()); + } + + private Object randomInstance(Class type, long seed, int poolSize) { + EasyRandom easyRandom = new EasyRandom(new EasyRandomParameters() + .objectPoolSize(poolSize) + .seed(seed) + .stringLengthRange(3, 5) + .collectionSizeRange(3, 4)); + return easyRandom.nextObject(type); + } + + public static class Pojo { + + private String id; + + private List a; + + private List b; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Pojo that = (Pojo) o; + return id.equals(that.id) + && a.equals(that.a) + && b.equals(that.b); + } + + @Override + public int hashCode() { + return Objects.hash(id, a, b); + } + + @Override + public String toString() { + return "Pojo{" + + "id='" + id + '\'' + + ", a=" + a + + ", b=" + b + + '}'; + } + } + + public static class PojoA { + + private String s; + + // equals/hashCode/toString by id to avoid possible stack overflow + private Pojo root; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PojoA that = (PojoA) o; + return s.equals(that.s) + && root.id.equals(that.root.id); + } + + @Override + public int hashCode() { + return Objects.hash(s, root.id); + } + + @Override + public String toString() { + return "PojoA{" + + "s='" + s + '\'' + + ", root.id=" + root.id + + '}'; + } + } + + public static class PojoB { + + private String s; + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + PojoB that = (PojoB) o; + return s.equals(that.s); + } + + @Override + public int hashCode() { + return Objects.hash(s); + } + + @Override + public String toString() { + return "PojoB{" + + "s='" + s + '\'' + + '}'; + } + } +} diff --git a/easy-random-core/src/test/java/org/jeasy/random/util/CollectionUtilsTest.java b/easy-random-core/src/test/java/org/jeasy/random/util/CollectionUtilsTest.java index 3bbaa9e46..420f4ab82 100644 --- a/easy-random-core/src/test/java/org/jeasy/random/util/CollectionUtilsTest.java +++ b/easy-random-core/src/test/java/org/jeasy/random/util/CollectionUtilsTest.java @@ -41,4 +41,4 @@ void testRandomElementOf() { // Then assertThat(element).isIn(elements); } -} \ No newline at end of file +}