diff options
author | Carlo Zancanaro <carlo@zancanaro.id.au> | 2015-05-30 02:00:43 +1000 |
---|---|---|
committer | Carlo Zancanaro <carlo@zancanaro.id.au> | 2015-05-30 02:00:43 +1000 |
commit | d29e1d49116c66adab72b1c1bb49c1fa3d4f8140 (patch) | |
tree | 17363b0d5b939c217cc95e4c57326e01c45d121e /src/main/java/au/id/zancanaro/PropertyTestRunner.java |
Initial commit: only works for plain int typed arguments
Diffstat (limited to 'src/main/java/au/id/zancanaro/PropertyTestRunner.java')
-rw-r--r-- | src/main/java/au/id/zancanaro/PropertyTestRunner.java | 153 |
1 files changed, 153 insertions, 0 deletions
diff --git a/src/main/java/au/id/zancanaro/PropertyTestRunner.java b/src/main/java/au/id/zancanaro/PropertyTestRunner.java new file mode 100644 index 0000000..fc90b8e --- /dev/null +++ b/src/main/java/au/id/zancanaro/PropertyTestRunner.java @@ -0,0 +1,153 @@ +package au.id.zancanaro; + +import au.id.zancanaro.annotations.Property; +import au.id.zancanaro.annotations.Seed; +import junit.framework.AssertionFailedError; +import org.junit.AssumptionViolatedException; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import java.util.Random; + +public class PropertyTestRunner extends Runner { + private final Class<?> classUnderTest; + + public PropertyTestRunner(Class<?> classUnderTest) { + this.classUnderTest = classUnderTest; + } + + @Override + public Description getDescription() { + return Description.createSuiteDescription(classUnderTest); + } + + private long getSeed(Method method) { + Seed seed = method.getAnnotation(Seed.class); + if (seed == null) { + return System.currentTimeMillis(); + } else { + return seed.value(); + } + } + + @Override + public void run(RunNotifier notifier) { + Method[] methods = classUnderTest.getMethods(); + for (Method method : methods) { + Property details = method.getAnnotation(Property.class); + if (details != null) { + Description description = Description.createTestDescription(classUnderTest, method.getName()); + boolean failed = false; + int assumptionsFailed = 0; + + long seed = getSeed(method); + Random random = new Random(seed); + int numRuns = details.runs(); + for (int i = 0; i < numRuns && !failed; ++i) { + int size = details.size(); + notifier.fireTestStarted(description); + Object obj; + try { + obj = classUnderTest.getConstructor().newInstance(); + } catch (Throwable ex) { // HACKY + System.out.println(ex); + return; + } + RoseTree<Object[]> generated = generateArgs(random, size, + method.getGenericParameterTypes(), + method.getParameterAnnotations()); + try { + method.invoke(obj, generated.getValue()); + } catch (InvocationTargetException ex) { + if (ex.getTargetException() instanceof AssumptionViolatedException) { + assumptionsFailed++; + } else { + notifier.fireTestFailure(new Failure(description, ex.getTargetException())); + System.out.println("Test failed with seed: " + seed); + System.out.println("Failing arguments: " + Arrays.asList(generated.getValue())); + Object[] shrinkResult = shrink(method, obj, generated); + if (shrinkResult == null) { + System.out.println("Arguments could not be shrunk any further"); + } else { + System.out.println("Arguments shrunk to: " + Arrays.asList(shrinkResult)); + } + failed = true; + } + } catch (IllegalAccessException ex) { + notifier.fireTestFailure(new Failure(description, ex)); + failed = true; + } + } + + if (assumptionsFailed > 0) { + System.out.println("Failed " + assumptionsFailed + " assumptions"); + } + notifier.fireTestFinished(description); + } + } + } + + private Object[] shrink(Method method, Object obj, RoseTree<Object[]> failed) { + Object[] smallest = failed.getValue(); + Iterator<RoseTree<Object[]>> trees = failed.getChildren(); + while (trees.hasNext()) { + RoseTree<Object[]> tree = trees.next(); + try { + method.invoke(obj, tree.getValue()); + } catch (Throwable ex) { + Iterator<RoseTree<Object[]>> children = tree.getChildren(); + if (children.hasNext()) { + trees = children; + } + smallest = tree.getValue(); + } + } + return smallest; + } + + private <T> String printShrinkTree(RoseTree<T[]> generated) { + StringBuilder builder = new StringBuilder(); + builder.append('('); + builder.append(Arrays.toString(generated.getValue())); + generated.getChildren().forEachRemaining((child) -> { + builder.append(' '); + builder.append(printShrinkTree(child)); + }); + builder.append(')'); + return builder.toString(); + } + + + private RoseTree<Object[]> generateArgs(Random random, int size, Type[] types, Annotation[][] annotations) { + Generator<?>[] generators = new Generator[types.length]; + for (int i = 0; i < types.length; ++i) { +// generators[i] = getGeneratorFromAnnotations(annotations[i]); +// if (generators[i] == null) { + generators[i] = getGeneratorFromType(types[i]); +// } + } + return Generators.arrayGenerator(generators).generate(random, size); + } + + private Generator<?> getGeneratorFromType(Type type) { + if (type instanceof Class) { + Class<?> clazz = (Class<?>) type; + if (clazz.isPrimitive() && clazz == Integer.TYPE) { + return Generators.integerGenerator(); + } else { + throw new RuntimeException("Unknown type for generator (atm only int is supported)"); + } + } else { + throw new RuntimeException("Unknown type for generator (atm only int is supported)"); + } + } +} |