From 7e1182355d54f0dc8461ce7df7c4aca8c40d2a92 Mon Sep 17 00:00:00 2001 From: Carlo Zancanaro Date: Mon, 1 Jun 2015 00:25:19 +1000 Subject: Just some rearranging, renaming and clean-up --- src/main/java/au/id/zancanaro/Generators.java | 16 +- src/main/java/au/id/zancanaro/Properties.java | 249 +++++++++++++++++++++ src/main/java/au/id/zancanaro/PropertyError.java | 45 ++++ .../java/au/id/zancanaro/PropertyTestError.java | 45 ---- .../java/au/id/zancanaro/PropertyTestRunner.java | 249 --------------------- .../au/id/zancanaro/annotations/Generator.java | 8 - 6 files changed, 302 insertions(+), 310 deletions(-) create mode 100644 src/main/java/au/id/zancanaro/Properties.java create mode 100644 src/main/java/au/id/zancanaro/PropertyError.java delete mode 100644 src/main/java/au/id/zancanaro/PropertyTestError.java delete mode 100644 src/main/java/au/id/zancanaro/PropertyTestRunner.java delete mode 100644 src/main/java/au/id/zancanaro/annotations/Generator.java (limited to 'src/main/java/au/id/zancanaro') diff --git a/src/main/java/au/id/zancanaro/Generators.java b/src/main/java/au/id/zancanaro/Generators.java index 852a290..e0927ff 100644 --- a/src/main/java/au/id/zancanaro/Generators.java +++ b/src/main/java/au/id/zancanaro/Generators.java @@ -22,14 +22,6 @@ public class Generators { return integer(0, gens.length).flatMap(index -> gens[index]); } - public static Generator integer() { - return (random, size) -> integer(-size, size).generate(random, size); - } - - public static Generator natural() { - return (random, size) -> integer(0, size).generate(random, size); - } - public static Generator integer(int lower, int upper) { return (random, size) -> { int value = lower + random.nextInt(upper - lower); @@ -38,6 +30,14 @@ public class Generators { }; } + public static Generator integer() { + return (random, size) -> integer(-size, size).generate(random, size); + } + + public static Generator natural() { + return (random, size) -> integer(0, size).generate(random, size); + } + private static Iterable> intShrinkingIterable(final int value, final int bound) { return () -> new Iterator>() { int curr = value - bound; diff --git a/src/main/java/au/id/zancanaro/Properties.java b/src/main/java/au/id/zancanaro/Properties.java new file mode 100644 index 0000000..fab48f4 --- /dev/null +++ b/src/main/java/au/id/zancanaro/Properties.java @@ -0,0 +1,249 @@ +package au.id.zancanaro; + +import au.id.zancanaro.annotations.Property; +import au.id.zancanaro.annotations.Seed; +import org.junit.AssumptionViolatedException; +import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.Statement; +import org.junit.runners.model.TestClass; + +import java.lang.reflect.*; +import java.util.*; + + +public class Properties extends BlockJUnit4ClassRunner { + private final Map> generators = new HashMap<>(); + + public Properties(Class klass) throws InitializationError { + super(klass); + } + + @Override + protected void collectInitializationErrors(List errors) { + super.collectInitializationErrors(errors); + validateGeneratorFields(errors); + } + + private void validateGeneratorFields(List errors) { + Field[] fields = getTestClass().getJavaClass().getDeclaredFields(); + + for (Field field : fields) { + Type type = field.getGenericType(); + if (!(type instanceof ParameterizedType)) { + continue; + } + ParameterizedType ptype = (ParameterizedType) type; + if (!(ptype.getRawType() instanceof Class)) { + continue; + } + Class c = (Class) ptype.getRawType(); + if (c != Generator.class) { + continue; + } + if (!Modifier.isStatic(field.getModifiers())) { + errors.add(new Error("Generator field " + field.getName() + " must be static")); + } + if (!Modifier.isPublic(field.getModifiers())) { + errors.add(new Error("Generator field " + field.getName() + " must be public")); + } + } + } + + private static final Map rawTypes; + + static { + Map types = new HashMap<>(); + types.put(Double.class, Double.TYPE); + types.put(Float.class, Float.TYPE); + types.put(Long.class, Long.TYPE); + types.put(Integer.class, Integer.TYPE); + types.put(Short.class, Short.TYPE); + types.put(Byte.class, Byte.TYPE); + types.put(Character.class, Character.TYPE); + types.put(Boolean.class, Boolean.TYPE); + rawTypes = Collections.unmodifiableMap(types); + } + + private Map> computeGenerators() { + if (generators.isEmpty()) { + Field[] fields = getTestClass().getJavaClass().getDeclaredFields(); + + for (Field field : fields) { + Type type = field.getGenericType(); + if (!(type instanceof ParameterizedType)) { + continue; + } + ParameterizedType ptype = (ParameterizedType) type; + if (!(ptype.getRawType() instanceof Class)) { + continue; + } + Class c = (Class) ptype.getRawType(); + if (c != Generator.class) { + continue; + } + try { + Type target = ptype.getActualTypeArguments()[0]; + @SuppressWarnings("unchecked") + Generator generator = (Generator) field.get(null); + generators.put(target, generator); + if (rawTypes.containsKey(target)) { + generators.put(rawTypes.get(target), generator); + } + } catch (IllegalAccessException ex) { + + } + } + } + return generators; + } + + @Override + protected void validateConstructor(List errors) { + validateOnlyOneConstructor(errors); + } + + @Override + protected void validateTestMethods(List errors) { + for (FrameworkMethod each : computeTestMethods()) { + if (each.getAnnotation(Property.class) != null) { + each.validatePublicVoid(false, errors); + each.validateNoTypeParametersOnArgs(errors); + } else { + each.validatePublicVoidNoArg(false, errors); + } + } + } + + @Override + protected List computeTestMethods() { + List testMethods = new ArrayList<>(super.computeTestMethods()); + List theoryMethods = getTestClass().getAnnotatedMethods(Property.class); + testMethods.removeAll(theoryMethods); + testMethods.addAll(theoryMethods); + return testMethods; + } + + @Override + public Statement methodBlock(final FrameworkMethod method) { + return new GenerativeTester(method, getTestClass(), computeGenerators()); + } + + public static class GenerativeTester extends Statement { + private final FrameworkMethod testMethod; + private final TestClass testClass; + private final Map> generators; + + public GenerativeTester(FrameworkMethod testMethod, TestClass testClass, Map> generators) { + this.testMethod = testMethod; + this.testClass = testClass; + this.generators = generators; + } + + private long getSeed(Method method) { + Seed seed = method.getAnnotation(Seed.class); + if (seed == null) { + return System.currentTimeMillis(); + } else { + return seed.value(); + } + } + + @Override + public void evaluate() throws Throwable { + Method method = testMethod.getMethod(); + if (method.getParameterCount() == 0) { + runTest(new Object[0]); + } else { + @SuppressWarnings("unchecked") + Generator[] generators = (Generator[]) new Generator[method.getParameterCount()]; + int index = 0; + for (Type type : method.getGenericParameterTypes()) { + generators[index++] = this.generators.get(type); + } + Generator generator = Generator.tuple(generators); + + long seed = getSeed(method); + Random random = new Random(seed); + + Property property = testMethod.getAnnotation(Property.class); + int assumptionsViolated = 0; + int maxSize = property.maxSize(); + int numTests = property.runs(); + for (int i = 0; i < numTests; ++i) { + int size = Math.min(i + 1, maxSize); + RoseTree tree = generator.generate(random, size); + try { + runTest(tree.getValue()); + assumptionsViolated = 0; + } catch (AssumptionViolatedException ex) { + numTests++; + if (assumptionsViolated++ == 50) { + throw new Error("Violated 50 assumptions in a row: failing test"); + } + ; + } catch (Throwable ex) { + throw new PropertyError(method.getName(), seed, shrink(tree, ex)); + } + } + } + } + + private ShrinkResult shrink(RoseTree failed, Throwable originalEx) { + ShrinkResult smallest = new ShrinkResult(failed.getValue(), originalEx); + Iterator> trees = failed.getChildren(); + Set> seenArgs = new HashSet<>(); + while (trees.hasNext()) { + RoseTree tree = trees.next(); + if (seenArgs.add(Arrays.asList(tree.getValue()))) { + try { + runTest(tree.getValue()); + } catch (AssumptionViolatedException ex) { + // ignore, because it's not useful + } catch (Throwable ex) { + smallest = new ShrinkResult(tree.getValue(), ex); + Iterator> children = tree.getChildren(); + if (children.hasNext()) { + trees = children; + } else { + break; + } + } + } + } + return smallest; + } + + public void runTest(final Object[] args) throws Throwable { + new BlockJUnit4ClassRunner(testClass.getJavaClass()) { + @Override + protected void collectInitializationErrors( + List errors) { + // do nothing + } + + @Override + public Statement methodBlock(FrameworkMethod method) { + return super.methodBlock(method); + } + + @Override + protected Statement methodInvoker(FrameworkMethod method, Object test) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + method.invokeExplosively(test, args); + } + }; + } + + @Override + public Object createTest() throws Exception { + return getTestClass().getOnlyConstructor().newInstance(); + } + }.methodBlock(testMethod).evaluate(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/au/id/zancanaro/PropertyError.java b/src/main/java/au/id/zancanaro/PropertyError.java new file mode 100644 index 0000000..b2fb589 --- /dev/null +++ b/src/main/java/au/id/zancanaro/PropertyError.java @@ -0,0 +1,45 @@ +package au.id.zancanaro; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; + +public class PropertyError extends AssertionError { + public PropertyError(String methodName, long seed, ShrinkResult shrunk) { + super(String.format("%s(%s)\n\tSeed: %s\n%s", + methodName, join(", ", shrunk.args), + seed, + shrunk.thrown.getMessage())); + initCause(shrunk.thrown); + } + + + public static String join(String delimiter, Object... params) { + return join(delimiter, Arrays.asList(params)); + } + + public static String join(String delimiter, Collection values) { + StringBuilder sb = new StringBuilder(); + Iterator iter = values.iterator(); + while (iter.hasNext()) { + Object next = iter.next(); + sb.append(stringValueOf(next)); + if (iter.hasNext()) { + sb.append(delimiter); + } + } + return sb.toString(); + } + + private static String stringValueOf(Object next) { + if (next instanceof String) { + return '"' + ((String) next).replace("\"", "\\\"") + '"'; + } else { + try { + return String.valueOf(next); + } catch (Throwable e) { + return "[toString failed]"; + } + } + } +} diff --git a/src/main/java/au/id/zancanaro/PropertyTestError.java b/src/main/java/au/id/zancanaro/PropertyTestError.java deleted file mode 100644 index afd614b..0000000 --- a/src/main/java/au/id/zancanaro/PropertyTestError.java +++ /dev/null @@ -1,45 +0,0 @@ -package au.id.zancanaro; - -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; - -public class PropertyTestError extends AssertionError { - public PropertyTestError(String methodName, long seed, ShrinkResult shrunk) { - super(String.format("%s(%s)\n\tSeed: %s\n%s", - methodName, join(", ", shrunk.args), - seed, - shrunk.thrown.getMessage())); - initCause(shrunk.thrown); - } - - - public static String join(String delimiter, Object... params) { - return join(delimiter, Arrays.asList(params)); - } - - public static String join(String delimiter, Collection values) { - StringBuilder sb = new StringBuilder(); - Iterator iter = values.iterator(); - while (iter.hasNext()) { - Object next = iter.next(); - sb.append(stringValueOf(next)); - if (iter.hasNext()) { - sb.append(delimiter); - } - } - return sb.toString(); - } - - private static String stringValueOf(Object next) { - if (next instanceof String) { - return '"' + ((String) next).replace("\"", "\\\"") + '"'; - } else { - try { - return String.valueOf(next); - } catch (Throwable e) { - return "[toString failed]"; - } - } - } -} diff --git a/src/main/java/au/id/zancanaro/PropertyTestRunner.java b/src/main/java/au/id/zancanaro/PropertyTestRunner.java deleted file mode 100644 index ea36341..0000000 --- a/src/main/java/au/id/zancanaro/PropertyTestRunner.java +++ /dev/null @@ -1,249 +0,0 @@ -package au.id.zancanaro; - -import au.id.zancanaro.annotations.Property; -import au.id.zancanaro.annotations.Seed; -import org.junit.AssumptionViolatedException; -import org.junit.runners.BlockJUnit4ClassRunner; -import org.junit.runners.model.FrameworkMethod; -import org.junit.runners.model.InitializationError; -import org.junit.runners.model.Statement; -import org.junit.runners.model.TestClass; - -import java.lang.reflect.*; -import java.util.*; - - -public class PropertyTestRunner extends BlockJUnit4ClassRunner { - private final Map> generators = new HashMap<>(); - - public PropertyTestRunner(Class klass) throws InitializationError { - super(klass); - } - - @Override - protected void collectInitializationErrors(List errors) { - super.collectInitializationErrors(errors); - validateGeneratorFields(errors); - } - - private void validateGeneratorFields(List errors) { - Field[] fields = getTestClass().getJavaClass().getDeclaredFields(); - - for (Field field : fields) { - Type type = field.getGenericType(); - if (!(type instanceof ParameterizedType)) { - continue; - } - ParameterizedType ptype = (ParameterizedType) type; - if (!(ptype.getRawType() instanceof Class)) { - continue; - } - Class c = (Class) ptype.getRawType(); - if (c != Generator.class) { - continue; - } - if (!Modifier.isStatic(field.getModifiers())) { - errors.add(new Error("Generator field " + field.getName() + " must be static")); - } - if (!Modifier.isPublic(field.getModifiers())) { - errors.add(new Error("Generator field " + field.getName() + " must be public")); - } - } - } - - private static final Map rawTypes; - - static { - Map types = new HashMap<>(); - types.put(Double.class, Double.TYPE); - types.put(Float.class, Float.TYPE); - types.put(Long.class, Long.TYPE); - types.put(Integer.class, Integer.TYPE); - types.put(Short.class, Short.TYPE); - types.put(Byte.class, Byte.TYPE); - types.put(Character.class, Character.TYPE); - types.put(Boolean.class, Boolean.TYPE); - rawTypes = Collections.unmodifiableMap(types); - } - - private Map> computeGenerators() { - if (generators.isEmpty()) { - Field[] fields = getTestClass().getJavaClass().getDeclaredFields(); - - for (Field field : fields) { - Type type = field.getGenericType(); - if (!(type instanceof ParameterizedType)) { - continue; - } - ParameterizedType ptype = (ParameterizedType) type; - if (!(ptype.getRawType() instanceof Class)) { - continue; - } - Class c = (Class) ptype.getRawType(); - if (c != Generator.class) { - continue; - } - try { - Type target = ptype.getActualTypeArguments()[0]; - @SuppressWarnings("unchecked") - Generator generator = (Generator) field.get(null); - generators.put(target, generator); - if (rawTypes.containsKey(target)) { - generators.put(rawTypes.get(target), generator); - } - } catch (IllegalAccessException ex) { - - } - } - } - return generators; - } - - @Override - protected void validateConstructor(List errors) { - validateOnlyOneConstructor(errors); - } - - @Override - protected void validateTestMethods(List errors) { - for (FrameworkMethod each : computeTestMethods()) { - if (each.getAnnotation(Property.class) != null) { - each.validatePublicVoid(false, errors); - each.validateNoTypeParametersOnArgs(errors); - } else { - each.validatePublicVoidNoArg(false, errors); - } - } - } - - @Override - protected List computeTestMethods() { - List testMethods = new ArrayList<>(super.computeTestMethods()); - List theoryMethods = getTestClass().getAnnotatedMethods(Property.class); - testMethods.removeAll(theoryMethods); - testMethods.addAll(theoryMethods); - return testMethods; - } - - @Override - public Statement methodBlock(final FrameworkMethod method) { - return new GenerativeTester(method, getTestClass(), computeGenerators()); - } - - public static class GenerativeTester extends Statement { - private final FrameworkMethod testMethod; - private final TestClass testClass; - private final Map> generators; - - public GenerativeTester(FrameworkMethod testMethod, TestClass testClass, Map> generators) { - this.testMethod = testMethod; - this.testClass = testClass; - this.generators = generators; - } - - private long getSeed(Method method) { - Seed seed = method.getAnnotation(Seed.class); - if (seed == null) { - return System.currentTimeMillis(); - } else { - return seed.value(); - } - } - - @Override - public void evaluate() throws Throwable { - Method method = testMethod.getMethod(); - if (method.getParameterCount() == 0) { - runTest(new Object[0]); - } else { - @SuppressWarnings("unchecked") - Generator[] generators = (Generator[]) new Generator[method.getParameterCount()]; - int index = 0; - for (Type type : method.getGenericParameterTypes()) { - generators[index++] = this.generators.get(type); - } - Generator generator = Generator.tuple(generators); - - long seed = getSeed(method); - Random random = new Random(seed); - - Property property = testMethod.getAnnotation(Property.class); - int assumptionsViolated = 0; - int maxSize = property.maxSize(); - int numTests = property.runs(); - for (int i = 0; i < numTests; ++i) { - int size = Math.min(i + 1, maxSize); - RoseTree tree = generator.generate(random, size); - try { - runTest(tree.getValue()); - assumptionsViolated = 0; - } catch (AssumptionViolatedException ex) { - numTests++; - if (assumptionsViolated++ == 50) { - throw new Error("Violated 50 assumptions in a row: failing test"); - } - ; - } catch (Throwable ex) { - throw new PropertyTestError(method.getName(), seed, shrink(tree, ex)); - } - } - } - } - - private ShrinkResult shrink(RoseTree failed, Throwable originalEx) { - ShrinkResult smallest = new ShrinkResult(failed.getValue(), originalEx); - Iterator> trees = failed.getChildren(); - Set> seenArgs = new HashSet<>(); - while (trees.hasNext()) { - RoseTree tree = trees.next(); - if (seenArgs.add(Arrays.asList(tree.getValue()))) { - try { - runTest(tree.getValue()); - } catch (AssumptionViolatedException ex) { - // ignore, because it's not useful - } catch (Throwable ex) { - smallest = new ShrinkResult(tree.getValue(), ex); - Iterator> children = tree.getChildren(); - if (children.hasNext()) { - trees = children; - } else { - break; - } - } - } - } - return smallest; - } - - public void runTest(final Object[] args) throws Throwable { - new BlockJUnit4ClassRunner(testClass.getJavaClass()) { - @Override - protected void collectInitializationErrors( - List errors) { - // do nothing - } - - @Override - public Statement methodBlock(FrameworkMethod method) { - return super.methodBlock(method); - } - - @Override - protected Statement methodInvoker(FrameworkMethod method, Object test) { - return new Statement() { - @Override - public void evaluate() throws Throwable { - method.invokeExplosively(test, args); - } - }; - } - - @Override - public Object createTest() throws Exception { - return getTestClass().getOnlyConstructor().newInstance(); - } - }.methodBlock(testMethod).evaluate(); - } - } - -} \ No newline at end of file diff --git a/src/main/java/au/id/zancanaro/annotations/Generator.java b/src/main/java/au/id/zancanaro/annotations/Generator.java deleted file mode 100644 index 98e9446..0000000 --- a/src/main/java/au/id/zancanaro/annotations/Generator.java +++ /dev/null @@ -1,8 +0,0 @@ -package au.id.zancanaro.annotations; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Target; - -@Target(ElementType.PARAMETER) -public @interface Generator { -} -- cgit v1.2.3