From 40961d4950c40643d5d71721a7e024e3951323ce Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Tue, 9 Jun 2015 23:31:54 +1000 Subject: Generalise the ObjectGeneration stuff The new ObjectGeneration stuff is now used to generate everything for a test case, which means it's all unified and "nice" now. Add a @UseGenerator annotation to be used to specify how to generate specific field values. Obviously, not everything can be generated magically, so if you specify a @DataSource in your test then it will be used in preference to any magically generated value. --- .../id/zancanaro/javacheck/DataSourceHelper.java | 46 ++-------------------- .../id/zancanaro/javacheck/junit/Properties.java | 36 +++++------------ .../zancanaro/javacheck/junit/PropertyError.java | 1 - .../au/id/zancanaro/javacheck/object/CharType.java | 2 +- .../id/zancanaro/javacheck/object/DoubleRange.java | 2 +- .../javacheck/object/GeneratorProvider.java | 29 ++++++++++++-- .../au/id/zancanaro/javacheck/object/IntRange.java | 2 +- .../id/zancanaro/javacheck/object/LongRange.java | 2 +- .../object/ObjectGenerationException.java | 4 ++ .../zancanaro/javacheck/object/UseGenerator.java | 14 +++++++ .../zancanaro/javacheck/ListFunctorRulesTest.java | 6 +-- .../javacheck/SimpleListOperationsTest.java | 8 ---- .../au/id/zancanaro/javacheck/object/MyObject.java | 21 +++++++++- .../javacheck/object/MyObjectAddTest.java | 8 ---- .../javacheck/state/queue/commands/Offer.java | 1 - 15 files changed, 82 insertions(+), 100 deletions(-) create mode 100644 src/main/java/au/id/zancanaro/javacheck/object/UseGenerator.java (limited to 'src') diff --git a/src/main/java/au/id/zancanaro/javacheck/DataSourceHelper.java b/src/main/java/au/id/zancanaro/javacheck/DataSourceHelper.java index 522fd26..f118649 100644 --- a/src/main/java/au/id/zancanaro/javacheck/DataSourceHelper.java +++ b/src/main/java/au/id/zancanaro/javacheck/DataSourceHelper.java @@ -3,10 +3,11 @@ package au.id.zancanaro.javacheck; import au.id.zancanaro.javacheck.annotations.DataSource; import java.lang.reflect.Field; -import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; public class DataSourceHelper { private final Class classObject; @@ -17,47 +18,6 @@ public class DataSourceHelper { this.generators = new HashMap<>(); } - public static Set validateGeneratorFields(Class classObject, List errors) { - Set result = new HashSet<>(); - - for (Field field : classObject.getDeclaredFields()) { - if (field.isAnnotationPresent(DataSource.class)) { - boolean error = false; - if (!Modifier.isStatic(field.getModifiers())) { - errors.add(new Error("@DataSource field " + field.getName() + " must be static")); - error = true; - } - if (!Modifier.isPublic(field.getModifiers())) { - errors.add(new Error("@DataSource field " + field.getName() + " must be public")); - error = true; - } - - Type type = field.getGenericType(); - ParameterizedType parameterizedType; - if (type instanceof ParameterizedType) { - parameterizedType = (ParameterizedType) type; - if (parameterizedType.getRawType() instanceof Class) { - Class c = (Class) parameterizedType.getRawType(); - if (c == Generator.class) { - if (!error) { - result.add(parameterizedType.getActualTypeArguments()[0]); - } - } else { - errors.add(new Error("@DataSource fields must be of type Generator")); - } - } else { - errors.add(new Error("@DataSource fields must be of type Generator")); - } - } else { - errors.add(new Error("@DataSource fields must be of type Generator")); - } - } - } - - return result; - } - - private static final Map rawTypes; static { diff --git a/src/main/java/au/id/zancanaro/javacheck/junit/Properties.java b/src/main/java/au/id/zancanaro/javacheck/junit/Properties.java index 94032bc..a7bf19b 100644 --- a/src/main/java/au/id/zancanaro/javacheck/junit/Properties.java +++ b/src/main/java/au/id/zancanaro/javacheck/junit/Properties.java @@ -1,11 +1,11 @@ package au.id.zancanaro.javacheck.junit; -import au.id.zancanaro.javacheck.DataSourceHelper; import au.id.zancanaro.javacheck.Generator; import au.id.zancanaro.javacheck.ShrinkResult; import au.id.zancanaro.javacheck.ShrinkTree; import au.id.zancanaro.javacheck.annotations.Property; import au.id.zancanaro.javacheck.annotations.Seed; +import au.id.zancanaro.javacheck.object.GeneratorProvider; import org.junit.AssumptionViolatedException; import org.junit.runners.BlockJUnit4ClassRunner; import org.junit.runners.model.FrameworkMethod; @@ -13,38 +13,24 @@ import org.junit.runners.model.InitializationError; import org.junit.runners.model.Statement; import org.junit.runners.model.TestClass; +import java.lang.annotation.Annotation; import java.lang.reflect.Method; -import java.lang.reflect.Type; import java.util.*; @SuppressWarnings("WeakerAccess") public class Properties extends BlockJUnit4ClassRunner { - private final DataSourceHelper helper; + private final GeneratorProvider provider; public Properties(Class classObject) throws InitializationError { super(classObject); - helper = new DataSourceHelper(classObject); + provider = GeneratorProvider.DEFAULT_PROVIDER.withDataSources(classObject); } @Override protected void collectInitializationErrors(List errors) { super.collectInitializationErrors(errors); - Set generated = DataSourceHelper.validateGeneratorFields(getTestClass().getJavaClass(), errors); - validateTestMethodParameters(errors, generated); } - private void validateTestMethodParameters(List errors, Set generated) { - for (FrameworkMethod each : computeTestMethods()) { - for (Type type : each.getMethod().getGenericParameterTypes()) { - if (!generated.contains(type)) { - errors.add(new Error("No @DataSource for type: " + type)); - generated.add(type); // ignore future errors on this type - } - } - } - } - - @Override protected void validateConstructor(List errors) { validateOnlyOneConstructor(errors); @@ -73,18 +59,18 @@ public class Properties extends BlockJUnit4ClassRunner { @Override public Statement methodBlock(final FrameworkMethod method) { - return new GenerativeTester(method, getTestClass(), helper.computeGenerators()); + return new GenerativeTester(method, getTestClass(), provider); } public static class GenerativeTester extends Statement { private final FrameworkMethod testMethod; private final TestClass testClass; - private final Map> generators; + private final GeneratorProvider provider; - public GenerativeTester(FrameworkMethod testMethod, TestClass testClass, Map> generators) { + public GenerativeTester(FrameworkMethod testMethod, TestClass testClass, GeneratorProvider provider) { this.testMethod = testMethod; this.testClass = testClass; - this.generators = generators; + this.provider = provider; } private static long getSeed(Method method) { @@ -103,7 +89,7 @@ public class Properties extends BlockJUnit4ClassRunner { runTest(new Object[0]); } else { Generator[] generators = Arrays.stream(method.getGenericParameterTypes()) - .map(this.generators::get) + .map(param -> provider.getGenerator(param, new Annotation[0], provider)) .toArray(Generator[]::new); Generator> generator = Generator.tuple(generators); @@ -126,7 +112,6 @@ public class Properties extends BlockJUnit4ClassRunner { throw new Error("Violated 50 assumptions in a row: failing test"); } } catch (Throwable ex) { -// tree.print(new OutputStreamWriter(System.out), Arrays::toString); throw new PropertyError(method.getName(), seed, shrink(tree, ex)); } } @@ -178,8 +163,7 @@ public class Properties extends BlockJUnit4ClassRunner { public void runTest(final Object[] args) throws Throwable { new BlockJUnit4ClassRunner(testClass.getJavaClass()) { @Override - protected void collectInitializationErrors( - List errors) { + protected void collectInitializationErrors(List errors) { // do nothing } diff --git a/src/main/java/au/id/zancanaro/javacheck/junit/PropertyError.java b/src/main/java/au/id/zancanaro/javacheck/junit/PropertyError.java index d3747b9..5f89ca8 100644 --- a/src/main/java/au/id/zancanaro/javacheck/junit/PropertyError.java +++ b/src/main/java/au/id/zancanaro/javacheck/junit/PropertyError.java @@ -2,7 +2,6 @@ package au.id.zancanaro.javacheck.junit; import au.id.zancanaro.javacheck.ShrinkResult; -import java.util.Arrays; import java.util.Iterator; import java.util.List; diff --git a/src/main/java/au/id/zancanaro/javacheck/object/CharType.java b/src/main/java/au/id/zancanaro/javacheck/object/CharType.java index befbd04..aa3049e 100644 --- a/src/main/java/au/id/zancanaro/javacheck/object/CharType.java +++ b/src/main/java/au/id/zancanaro/javacheck/object/CharType.java @@ -5,7 +5,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target(ElementType.FIELD) +@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface CharType { public static enum TYPE { diff --git a/src/main/java/au/id/zancanaro/javacheck/object/DoubleRange.java b/src/main/java/au/id/zancanaro/javacheck/object/DoubleRange.java index ca7c9a9..08f8e3a 100644 --- a/src/main/java/au/id/zancanaro/javacheck/object/DoubleRange.java +++ b/src/main/java/au/id/zancanaro/javacheck/object/DoubleRange.java @@ -5,7 +5,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target(ElementType.FIELD) +@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface DoubleRange { double min(); diff --git a/src/main/java/au/id/zancanaro/javacheck/object/GeneratorProvider.java b/src/main/java/au/id/zancanaro/javacheck/object/GeneratorProvider.java index 7e5964f..67769b6 100644 --- a/src/main/java/au/id/zancanaro/javacheck/object/GeneratorProvider.java +++ b/src/main/java/au/id/zancanaro/javacheck/object/GeneratorProvider.java @@ -1,8 +1,10 @@ package au.id.zancanaro.javacheck.object; +import au.id.zancanaro.javacheck.DataSourceHelper; import au.id.zancanaro.javacheck.Generator; import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; @@ -28,7 +30,14 @@ public interface GeneratorProvider { @Override public Generator getGenerator(Type type, Annotation[] annotations, GeneratorProvider provider) { - if (type == Integer.TYPE || type == Integer.class) { + UseGenerator generator = getAnnotation(annotations, UseGenerator.class); + if (generator != null) { + try { + return generator.value().getConstructor().newInstance(); + } catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException ex) { + throw new ObjectGenerationException("Could not construct generator of type " + generator.value(), ex); + } + } else if (type == Integer.TYPE || type == Integer.class) { IntRange range = getAnnotation(annotations, IntRange.class); if (range == null) { return integer(); @@ -50,11 +59,11 @@ public interface GeneratorProvider { return doublePrecision(range.min(), range.max()); } } else if (type == String.class) { - CharType range = getAnnotation(annotations, CharType.class); - if (range == null) { + CharType charType = getAnnotation(annotations, CharType.class); + if (charType == null) { return string(); } else { - switch (range.value()) { + switch (charType.value()) { case ALPHA: return stringOf(alphaCharacter()); case ALPHA_NUMERIC: @@ -114,4 +123,16 @@ public interface GeneratorProvider { } }; } + + default GeneratorProvider withDataSources(Class wrappingType) { + DataSourceHelper helper = new DataSourceHelper(wrappingType); + Map> generators = helper.computeGenerators(); + return (type, annotations, provider) -> { + if (generators.containsKey(type)) { + return generators.get(type); + } else { + return this.getGenerator(type, annotations, provider); + } + }; + } } diff --git a/src/main/java/au/id/zancanaro/javacheck/object/IntRange.java b/src/main/java/au/id/zancanaro/javacheck/object/IntRange.java index 8dc3f28..5d2aa30 100644 --- a/src/main/java/au/id/zancanaro/javacheck/object/IntRange.java +++ b/src/main/java/au/id/zancanaro/javacheck/object/IntRange.java @@ -5,7 +5,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target(ElementType.FIELD) +@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface IntRange { int max(); diff --git a/src/main/java/au/id/zancanaro/javacheck/object/LongRange.java b/src/main/java/au/id/zancanaro/javacheck/object/LongRange.java index dd5a61f..dd5b8cf 100644 --- a/src/main/java/au/id/zancanaro/javacheck/object/LongRange.java +++ b/src/main/java/au/id/zancanaro/javacheck/object/LongRange.java @@ -5,7 +5,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target(ElementType.FIELD) +@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface LongRange { long max(); diff --git a/src/main/java/au/id/zancanaro/javacheck/object/ObjectGenerationException.java b/src/main/java/au/id/zancanaro/javacheck/object/ObjectGenerationException.java index d2147eb..444de32 100644 --- a/src/main/java/au/id/zancanaro/javacheck/object/ObjectGenerationException.java +++ b/src/main/java/au/id/zancanaro/javacheck/object/ObjectGenerationException.java @@ -8,4 +8,8 @@ public class ObjectGenerationException extends RuntimeException { public ObjectGenerationException(Throwable cause) { super(cause); } + + public ObjectGenerationException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/src/main/java/au/id/zancanaro/javacheck/object/UseGenerator.java b/src/main/java/au/id/zancanaro/javacheck/object/UseGenerator.java new file mode 100644 index 0000000..f62d978 --- /dev/null +++ b/src/main/java/au/id/zancanaro/javacheck/object/UseGenerator.java @@ -0,0 +1,14 @@ +package au.id.zancanaro.javacheck.object; + +import au.id.zancanaro.javacheck.Generator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.FIELD, ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +public @interface UseGenerator { + Class value(); +} diff --git a/src/test/java/au/id/zancanaro/javacheck/ListFunctorRulesTest.java b/src/test/java/au/id/zancanaro/javacheck/ListFunctorRulesTest.java index b757bcd..4100378 100644 --- a/src/test/java/au/id/zancanaro/javacheck/ListFunctorRulesTest.java +++ b/src/test/java/au/id/zancanaro/javacheck/ListFunctorRulesTest.java @@ -9,7 +9,8 @@ import java.util.List; import java.util.function.Function; import java.util.stream.Collectors; -import static au.id.zancanaro.javacheck.Generators.*; +import static au.id.zancanaro.javacheck.Generators.longInteger; +import static au.id.zancanaro.javacheck.Generators.oneOf; import static org.junit.Assert.assertEquals; @RunWith(Properties.class) @@ -18,9 +19,6 @@ public class ListFunctorRulesTest { private final static int runs = 1000; private final static int maxSize = 1000; - @DataSource - public static Generator> listOfIntegers = listOf(longInteger()); - @DataSource public static Generator> integerFunction = oneOf( diff --git a/src/test/java/au/id/zancanaro/javacheck/SimpleListOperationsTest.java b/src/test/java/au/id/zancanaro/javacheck/SimpleListOperationsTest.java index fcc8baf..3f4d58b 100644 --- a/src/test/java/au/id/zancanaro/javacheck/SimpleListOperationsTest.java +++ b/src/test/java/au/id/zancanaro/javacheck/SimpleListOperationsTest.java @@ -1,6 +1,5 @@ package au.id.zancanaro.javacheck; -import au.id.zancanaro.javacheck.annotations.DataSource; import au.id.zancanaro.javacheck.annotations.Property; import au.id.zancanaro.javacheck.junit.Properties; import org.junit.runner.RunWith; @@ -8,7 +7,6 @@ import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; -import static au.id.zancanaro.javacheck.Generators.*; import static java.util.Collections.reverse; import static java.util.Collections.sort; import static org.junit.Assert.assertEquals; @@ -19,12 +17,6 @@ public class SimpleListOperationsTest { private final static int runs = 1000; private final static int maxSize = 1000; - @DataSource - public static Generator> listOfIntegers = listOf(integer()); - - @DataSource - public static Generator> listOfStrings = listOf(stringOf(alphaNumericCharacter())); - @Property(maxSize = maxSize, runs = runs) public void sortingIsIdempotent(List list) { List left = new ArrayList<>(list); diff --git a/src/test/java/au/id/zancanaro/javacheck/object/MyObject.java b/src/test/java/au/id/zancanaro/javacheck/object/MyObject.java index ac0c370..824fd8d 100644 --- a/src/test/java/au/id/zancanaro/javacheck/object/MyObject.java +++ b/src/test/java/au/id/zancanaro/javacheck/object/MyObject.java @@ -1,5 +1,13 @@ package au.id.zancanaro.javacheck.object; +import au.id.zancanaro.javacheck.Generator; +import au.id.zancanaro.javacheck.ShrinkTree; + +import java.util.Random; + +import static au.id.zancanaro.javacheck.Generators.asciiCharacter; +import static au.id.zancanaro.javacheck.Generators.stringOf; + public class MyObject { public final String string; public final int value; @@ -12,10 +20,21 @@ public class MyObject { } @UseForGeneration - public MyObject(String string, SubObject subObject) { + public MyObject( + @UseGenerator(StringGenerator.class) String string, + SubObject subObject) { this(string, string.length(), subObject); } + public static class StringGenerator implements Generator { + public final Generator gen = stringOf(asciiCharacter()); + + @Override + public ShrinkTree generate(Random random, int size) { + return gen.generate(random, size); + } + } + public MyObject add(MyObject other) { return new MyObject( this.string + other.string, diff --git a/src/test/java/au/id/zancanaro/javacheck/object/MyObjectAddTest.java b/src/test/java/au/id/zancanaro/javacheck/object/MyObjectAddTest.java index 7965449..9321acb 100644 --- a/src/test/java/au/id/zancanaro/javacheck/object/MyObjectAddTest.java +++ b/src/test/java/au/id/zancanaro/javacheck/object/MyObjectAddTest.java @@ -1,23 +1,15 @@ package au.id.zancanaro.javacheck.object; -import au.id.zancanaro.javacheck.Generator; -import au.id.zancanaro.javacheck.annotations.DataSource; import au.id.zancanaro.javacheck.annotations.Property; import au.id.zancanaro.javacheck.junit.Properties; import org.junit.runner.RunWith; -import java.util.Collections; - -import static au.id.zancanaro.javacheck.Generators.ofType; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @RunWith(Properties.class) public class MyObjectAddTest { - @DataSource - public static Generator source = ofType(MyObject.class); - @Property public void testAdd(MyObject a, MyObject b) { MyObject added = a.add(b); diff --git a/src/test/java/au/id/zancanaro/javacheck/state/queue/commands/Offer.java b/src/test/java/au/id/zancanaro/javacheck/state/queue/commands/Offer.java index 301efeb..366e974 100644 --- a/src/test/java/au/id/zancanaro/javacheck/state/queue/commands/Offer.java +++ b/src/test/java/au/id/zancanaro/javacheck/state/queue/commands/Offer.java @@ -7,7 +7,6 @@ import au.id.zancanaro.javacheck.state.queue.QueueState; import java.util.ArrayList; import java.util.List; -import java.util.Queue; public class Offer extends Command, T, Void> { public final Generator generator; -- cgit v1.2.3