Skip to content

Commit

Permalink
Merge pull request #68 from ocadotechnology/easy_random_with_seal
Browse files Browse the repository at this point in the history
Easy random with seal
  • Loading branch information
mjureczko authored Oct 28, 2024
2 parents f7042ee + e00f72d commit 846bce0
Show file tree
Hide file tree
Showing 8 changed files with 34 additions and 158 deletions.
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,14 @@ product.setBrand("Ocado");
<dependency>
<groupId>com.ocadotechnology.gembus</groupId>
<artifactId>test-arranger</artifactId>
<version>1.6.2</version>
<version>1.6.3</version>
</dependency>
```

### Gradle

```groovy
testImplementation 'com.ocadotechnology.gembus:test-arranger:1.6.2'
testImplementation 'com.ocadotechnology.gembus:test-arranger:1.6.3'
```

## Features
Expand Down Expand Up @@ -318,13 +318,11 @@ Eventually, we may end up with something like this:
class ShopFixture {
Repository repo;
public void shopWithNineProductsAndFourCustomers() {
Stream.generate(() -> Arranger.some(Product.class))
.limit(9)
.forEach(p -> repo.save(p));
Arranger.someObjects(Product.class, 9)
.forEach(p -> repo.save(p));

Stream.generate(() -> Arranger.some(Customer.class))
.limit(4)
.forEach(p -> repo.save(p));
Arranger.someObjects(Customer.class, 4)
.forEach(p -> repo.save(p));
}
}
```
Expand Down
28 changes: 14 additions & 14 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.ocadotechnology.gembus</groupId>
<artifactId>test-arranger</artifactId>
<version>1.6.2.1</version>
<version>1.6.3</version>
<packaging>jar</packaging>
<name>test-arranger</name>
<description>A tool for arranging test data with pseudo-random values.</description>
Expand Down Expand Up @@ -59,8 +59,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>17</java.version>
<kotlin.version>1.9.22</kotlin.version>
<easy-random.version>7.0.0</easy-random.version>
<kotlin.version>1.9.25</kotlin.version>
<easy-random.version>7.1.0</easy-random.version>
</properties>

<dependencies>
Expand Down Expand Up @@ -95,17 +95,17 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
<version>3.17.0</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>2.2</version>
<version>2.3</version>
</dependency>
<dependency>
<groupId>io.github.classgraph</groupId>
<artifactId>classgraph</artifactId>
<version>4.8.165</version>
<version>4.8.177</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
Expand All @@ -130,7 +130,7 @@
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.25.1</version>
<version>3.26.3</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand All @@ -140,7 +140,7 @@
<dependency>
<groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId>
<version>5.10.1</version>
<version>5.11.3</version>
<type>pom</type>
<scope>import</scope>
</dependency>
Expand Down Expand Up @@ -195,7 +195,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<version>3.13.0</version>
<executions>
<!-- Replacing default-compile and default-testCompile as it is treated specially by maven -->
<execution>
Expand Down Expand Up @@ -229,13 +229,13 @@

<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
<version>3.5.1</version>
</plugin>

<plugin>
<groupId>com.mycila</groupId>
<artifactId>license-maven-plugin</artifactId>
<version>4.3</version>
<version>4.6</version>
<configuration>
<licenseSets>
<licenseSet>
Expand Down Expand Up @@ -284,7 +284,7 @@
<plugin>
<groupId>org.sonatype.plugins</groupId>
<artifactId>nexus-staging-maven-plugin</artifactId>
<version>1.6.14</version>
<version>1.7.0</version>
<extensions>true</extensions>
<configuration>
<serverId>ossrh</serverId>
Expand All @@ -296,7 +296,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.3.0</version>
<version>3.3.1</version>
<executions>
<execution>
<id>attach-sources</id>
Expand All @@ -310,7 +310,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
<version>3.10.1</version>
<executions>
<execution>
<id>attach-javadocs</id>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,11 @@ EnhancedRandom simplifiedRandom() {
return randomWithArrangers(simplifiedArrangers, new EnhancedRandom.Builder(ArrangersConfigurer::getEasyRandomSimplifiedParameters));
}

EnhancedRandom randomForGivenConfiguration(Class<?> type, boolean withoutGivenType, Map<Class<?>, CustomArranger<?>> arrangers, Supplier<EasyRandomParameters> parametersSupplier) {
EnhancedRandom randomForGivenConfiguration(Class<?> type, Map<Class<?>, CustomArranger<?>> arrangers, Supplier<EasyRandomParameters> parametersSupplier) {
EnhancedRandom.Builder randomBuilder = new EnhancedRandom.Builder(parametersSupplier);
long seed = SeedHelper.calculateSeed();
if (withoutGivenType && arrangers.get(type) != null) {
CustomArranger<?> arrangerToUpdate = arrangers.get(type);
if (arrangerToUpdate != null) {
seed = SeedHelper.customArrangerTypeSpecificSeedRespectingRandomSeedSetting(type);
arrangers = withoutGivenType(arrangers, type);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,9 @@
public abstract class CustomArranger<T> {

protected EnhancedRandom enhancedRandom = null;
protected final Class<T> type;
protected final Class<T> type = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];

protected CustomArranger() {
this.type = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
initEnhancedRandom();
}

protected CustomArranger(Class<T> type) {
this.type = type;
initEnhancedRandom();
}

private void initEnhancedRandom() {
if (ArrangersConfigurer.defaultInitialized.get()) {
enhancedRandom = ArrangersConfigurer.instance().defaultRandom();
} else {
Expand Down
94 changes: 9 additions & 85 deletions src/main/java/com/ocadotechnology/gembus/test/EnhancedRandom.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,15 @@
*/
package com.ocadotechnology.gembus.test;

import com.ocadotechnology.gembus.test.experimental.SealedInterfaceArranger;
import org.jeasy.random.EasyRandom;
import org.jeasy.random.EasyRandomParameters;
import org.jeasy.random.api.Randomizer;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.function.Function.identity;

/**
* Class for random object generator.
*/
Expand Down Expand Up @@ -96,90 +84,26 @@ public <T> Stream<T> objects(final Class<T> type, final int amount, final String
}

private <T> EasyRandom selectEasyRandomWithRespectToExclusion(Class<T> type, String[] excludedFields) {
if (newEasyRandomWithCustomRandomizersOrFieldExclusionConfigIsRequired(type, excludedFields)) {
return createEasyRandomWithCustomRandomizersAndExclusions(type, excludedFields);
if (newEasyRandomWithFieldExclusionConfigIsRequired(type, excludedFields)) {
return createEasyRandomWithExclusions(type, excludedFields);
}
return easyRandom;
}

private <T> boolean newEasyRandomWithCustomRandomizersOrFieldExclusionConfigIsRequired(Class<T> type, String[] excludedFields) {
private <T> boolean newEasyRandomWithFieldExclusionConfigIsRequired(Class<T> type, String[] excludedFields) {
/* There is a logical inconsistency in using a custom arranger and field exclusion for the same type - the
* exclusion can be configured in the custom arranger. Technically, creating an arranger with exclusion disables
* the custom arranger for the type that is being instantiated. */
return !arrangers.containsKey(type) && (excludedFields.length != 0 || isSealedInterface(type) || !nestedSealedInterfaceFields(type).isEmpty());
return !arrangers.containsKey(type) && excludedFields.length != 0;
}

private <T> EasyRandom createEasyRandomWithCustomRandomizersAndExclusions(Class<T> type, String[] excludedFields) {
private <T> EasyRandom createEasyRandomWithExclusions(Class<T> type, String[] excludedFields) {
Set<String> fields = new HashSet<>(Arrays.asList(excludedFields));
var forSealedInterfaces = createCustomArrangersForSealedInterfaces(type, fields);
Set<String> cacheKey = getCacheKey(fields, forSealedInterfaces.keySet());
cache.computeIfAbsent(cacheKey, key -> {
HashMap<Class<?>, CustomArranger<?>> enhancedArrangers = new HashMap<>(arrangers);
enhancedArrangers.putAll(forSealedInterfaces);
EnhancedRandom er = ArrangersConfigurer.instance()
.randomForGivenConfiguration(type, !forSealedInterfaces.containsKey(type), enhancedArrangers, () -> addExclusionToParameters(fields));
cache.computeIfAbsent(fields, key -> {
EnhancedRandom er = ArrangersConfigurer.instance().randomForGivenConfiguration(type, arrangers, () -> addExclusionToParameters(fields));
return er.easyRandom;
});
return cache.get(cacheKey);
}

private Set<String> getCacheKey(Set<String> fields, Set<Class<?>> sealedInterfaces) {
Set<String> cacheKey = new HashSet<>(fields);
cacheKey.addAll(sealedInterfaces.stream().map(Class::getName).toList());
return cacheKey;
}

private <T> Map<Class<?>, CustomArranger<?>> createCustomArrangersForSealedInterfaces(Class<T> type, Set<String> excludedFields) {
Map<Class<?>, CustomArranger<?>> sealedInterfaceArrangers = new HashMap<>();
if (isSealedInterface(type)) {
sealedInterfaceArrangers.put(type, new SealedInterfaceArranger<T>(type));
}
sealedInterfaceArrangers.putAll(nestedSealedInterfaceFields(type)
.entrySet()
.stream()
.filter(entry -> !excludedFields.contains(entry.getKey()))
.map(Map.Entry::getValue)
.collect(Collectors.toMap(identity(), SealedInterfaceArranger::new)));
return sealedInterfaceArrangers;
}

private <T> Map<String, Class<?>> nestedSealedInterfaceFields(Class<T> type) {
return allNestedFields(new HashMap<>(), type)
.entrySet()
.stream()
.filter(entry -> isSealedInterface(entry.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

private Map<String, Class<?>> allNestedFields(Map<String, Class<?>> acc, Class<?> clazz) {
if (clazz == null || clazz.isPrimitive()) {
return Map.of();
}
if (isSealedInterface(clazz)) {
for (Class<?> permittedSubclasses : clazz.getPermittedSubclasses()) {
acc.putAll(allNestedFields(acc, permittedSubclasses));
}
}
for (Field field : clazz.getDeclaredFields()) {
if (!acc.containsKey(field.getName())) {
if (field.getGenericType() instanceof ParameterizedType parameterizedType) {
for (var genericType : parameterizedType.getActualTypeArguments()) {
if(genericType instanceof Class<?> genericTypeClass) {
acc.put(field.getName(), genericTypeClass);
acc.putAll(allNestedFields(acc, genericTypeClass));
}
}
} else {
acc.put(field.getName(), field.getType());
acc.putAll(allNestedFields(acc, field.getType()));
}
}
}
return acc;
}

private static boolean isSealedInterface(Class<?> type) {
return type.isSealed() && type.isInterface();
return cache.get(fields);
}

private EasyRandomParameters addExclusionToParameters(Set<String> fields) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package com.ocadotechnology.gembus.test;

import com.ocadotechnology.gembus.test.experimental.SealedInterfaceArranger;
import io.github.classgraph.ClassGraph;

import java.lang.reflect.Constructor;
Expand All @@ -40,7 +39,6 @@ class ReflectionHelper {
.loadClasses(CustomArranger.class, true)
.stream()
.filter(clazz -> isNotAbstract(clazz))
.filter(clazz -> !SealedInterfaceArranger.class.equals(clazz))
.map(clazz -> extractConstructor(clazz))
.filter(constructor -> constructor.isPresent())
.map(constructor -> constructor.get())
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/
package com.ocadotechnology.gembus.test;

import com.ocadotechnology.gembus.test.experimental.SealedInterfaceArranger;
import org.jeasy.random.ObjectCreationException;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -152,7 +151,7 @@ sealed interface SealedInterfaceWithCustomArranger permits ConcreteDataWithCusto
record ConcreteDataWithCustomArranger(String test) implements SealedInterfaceWithCustomArranger {
}

class CustomSealedInterfaceArranger extends SealedInterfaceArranger<SealedInterfaceWithCustomArranger> {
class CustomSealedInterfaceArranger extends CustomArranger<SealedInterfaceWithCustomArranger> {

@Override
protected SealedInterfaceWithCustomArranger instance() {
Expand Down

0 comments on commit 846bce0

Please sign in to comment.