From f8104b79a7c704bd4ff60de2e4a4bd4e5cb421f3 Mon Sep 17 00:00:00 2001 From: "marian.jureczko" Date: Fri, 5 May 2023 12:55:21 +0200 Subject: [PATCH 1/8] Improvements regarding records creation limitations. --- .../random/DepthLimitationObjectFactory.java | 20 +++++ .../java/org/jeasy/random/EasyRandom.java | 2 +- .../jeasy/random/RandomizationContext.java | 3 +- .../java/org/jeasy/random/RecordFactory.java | 69 ++++++++++------ .../jeasy/random/api/RandomizerContext.java | 6 ++ .../jeasy/random/beans/DirectlyNested.java | 4 + .../beans/NestedRecordThroughCollection.java | 6 ++ .../random/beans/RecordLimitationsTest.java | 78 +++++++++++++++++++ .../random/beans/WrapperOfNonPublic.java | 10 +++ 9 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 easy-random-core/src/main/java/org/jeasy/random/DepthLimitationObjectFactory.java create mode 100644 easy-random-core/src/test/java/org/jeasy/random/beans/DirectlyNested.java create mode 100644 easy-random-core/src/test/java/org/jeasy/random/beans/NestedRecordThroughCollection.java create mode 100644 easy-random-core/src/test/java/org/jeasy/random/beans/RecordLimitationsTest.java create mode 100644 easy-random-core/src/test/java/org/jeasy/random/beans/WrapperOfNonPublic.java diff --git a/easy-random-core/src/main/java/org/jeasy/random/DepthLimitationObjectFactory.java b/easy-random-core/src/main/java/org/jeasy/random/DepthLimitationObjectFactory.java new file mode 100644 index 00000000..13bd3c50 --- /dev/null +++ b/easy-random-core/src/main/java/org/jeasy/random/DepthLimitationObjectFactory.java @@ -0,0 +1,20 @@ +package org.jeasy.random; + +import org.jeasy.random.util.ReflectionUtils; + +import java.lang.reflect.Array; + +class DepthLimitationObjectFactory { + static Object produceEmptyValueForField(Class fieldType) { + if (ReflectionUtils.isArrayType(fieldType)) { + return Array.newInstance(fieldType.getComponentType(), 0); + } + if (ReflectionUtils.isCollectionType(fieldType)) { + return ReflectionUtils.getEmptyImplementationForCollectionInterface(fieldType); + } + if (ReflectionUtils.isMapType(fieldType)) { + return ReflectionUtils.getEmptyImplementationForMapInterface(fieldType); + } + return null; + } +} diff --git a/easy-random-core/src/main/java/org/jeasy/random/EasyRandom.java b/easy-random-core/src/main/java/org/jeasy/random/EasyRandom.java index 43f91474..e92e74c3 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/EasyRandom.java +++ b/easy-random-core/src/main/java/org/jeasy/random/EasyRandom.java @@ -143,7 +143,7 @@ T doPopulateBean(final Class type, final RandomizationContext context) { } if (isRecord(type)) { - return new RecordFactory().createInstance(type, context); + return new RecordFactory(context).createInstance(type, context); } // Collection types are randomized without introspection for internal fields 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 e04d0b88..883c971f 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 @@ -91,7 +91,8 @@ String getFieldFullName(final Field field) { return String.join(".", toLowerCase(pathToField)); } - boolean hasExceededRandomizationDepth() { + @Override + public boolean hasExceededRandomizationDepth() { int currentRandomizationDepth = stack.size(); return currentRandomizationDepth > parameters.getRandomizationDepth(); } diff --git a/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java b/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java index d08ba10e..622cb15d 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java +++ b/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java @@ -31,48 +31,73 @@ import java.lang.reflect.Constructor; import java.lang.reflect.RecordComponent; import java.lang.reflect.Type; + import org.jeasy.random.api.RandomizerContext; +/** + * What is the justification for extending ObjenesisObjectFactory? + * RecordFactory, to support nesting depth, needs access to context implementation which renders the interface (RandomizerContext) + * introduced by ObjenesisObjectFactory method signature redundant. Abandoning the inheritance will hence simplify the + * implementation by reducing the number of parameters, and by additionally storing the instance of RecordFactory as a field in + * the EasyRandom class. + */ public class RecordFactory extends ObjenesisObjectFactory { private EasyRandom easyRandom; + private final RandomizationContext contextImpl; + + public RecordFactory(RandomizationContext contextImpl) { + this.contextImpl = contextImpl; + } @Override public T createInstance(Class type, RandomizerContext context) { if (easyRandom == null) { - easyRandom = new EasyRandom(context.getParameters()); + easyRandom = new EasyRandom(contextImpl.getParameters()); } - return createRandomRecord(type, context); + return createRandomRecord(type, contextImpl); } - private T createRandomRecord(Class recordType, RandomizerContext context) { + private T createRandomRecord(Class recordType, RandomizationContext context) { // generate random values for record components RecordComponent[] recordComponents = recordType.getRecordComponents(); Object[] randomValues = new Object[recordComponents.length]; - for (int i = 0; i < recordComponents.length; i++) { - Class type = recordComponents[i].getType(); - Type genericType = recordComponents[i].getGenericType(); - if (isArrayType(type)) { - randomValues[i] = new ArrayPopulator(easyRandom).getRandomArray(type, (RandomizationContext) context); - } else if (isMapType(type)) { - randomValues[i] = - new MapPopulator(easyRandom, context.getParameters().getObjectFactory()) - .getRandomMap(genericType, type, (RandomizationContext) context); - } else if (isOptionalType(type)) { - randomValues[i] = - new OptionalPopulator(easyRandom).getRandomOptional(genericType, (RandomizationContext) context); - } else if (isCollectionType(type)) { - randomValues[i] = - new CollectionPopulator(easyRandom) - .getRandomCollection(genericType, type, (RandomizationContext) context); - } else { - randomValues[i] = easyRandom.nextObject(type); + if (context.hasExceededRandomizationDepth()) { + for (int i = 0; i < recordComponents.length; i++) { + Class type = recordComponents[i].getType(); + randomValues[i] = DepthLimitationObjectFactory.produceEmptyValueForField(type); + } + } else { + for (int i = 0; i < recordComponents.length; i++) { + context.pushStackItem(new RandomizationContextStackItem(recordType, null)); + Class type = recordComponents[i].getType(); + Type genericType = recordComponents[i].getGenericType(); + if (isArrayType(type)) { + randomValues[i] = new ArrayPopulator(easyRandom).getRandomArray(type, context); + } else if (isMapType(type)) { + randomValues[i] = + new MapPopulator(easyRandom, context.getParameters().getObjectFactory()) + .getRandomMap(genericType, type, context); + } else if (isOptionalType(type)) { + randomValues[i] = + new OptionalPopulator(easyRandom).getRandomOptional(genericType, context); + } else if (isCollectionType(type)) { + randomValues[i] = + new CollectionPopulator(easyRandom) + .getRandomCollection(genericType, type, context); + } else { + randomValues[i] = easyRandom.doPopulateBean(type, context); + } + context.popStackItem(); } } + // create a random instance with random values try { - return getCanonicalConstructor(recordType).newInstance(randomValues); + Constructor canonicalConstructor = getCanonicalConstructor(recordType); + canonicalConstructor.setAccessible(true); + return canonicalConstructor.newInstance(randomValues); } catch (Exception e) { throw new ObjectCreationException("Unable to create a random instance of recordType " + recordType, e); } diff --git a/easy-random-core/src/main/java/org/jeasy/random/api/RandomizerContext.java b/easy-random-core/src/main/java/org/jeasy/random/api/RandomizerContext.java index 1571f6dc..275bb058 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/api/RandomizerContext.java +++ b/easy-random-core/src/main/java/org/jeasy/random/api/RandomizerContext.java @@ -68,4 +68,10 @@ public interface RandomizerContext { * @return currently used parameters */ EasyRandomParameters getParameters(); + + /** + * Checks if the current randomization depth reached the configured maximum + * @return true when on the deepest level + */ + boolean hasExceededRandomizationDepth(); } diff --git a/easy-random-core/src/test/java/org/jeasy/random/beans/DirectlyNested.java b/easy-random-core/src/test/java/org/jeasy/random/beans/DirectlyNested.java new file mode 100644 index 00000000..0ae7002e --- /dev/null +++ b/easy-random-core/src/test/java/org/jeasy/random/beans/DirectlyNested.java @@ -0,0 +1,4 @@ +package org.jeasy.random.beans; + +public record DirectlyNested(DirectlyNested child, Integer value) { +} diff --git a/easy-random-core/src/test/java/org/jeasy/random/beans/NestedRecordThroughCollection.java b/easy-random-core/src/test/java/org/jeasy/random/beans/NestedRecordThroughCollection.java new file mode 100644 index 00000000..a0394fdb --- /dev/null +++ b/easy-random-core/src/test/java/org/jeasy/random/beans/NestedRecordThroughCollection.java @@ -0,0 +1,6 @@ +package org.jeasy.random.beans; + +import java.util.List; + +public record NestedRecordThroughCollection(List children) { +} diff --git a/easy-random-core/src/test/java/org/jeasy/random/beans/RecordLimitationsTest.java b/easy-random-core/src/test/java/org/jeasy/random/beans/RecordLimitationsTest.java new file mode 100644 index 00000000..aa4e56b8 --- /dev/null +++ b/easy-random-core/src/test/java/org/jeasy/random/beans/RecordLimitationsTest.java @@ -0,0 +1,78 @@ +package org.jeasy.random.beans; + +import org.jeasy.random.EasyRandom; +import org.jeasy.random.EasyRandomParameters; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class RecordLimitationsTest { + + @Test + void shouldGenerateNonPublicClasses() { + //given + EasyRandomParameters parameters = new EasyRandomParameters(); + EasyRandom easyRandom = new EasyRandom(parameters); + + //when + WrapperOfNonPublic.NonPublicClass ordinaryClass = easyRandom.nextObject(WrapperOfNonPublic.NonPublicClass.class); + + //then + assertThat(ordinaryClass.name).isNotEmpty(); + } + + //This tst fail despite its counterpart regarding classes passes. + @Test + void shouldGenerateNonPublicRecords() { + //given + EasyRandomParameters parameters = new EasyRandomParameters(); + EasyRandom easyRandom = new EasyRandom(parameters); + + //when + WrapperOfNonPublic.NonPublicRecord record = easyRandom.nextObject(WrapperOfNonPublic.NonPublicRecord.class); + + //then + assertThat(record.name()).isNotEmpty(); + } + + @Test + @Timeout(3) + void shouldLimitTheNestingLevel_whenInDirectlyRecursiveStructures() { + //given + EasyRandomParameters parameters = new EasyRandomParameters(); + parameters.randomizationDepth(2); + EasyRandom easyRandom = new EasyRandom(parameters); + + //when + DirectlyNested actual = easyRandom.nextObject(DirectlyNested.class); + + //then + assertThat(actual.value()).isNotNull(); + assertThat(actual.child().child().child()) + .as("On the 3rd level, the field values should equal null, i.e. end of nesting.") + .isEqualTo(new DirectlyNested(null,null)); + assertThat(actual.child().child().value()).isNotNull(); + } + + @Test + @Timeout(3) + void shouldLimitTheNestingLevel_whenInRecursiveStructures() { + //given + EasyRandomParameters parameters = new EasyRandomParameters(); + parameters.randomizationDepth(1); + parameters.setCollectionSizeRange(new EasyRandomParameters.Range<>(1,2)); + EasyRandom easyRandom = new EasyRandom(parameters); + + //when + NestedRecordThroughCollection actual = easyRandom.nextObject(NestedRecordThroughCollection.class); + + //then + assertThat(actual.children()).isNotEmpty(); + assertThat(actual.children().get(0).children().get(0)) + .as("On the 2nd level, the field should be initialized with empty list, i.e. end of nesting.") + .isEqualTo(new NestedRecordThroughCollection(List.of())); + } +} diff --git a/easy-random-core/src/test/java/org/jeasy/random/beans/WrapperOfNonPublic.java b/easy-random-core/src/test/java/org/jeasy/random/beans/WrapperOfNonPublic.java new file mode 100644 index 00000000..5ff64996 --- /dev/null +++ b/easy-random-core/src/test/java/org/jeasy/random/beans/WrapperOfNonPublic.java @@ -0,0 +1,10 @@ +package org.jeasy.random.beans; + +public class WrapperOfNonPublic { + record NonPublicRecord(String name) { + } + + class NonPublicClass { + String name; + } +} \ No newline at end of file From adee302eb1be89ceff816d25ab17251571bf51e7 Mon Sep 17 00:00:00 2001 From: dvgaba <3621249+dvgaba@users.noreply.github.com> Date: Sat, 6 May 2023 19:50:22 -0400 Subject: [PATCH 2/8] Applied Spotless --- .../random/DepthLimitationObjectFactory.java | 4 ++-- .../java/org/jeasy/random/RecordFactory.java | 11 ++++------ .../jeasy/random/beans/DirectlyNested.java | 3 +-- .../beans/NestedRecordThroughCollection.java | 3 +-- .../random/beans/RecordLimitationsTest.java | 21 ++++++++++--------- .../random/beans/WrapperOfNonPublic.java | 7 ++++--- 6 files changed, 23 insertions(+), 26 deletions(-) diff --git a/easy-random-core/src/main/java/org/jeasy/random/DepthLimitationObjectFactory.java b/easy-random-core/src/main/java/org/jeasy/random/DepthLimitationObjectFactory.java index 13bd3c50..8b8b566c 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/DepthLimitationObjectFactory.java +++ b/easy-random-core/src/main/java/org/jeasy/random/DepthLimitationObjectFactory.java @@ -1,10 +1,10 @@ package org.jeasy.random; -import org.jeasy.random.util.ReflectionUtils; - import java.lang.reflect.Array; +import org.jeasy.random.util.ReflectionUtils; class DepthLimitationObjectFactory { + static Object produceEmptyValueForField(Class fieldType) { if (ReflectionUtils.isArrayType(fieldType)) { return Array.newInstance(fieldType.getComponentType(), 0); diff --git a/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java b/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java index 622cb15d..877f1adc 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java +++ b/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java @@ -31,7 +31,6 @@ import java.lang.reflect.Constructor; import java.lang.reflect.RecordComponent; import java.lang.reflect.Type; - import org.jeasy.random.api.RandomizerContext; /** @@ -77,15 +76,13 @@ private T createRandomRecord(Class recordType, RandomizationContext conte randomValues[i] = new ArrayPopulator(easyRandom).getRandomArray(type, context); } else if (isMapType(type)) { randomValues[i] = - new MapPopulator(easyRandom, context.getParameters().getObjectFactory()) - .getRandomMap(genericType, type, context); + new MapPopulator(easyRandom, context.getParameters().getObjectFactory()) + .getRandomMap(genericType, type, context); } else if (isOptionalType(type)) { - randomValues[i] = - new OptionalPopulator(easyRandom).getRandomOptional(genericType, context); + randomValues[i] = new OptionalPopulator(easyRandom).getRandomOptional(genericType, context); } else if (isCollectionType(type)) { randomValues[i] = - new CollectionPopulator(easyRandom) - .getRandomCollection(genericType, type, context); + new CollectionPopulator(easyRandom).getRandomCollection(genericType, type, context); } else { randomValues[i] = easyRandom.doPopulateBean(type, context); } diff --git a/easy-random-core/src/test/java/org/jeasy/random/beans/DirectlyNested.java b/easy-random-core/src/test/java/org/jeasy/random/beans/DirectlyNested.java index 0ae7002e..88860117 100644 --- a/easy-random-core/src/test/java/org/jeasy/random/beans/DirectlyNested.java +++ b/easy-random-core/src/test/java/org/jeasy/random/beans/DirectlyNested.java @@ -1,4 +1,3 @@ package org.jeasy.random.beans; -public record DirectlyNested(DirectlyNested child, Integer value) { -} +public record DirectlyNested(DirectlyNested child, Integer value) {} diff --git a/easy-random-core/src/test/java/org/jeasy/random/beans/NestedRecordThroughCollection.java b/easy-random-core/src/test/java/org/jeasy/random/beans/NestedRecordThroughCollection.java index a0394fdb..35102528 100644 --- a/easy-random-core/src/test/java/org/jeasy/random/beans/NestedRecordThroughCollection.java +++ b/easy-random-core/src/test/java/org/jeasy/random/beans/NestedRecordThroughCollection.java @@ -2,5 +2,4 @@ import java.util.List; -public record NestedRecordThroughCollection(List children) { -} +public record NestedRecordThroughCollection(List children) {} diff --git a/easy-random-core/src/test/java/org/jeasy/random/beans/RecordLimitationsTest.java b/easy-random-core/src/test/java/org/jeasy/random/beans/RecordLimitationsTest.java index aa4e56b8..d21296b7 100644 --- a/easy-random-core/src/test/java/org/jeasy/random/beans/RecordLimitationsTest.java +++ b/easy-random-core/src/test/java/org/jeasy/random/beans/RecordLimitationsTest.java @@ -1,14 +1,13 @@ package org.jeasy.random.beans; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; import org.jeasy.random.EasyRandom; import org.jeasy.random.EasyRandomParameters; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; - public class RecordLimitationsTest { @Test @@ -18,7 +17,9 @@ void shouldGenerateNonPublicClasses() { EasyRandom easyRandom = new EasyRandom(parameters); //when - WrapperOfNonPublic.NonPublicClass ordinaryClass = easyRandom.nextObject(WrapperOfNonPublic.NonPublicClass.class); + WrapperOfNonPublic.NonPublicClass ordinaryClass = easyRandom.nextObject( + WrapperOfNonPublic.NonPublicClass.class + ); //then assertThat(ordinaryClass.name).isNotEmpty(); @@ -52,8 +53,8 @@ void shouldLimitTheNestingLevel_whenInDirectlyRecursiveStructures() { //then assertThat(actual.value()).isNotNull(); assertThat(actual.child().child().child()) - .as("On the 3rd level, the field values should equal null, i.e. end of nesting.") - .isEqualTo(new DirectlyNested(null,null)); + .as("On the 3rd level, the field values should equal null, i.e. end of nesting.") + .isEqualTo(new DirectlyNested(null, null)); assertThat(actual.child().child().value()).isNotNull(); } @@ -63,7 +64,7 @@ void shouldLimitTheNestingLevel_whenInRecursiveStructures() { //given EasyRandomParameters parameters = new EasyRandomParameters(); parameters.randomizationDepth(1); - parameters.setCollectionSizeRange(new EasyRandomParameters.Range<>(1,2)); + parameters.setCollectionSizeRange(new EasyRandomParameters.Range<>(1, 2)); EasyRandom easyRandom = new EasyRandom(parameters); //when @@ -72,7 +73,7 @@ void shouldLimitTheNestingLevel_whenInRecursiveStructures() { //then assertThat(actual.children()).isNotEmpty(); assertThat(actual.children().get(0).children().get(0)) - .as("On the 2nd level, the field should be initialized with empty list, i.e. end of nesting.") - .isEqualTo(new NestedRecordThroughCollection(List.of())); + .as("On the 2nd level, the field should be initialized with empty list, i.e. end of nesting.") + .isEqualTo(new NestedRecordThroughCollection(List.of())); } } diff --git a/easy-random-core/src/test/java/org/jeasy/random/beans/WrapperOfNonPublic.java b/easy-random-core/src/test/java/org/jeasy/random/beans/WrapperOfNonPublic.java index 5ff64996..1d04abe7 100644 --- a/easy-random-core/src/test/java/org/jeasy/random/beans/WrapperOfNonPublic.java +++ b/easy-random-core/src/test/java/org/jeasy/random/beans/WrapperOfNonPublic.java @@ -1,10 +1,11 @@ package org.jeasy.random.beans; public class WrapperOfNonPublic { - record NonPublicRecord(String name) { - } + + record NonPublicRecord(String name) {} class NonPublicClass { + String name; } -} \ No newline at end of file +} From d16b5a72fabecdb2b227462f04811b2185989b7b Mon Sep 17 00:00:00 2001 From: mjureczko Date: Sat, 27 May 2023 20:01:22 +0200 Subject: [PATCH 3/8] #31 - initialize collections at the end of nesting data structures --- .../java/org/jeasy/random/RecordFactory.java | 52 ++++++------ .../jeasy/random/RecordFieldPopulator.java | 15 ++-- .../jeasy/random/RecordLimitationsTest.java | 3 +- .../random/beans/RecordLimitationsTest.java | 79 ------------------- .../random/beans/WrapperOfNonPublic.java | 11 --- 5 files changed, 32 insertions(+), 128 deletions(-) delete mode 100644 easy-random-core/src/test/java/org/jeasy/random/beans/RecordLimitationsTest.java delete mode 100644 easy-random-core/src/test/java/org/jeasy/random/beans/WrapperOfNonPublic.java diff --git a/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java b/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java index c6c72a87..f07f68d2 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java +++ b/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java @@ -26,6 +26,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.RecordComponent; + import org.jeasy.random.api.RandomizerContext; /** @@ -43,14 +44,14 @@ public class RecordFactory extends ObjenesisObjectFactory { public RecordFactory(EasyRandom easyRandom) { this.easyRandom = easyRandom; recordFieldPopulator = - new RecordFieldPopulator( - easyRandom, - easyRandom.getRandomizerProvider(), - new ArrayPopulator(easyRandom), - new CollectionPopulator(easyRandom), - new MapPopulator(easyRandom, easyRandom.getObjectFactory()), - new OptionalPopulator(easyRandom) - ); + new RecordFieldPopulator( + easyRandom, + easyRandom.getRandomizerProvider(), + new ArrayPopulator(easyRandom), + new CollectionPopulator(easyRandom), + new MapPopulator(easyRandom, easyRandom.getObjectFactory()), + new OptionalPopulator(easyRandom) + ); } @Override @@ -67,30 +68,25 @@ private T createRandomRecord(Class recordType, RandomizationContext conte Field[] fields = recordType.getDeclaredFields(); RecordComponent[] recordComponents = recordType.getRecordComponents(); Object[] randomValues = new Object[recordComponents.length]; - if (context.hasExceededRandomizationDepth()) { - for (int i = 0; i < recordComponents.length; i++) { - Class type = recordComponents[i].getType(); - randomValues[i] = DepthLimitationObjectFactory.produceEmptyValueForField(type); - } - } else { - for (int i = 0; i < recordComponents.length; i++) { - context.pushStackItem(new RandomizationContextStackItem(recordType, null)); - Class type = recordComponents[i].getType(); - try { - if (isRecord(type)) { - randomValues[i] = easyRandom.doPopulateBean(type, context); - } else { - randomValues[i] = this.recordFieldPopulator.populateField(fields[i], recordType, context); - } - } catch (IllegalAccessException e) { - throw new ObjectCreationException( + + for (int i = 0; i < recordComponents.length; i++) { + context.pushStackItem(new RandomizationContextStackItem(recordType, null)); + Class type = recordComponents[i].getType(); + try { + if (isRecord(type)) { + randomValues[i] = easyRandom.doPopulateBean(type, context); + } else { + randomValues[i] = this.recordFieldPopulator.populateField(fields[i], recordType, context); + } + } catch (IllegalAccessException e) { + throw new ObjectCreationException( "Unable to create a random instance of recordType " + recordType, e - ); - } - context.popStackItem(); + ); } + context.popStackItem(); } + // create a random instance with random values try { Constructor canonicalConstructor = getCanonicalConstructor(recordType); diff --git a/easy-random-core/src/main/java/org/jeasy/random/RecordFieldPopulator.java b/easy-random-core/src/main/java/org/jeasy/random/RecordFieldPopulator.java index 6393e65d..eebb2471 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/RecordFieldPopulator.java +++ b/easy-random-core/src/main/java/org/jeasy/random/RecordFieldPopulator.java @@ -83,20 +83,20 @@ Object populateField(final Field field, Class enclosingType, final Randomizat if (randomizer instanceof SkipRandomizer) { return null; } - //context.pushStackItem(new RandomizationContextStackItem(null, field)); if (randomizer instanceof ContextAwareRandomizer) { ((ContextAwareRandomizer) randomizer).setRandomizerContext(context); } - if (!context.hasExceededRandomizationDepth()) { - Object value; + if (context.hasExceededRandomizationDepth()) { + return DepthLimitationObjectFactory.produceEmptyValueForField(field.getType()); + } else { if (randomizer != null) { - value = randomizer.getRandomValue(); + return randomizer.getRandomValue(); } else { try { - value = generateRandomValue(field, context); + return generateRandomValue(field, context); } catch (ObjectCreationException e) { String exceptionMessage = String.format( - "Unable to create type: %s for field: %s of class: %s", + "Unable to create type: %s for field: %s of record: %s", field.getType().getName(), field.getName(), enclosingType.getName() @@ -105,10 +105,7 @@ Object populateField(final Field field, Class enclosingType, final Randomizat throw new ObjectCreationException(exceptionMessage, e); } } - return value; } - //context.popStackItem(); - return null; } private Randomizer getRandomizer(Field field, RandomizationContext context, Class fieldTargetType) { diff --git a/easy-random-core/src/test/java/org/jeasy/random/RecordLimitationsTest.java b/easy-random-core/src/test/java/org/jeasy/random/RecordLimitationsTest.java index 220fb9a1..990013a8 100644 --- a/easy-random-core/src/test/java/org/jeasy/random/RecordLimitationsTest.java +++ b/easy-random-core/src/test/java/org/jeasy/random/RecordLimitationsTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.jeasy.random.FieldPredicates.ofType; +import java.util.List; import java.util.Objects; import org.jeasy.random.beans.DirectlyNested; import org.jeasy.random.beans.NestedRecordThroughCollection; @@ -76,7 +77,7 @@ void shouldLimitTheNestingLevel_whenInRecursiveStructures() { assertThat(actual.children()).isNotEmpty(); assertThat(actual.children().get(0)) .as("On the 2nd level, the field should be initialized with empty list, i.e. end of nesting.") - .isEqualTo(new NestedRecordThroughCollection(null)); + .isEqualTo(new NestedRecordThroughCollection(List.of())); } @Test diff --git a/easy-random-core/src/test/java/org/jeasy/random/beans/RecordLimitationsTest.java b/easy-random-core/src/test/java/org/jeasy/random/beans/RecordLimitationsTest.java deleted file mode 100644 index d21296b7..00000000 --- a/easy-random-core/src/test/java/org/jeasy/random/beans/RecordLimitationsTest.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.jeasy.random.beans; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.util.List; -import org.jeasy.random.EasyRandom; -import org.jeasy.random.EasyRandomParameters; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; - -public class RecordLimitationsTest { - - @Test - void shouldGenerateNonPublicClasses() { - //given - EasyRandomParameters parameters = new EasyRandomParameters(); - EasyRandom easyRandom = new EasyRandom(parameters); - - //when - WrapperOfNonPublic.NonPublicClass ordinaryClass = easyRandom.nextObject( - WrapperOfNonPublic.NonPublicClass.class - ); - - //then - assertThat(ordinaryClass.name).isNotEmpty(); - } - - //This tst fail despite its counterpart regarding classes passes. - @Test - void shouldGenerateNonPublicRecords() { - //given - EasyRandomParameters parameters = new EasyRandomParameters(); - EasyRandom easyRandom = new EasyRandom(parameters); - - //when - WrapperOfNonPublic.NonPublicRecord record = easyRandom.nextObject(WrapperOfNonPublic.NonPublicRecord.class); - - //then - assertThat(record.name()).isNotEmpty(); - } - - @Test - @Timeout(3) - void shouldLimitTheNestingLevel_whenInDirectlyRecursiveStructures() { - //given - EasyRandomParameters parameters = new EasyRandomParameters(); - parameters.randomizationDepth(2); - EasyRandom easyRandom = new EasyRandom(parameters); - - //when - DirectlyNested actual = easyRandom.nextObject(DirectlyNested.class); - - //then - assertThat(actual.value()).isNotNull(); - assertThat(actual.child().child().child()) - .as("On the 3rd level, the field values should equal null, i.e. end of nesting.") - .isEqualTo(new DirectlyNested(null, null)); - assertThat(actual.child().child().value()).isNotNull(); - } - - @Test - @Timeout(3) - void shouldLimitTheNestingLevel_whenInRecursiveStructures() { - //given - EasyRandomParameters parameters = new EasyRandomParameters(); - parameters.randomizationDepth(1); - parameters.setCollectionSizeRange(new EasyRandomParameters.Range<>(1, 2)); - EasyRandom easyRandom = new EasyRandom(parameters); - - //when - NestedRecordThroughCollection actual = easyRandom.nextObject(NestedRecordThroughCollection.class); - - //then - assertThat(actual.children()).isNotEmpty(); - assertThat(actual.children().get(0).children().get(0)) - .as("On the 2nd level, the field should be initialized with empty list, i.e. end of nesting.") - .isEqualTo(new NestedRecordThroughCollection(List.of())); - } -} diff --git a/easy-random-core/src/test/java/org/jeasy/random/beans/WrapperOfNonPublic.java b/easy-random-core/src/test/java/org/jeasy/random/beans/WrapperOfNonPublic.java deleted file mode 100644 index 1d04abe7..00000000 --- a/easy-random-core/src/test/java/org/jeasy/random/beans/WrapperOfNonPublic.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.jeasy.random.beans; - -public class WrapperOfNonPublic { - - record NonPublicRecord(String name) {} - - class NonPublicClass { - - String name; - } -} From e4abc310a84bb85a36313c708a1b8bfb28b38818 Mon Sep 17 00:00:00 2001 From: dvgaba <3621249+dvgaba@users.noreply.github.com> Date: Sun, 28 May 2023 22:15:17 -0400 Subject: [PATCH 4/8] Formatting --- .../java/org/jeasy/random/RecordFactory.java | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java b/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java index f07f68d2..7b33beb2 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java +++ b/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java @@ -26,7 +26,6 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.RecordComponent; - import org.jeasy.random.api.RandomizerContext; /** @@ -44,14 +43,14 @@ public class RecordFactory extends ObjenesisObjectFactory { public RecordFactory(EasyRandom easyRandom) { this.easyRandom = easyRandom; recordFieldPopulator = - new RecordFieldPopulator( - easyRandom, - easyRandom.getRandomizerProvider(), - new ArrayPopulator(easyRandom), - new CollectionPopulator(easyRandom), - new MapPopulator(easyRandom, easyRandom.getObjectFactory()), - new OptionalPopulator(easyRandom) - ); + new RecordFieldPopulator( + easyRandom, + easyRandom.getRandomizerProvider(), + new ArrayPopulator(easyRandom), + new CollectionPopulator(easyRandom), + new MapPopulator(easyRandom, easyRandom.getObjectFactory()), + new OptionalPopulator(easyRandom) + ); } @Override @@ -79,10 +78,7 @@ private T createRandomRecord(Class recordType, RandomizationContext conte randomValues[i] = this.recordFieldPopulator.populateField(fields[i], recordType, context); } } catch (IllegalAccessException e) { - throw new ObjectCreationException( - "Unable to create a random instance of recordType " + recordType, - e - ); + throw new ObjectCreationException("Unable to create a random instance of recordType " + recordType, e); } context.popStackItem(); } From cea6169c0d94b1cb31fb76944813eedefcc2bb56 Mon Sep 17 00:00:00 2001 From: "marian.jureczko" Date: Mon, 29 May 2023 14:37:03 +0200 Subject: [PATCH 5/8] #31 fix for a failing and for a blinking tests --- .../java/org/jeasy/random/RecordFactory.java | 8 +++++-- .../jeasy/random/RecordLimitationsTest.java | 2 +- .../misc/LocaleRandomizerTest.java | 22 ++----------------- 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java b/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java index 7b33beb2..4a97abca 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java +++ b/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java @@ -73,9 +73,13 @@ private T createRandomRecord(Class recordType, RandomizationContext conte Class type = recordComponents[i].getType(); try { if (isRecord(type)) { - randomValues[i] = easyRandom.doPopulateBean(type, context); + if (context.hasExceededRandomizationDepth()) { + randomValues[i] = DepthLimitationObjectFactory.produceEmptyValueForField(type); + } else { + randomValues[i] = easyRandom.doPopulateBean(type, context); + } } else { - randomValues[i] = this.recordFieldPopulator.populateField(fields[i], recordType, context); + randomValues[i] = recordFieldPopulator.populateField(fields[i], recordType, context); } } catch (IllegalAccessException e) { throw new ObjectCreationException("Unable to create a random instance of recordType " + recordType, e); diff --git a/easy-random-core/src/test/java/org/jeasy/random/RecordLimitationsTest.java b/easy-random-core/src/test/java/org/jeasy/random/RecordLimitationsTest.java index 990013a8..63ca572d 100644 --- a/easy-random-core/src/test/java/org/jeasy/random/RecordLimitationsTest.java +++ b/easy-random-core/src/test/java/org/jeasy/random/RecordLimitationsTest.java @@ -57,7 +57,7 @@ void shouldLimitTheNestingLevel_whenInDirectlyRecursiveStructures() { assertThat(actual.value()).isNotNull(); assertThat(actual.child().child().child()) .as("On the 3rd level, the field values should equal null, i.e. end of nesting.") - .isEqualTo(new DirectlyNested(null, null)); + .isNull(); assertThat(actual.child().child().value()).isNull(); } diff --git a/easy-random-core/src/test/java/org/jeasy/random/randomizers/misc/LocaleRandomizerTest.java b/easy-random-core/src/test/java/org/jeasy/random/randomizers/misc/LocaleRandomizerTest.java index 0f6b67b8..20667110 100644 --- a/easy-random-core/src/test/java/org/jeasy/random/randomizers/misc/LocaleRandomizerTest.java +++ b/easy-random-core/src/test/java/org/jeasy/random/randomizers/misc/LocaleRandomizerTest.java @@ -39,25 +39,7 @@ void shouldGenerateRandomLocale() { @Test void shouldGenerateTheSameValueForTheSameSeed() { - BigDecimal javaVersion = new BigDecimal(System.getProperty("java.specification.version")); - if (javaVersion.compareTo(new BigDecimal("18")) >= 0) { - assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("mni", "")); - } else if (javaVersion.compareTo(new BigDecimal("17")) >= 0) { - assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("en", "CA")); - } else if (javaVersion.compareTo(new BigDecimal("16")) >= 0) { - assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("en", "CA")); - } else if (javaVersion.compareTo(new BigDecimal("15")) >= 0) { - assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("en", "CA")); - } else if (javaVersion.compareTo(new BigDecimal("14")) >= 0) { - assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("en", "CA")); - } else if (javaVersion.compareTo(new BigDecimal("13")) >= 0) { - assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("zh", "CN")); - } else if (javaVersion.compareTo(new BigDecimal("11")) >= 0) { - assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("en", "CK")); - } else if (javaVersion.compareTo(new BigDecimal("9")) >= 0) { - assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("sw", "ke")); - } else { - assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(new Locale("nl", "be")); - } + Locale expected = new LocaleRandomizer(SEED).getRandomValue(); + assertThat(new LocaleRandomizer(SEED).getRandomValue()).isEqualTo(expected); } } From 0475de4bafa2c3cf385116983684cb8c01854438 Mon Sep 17 00:00:00 2001 From: "marian.jureczko" Date: Tue, 30 May 2023 07:10:58 +0200 Subject: [PATCH 6/8] #31 refactoring --- .../java/org/jeasy/random/RecordFactory.java | 12 +++++----- .../jeasy/random/RecordFieldPopulator.java | 22 ++++++++----------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java b/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java index 4a97abca..80ef6d57 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java +++ b/easy-random-core/src/main/java/org/jeasy/random/RecordFactory.java @@ -72,14 +72,14 @@ private T createRandomRecord(Class recordType, RandomizationContext conte context.pushStackItem(new RandomizationContextStackItem(recordType, null)); Class type = recordComponents[i].getType(); try { - if (isRecord(type)) { - if (context.hasExceededRandomizationDepth()) { - randomValues[i] = DepthLimitationObjectFactory.produceEmptyValueForField(type); - } else { + if (context.hasExceededRandomizationDepth()) { + randomValues[i] = DepthLimitationObjectFactory.produceEmptyValueForField(type); + } else { + if (isRecord(type)) { randomValues[i] = easyRandom.doPopulateBean(type, context); + } else { + randomValues[i] = recordFieldPopulator.populateField(fields[i], recordType, context); } - } else { - randomValues[i] = recordFieldPopulator.populateField(fields[i], recordType, context); } } catch (IllegalAccessException e) { throw new ObjectCreationException("Unable to create a random instance of recordType " + recordType, e); diff --git a/easy-random-core/src/main/java/org/jeasy/random/RecordFieldPopulator.java b/easy-random-core/src/main/java/org/jeasy/random/RecordFieldPopulator.java index eebb2471..9d513d83 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/RecordFieldPopulator.java +++ b/easy-random-core/src/main/java/org/jeasy/random/RecordFieldPopulator.java @@ -86,24 +86,20 @@ Object populateField(final Field field, Class enclosingType, final Randomizat if (randomizer instanceof ContextAwareRandomizer) { ((ContextAwareRandomizer) randomizer).setRandomizerContext(context); } - if (context.hasExceededRandomizationDepth()) { - return DepthLimitationObjectFactory.produceEmptyValueForField(field.getType()); + if (randomizer != null) { + return randomizer.getRandomValue(); } else { - if (randomizer != null) { - return randomizer.getRandomValue(); - } else { - try { - return generateRandomValue(field, context); - } catch (ObjectCreationException e) { - String exceptionMessage = String.format( + try { + return generateRandomValue(field, context); + } catch (ObjectCreationException e) { + String exceptionMessage = String.format( "Unable to create type: %s for field: %s of record: %s", field.getType().getName(), field.getName(), enclosingType.getName() - ); - // FIXME catch ObjectCreationException and throw ObjectCreationException ? - throw new ObjectCreationException(exceptionMessage, e); - } + ); + // FIXME catch ObjectCreationException and throw ObjectCreationException ? + throw new ObjectCreationException(exceptionMessage, e); } } } From 7fb0f9882ef1641ed26e771aeb0dd98a615cac32 Mon Sep 17 00:00:00 2001 From: "marian.jureczko" Date: Mon, 14 Oct 2024 08:02:13 +0200 Subject: [PATCH 7/8] Tests for creating instances with sealed interfaces --- .../org/jeasy/random/SealedInterfaceTest.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 easy-random-core/src/test/java/org/jeasy/random/SealedInterfaceTest.java diff --git a/easy-random-core/src/test/java/org/jeasy/random/SealedInterfaceTest.java b/easy-random-core/src/test/java/org/jeasy/random/SealedInterfaceTest.java new file mode 100644 index 00000000..b051ffe6 --- /dev/null +++ b/easy-random-core/src/test/java/org/jeasy/random/SealedInterfaceTest.java @@ -0,0 +1,91 @@ +package org.jeasy.random; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SealedInterfaceTest { + EasyRandomParameters parameters = new EasyRandomParameters(); + EasyRandom easyRandom = new EasyRandom(parameters); + + @Test + void should_arrangeSealedInterface() { + //when + SealedInterfaceData actual = easyRandom.nextObject(SealedInterfaceData.class); + + //then + assertThat(actual).isInstanceOfAny(ConcreteData1.class, ConcreteData2.class); + } + + @Test + void should_arrangeDataWithNestedSealedInterfaceField() { + //when + NestedSealedInterfaceData actual = easyRandom.nextObject(NestedSealedInterfaceData.class); + + //then + assertThat(actual).isInstanceOf(ConcreteNested1.class); + assertThat((ConcreteNested1) actual) + .extracting(ConcreteNested1::sealedInterfaceData) + .isInstanceOfAny(ConcreteData1.class, ConcreteData2.class); + + } + + @Test + void should_arrangeRecordWithListWithSealedInterfaceField() { + //when + RootRecordWithNestedList actual = easyRandom.nextObject(RootRecordWithNestedList.class); + + //then + assertThat(actual.nestedRecordWithSealedInterfaces().get(0)) + .extracting(NestedRecordWithSealedInterface::sealedInterfaceData) + .isInstanceOfAny(ConcreteData1.class, ConcreteData2.class); + } +} + +record DataWithAbstract(Integer value, + SealedInterfaceData sealedInterfaceData, + ExcludedSealedInterface excludedSealedInterface, + NonSealedInterface nonSealedInterface) { + DataWithAbstract { + } + + DataWithAbstract(String name) { + this(0, new ConcreteData1(""), new ExcludedData1(), new NonSealedInterface() { + }); + } +} + +sealed interface SealedInterfaceData permits ConcreteData1, ConcreteData2 { +} + +record ConcreteData1(String value) implements SealedInterfaceData { +} + +final class ConcreteData2 implements SealedInterfaceData { + + Integer value; + +} + +sealed interface ExcludedSealedInterface permits ExcludedData1 { +} + +record ExcludedData1() implements ExcludedSealedInterface { +} + +interface NonSealedInterface { +} + +sealed interface NestedSealedInterfaceData permits ConcreteNested1 { +} + +record ConcreteNested1(SealedInterfaceData sealedInterfaceData) implements NestedSealedInterfaceData { +} + +record RootRecordWithNestedList(List nestedRecordWithSealedInterfaces) { +} + +record NestedRecordWithSealedInterface(SealedInterfaceData sealedInterfaceData) { +} From 7bad6fad7c3ef3641e358b7ce7547bce34ab2157 Mon Sep 17 00:00:00 2001 From: "marian.jureczko" Date: Mon, 14 Oct 2024 08:54:23 +0200 Subject: [PATCH 8/8] Sealed interfaces support --- easy-random-bean-validation/pom.xml | 2 +- easy-random-core/pom.xml | 2 +- .../src/main/java/org/jeasy/random/EasyRandom.java | 10 ++++++++++ easy-random-protobuf/pom.xml | 2 +- easy-random-randomizers/pom.xml | 2 +- pom.xml | 2 +- 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/easy-random-bean-validation/pom.xml b/easy-random-bean-validation/pom.xml index cce35bbc..61c2ed32 100644 --- a/easy-random-bean-validation/pom.xml +++ b/easy-random-bean-validation/pom.xml @@ -4,7 +4,7 @@ io.github.dvgaba easy-random - 7.0.0 + 7.0.1-SNAPSHOT 4.0.0 easy-random-bean-validation diff --git a/easy-random-core/pom.xml b/easy-random-core/pom.xml index 0aa4781f..4448540a 100644 --- a/easy-random-core/pom.xml +++ b/easy-random-core/pom.xml @@ -4,7 +4,7 @@ io.github.dvgaba easy-random - 7.0.0 + 7.0.1-SNAPSHOT 4.0.0 easy-random-core diff --git a/easy-random-core/src/main/java/org/jeasy/random/EasyRandom.java b/easy-random-core/src/main/java/org/jeasy/random/EasyRandom.java index 292723a7..060ef1d8 100644 --- a/easy-random-core/src/main/java/org/jeasy/random/EasyRandom.java +++ b/easy-random-core/src/main/java/org/jeasy/random/EasyRandom.java @@ -113,6 +113,10 @@ private boolean isRecord(final Class type) { return type.isRecord(); } + private static boolean isSealedInterface(Class type) { + return type.isSealed() && type.isInterface(); + } + /** * Generate a stream of random instances of the given type. * @@ -145,6 +149,12 @@ T doPopulateBean(final Class type, final RandomizationContext context) { return (T) randomizer.getRandomValue(); } + if (isSealedInterface(type)) { + Class[] subclasses = type.getPermittedSubclasses(); + Class subclass = subclasses[nextInt(subclasses.length)]; + return (T) nextObject(subclass); + } + if (isRecord(type)) { return recordFactory.createInstance(type, context); } diff --git a/easy-random-protobuf/pom.xml b/easy-random-protobuf/pom.xml index f974afd5..02270be2 100644 --- a/easy-random-protobuf/pom.xml +++ b/easy-random-protobuf/pom.xml @@ -4,7 +4,7 @@ io.github.dvgaba easy-random - 7.0.0 + 7.0.1-SNAPSHOT 4.0.0 easy-random-protobuf diff --git a/easy-random-randomizers/pom.xml b/easy-random-randomizers/pom.xml index eba896e6..4f1b6269 100644 --- a/easy-random-randomizers/pom.xml +++ b/easy-random-randomizers/pom.xml @@ -4,7 +4,7 @@ io.github.dvgaba easy-random - 7.0.0 + 7.0.1-SNAPSHOT 4.0.0 easy-random-randomizers diff --git a/pom.xml b/pom.xml index 80c11953..d9407048 100644 --- a/pom.xml +++ b/pom.xml @@ -9,7 +9,7 @@ 4.0.0 io.github.dvgaba easy-random - 7.0.0 + 7.0.1-SNAPSHOT pom