Skip to content

Commit

Permalink
Configure all Random instances with the same seed
Browse files Browse the repository at this point in the history
Issue #413
  • Loading branch information
seregamorph authored and fmbenhassine committed Nov 1, 2020
1 parent 5ca3541 commit 15e5a82
Show file tree
Hide file tree
Showing 6 changed files with 197 additions and 20 deletions.
21 changes: 10 additions & 11 deletions easy-random-core/src/main/java/org/jeasy/random/FieldPopulator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.*;

/**
Expand Down Expand Up @@ -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<Class<?>> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@
*/
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;
import org.objenesis.ObjenesisStd;

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;

Expand All @@ -44,13 +45,19 @@ public class ObjenesisObjectFactory implements ObjectFactory {

private final Objenesis objenesis = new ObjenesisStd();

private Random random;

@Override
public <T> T createInstance(Class<T> 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<Class<?>> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,16 @@ class RandomizationContext implements RandomizerContext {

private final Class<?> type;

private final Random random;

private Object rootObject;

RandomizationContext(final Class<?> type, final EasyRandomParameters parameters) {
this.type = type;
populatedBeans = new IdentityHashMap<>();
stack = new Stack<>();
this.parameters = parameters;
this.random = new Random(parameters.getSeed());
}

void addPopulatedBean(final Class<?> type, Object object) {
Expand All @@ -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);
}

Expand Down Expand Up @@ -104,10 +107,6 @@ private List<String> toLowerCase(final List<String> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
* <strong>This class is intended for internal use only.</strong>
*
* @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() {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PojoA> a;

private List<PojoB> 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 + '\'' +
'}';
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ void testRandomElementOf() {
// Then
assertThat(element).isIn(elements);
}
}
}

0 comments on commit 15e5a82

Please sign in to comment.