From 03b912cb7460724f95002957e39ef97c43d0e2e0 Mon Sep 17 00:00:00 2001 From: Paul Holser Date: Mon, 18 Oct 2010 17:36:14 -0500 Subject: [PATCH] initial classes --- .../AbstractJDKSourceOfRandomness.java | 63 +++++++++ .../pholser/junit/parameters/ExtractedBy.java | 13 ++ .../com/pholser/junit/parameters/ForAll.java | 16 +++ .../GeneratingParameterSupplier.java | 84 ++++++++++++ .../parameters/JDKSourceOfRandomness.java | 9 ++ .../parameters/RandomValueExtractor.java | 5 + .../SecureJDKSourceOfRandomness.java | 13 ++ .../junit/parameters/SourceOfRandomness.java | 23 ++++ .../WhenMarkingTheoryParametersAsForAll.java | 123 ++++++++++++++++++ 9 files changed, 349 insertions(+) create mode 100644 src/main/java/com/pholser/junit/parameters/AbstractJDKSourceOfRandomness.java create mode 100644 src/main/java/com/pholser/junit/parameters/ExtractedBy.java create mode 100644 src/main/java/com/pholser/junit/parameters/ForAll.java create mode 100644 src/main/java/com/pholser/junit/parameters/GeneratingParameterSupplier.java create mode 100644 src/main/java/com/pholser/junit/parameters/JDKSourceOfRandomness.java create mode 100644 src/main/java/com/pholser/junit/parameters/RandomValueExtractor.java create mode 100644 src/main/java/com/pholser/junit/parameters/SecureJDKSourceOfRandomness.java create mode 100644 src/main/java/com/pholser/junit/parameters/SourceOfRandomness.java create mode 100644 src/test/java/com/pholser/junit/parameters/WhenMarkingTheoryParametersAsForAll.java diff --git a/src/main/java/com/pholser/junit/parameters/AbstractJDKSourceOfRandomness.java b/src/main/java/com/pholser/junit/parameters/AbstractJDKSourceOfRandomness.java new file mode 100644 index 000000000..12228884c --- /dev/null +++ b/src/main/java/com/pholser/junit/parameters/AbstractJDKSourceOfRandomness.java @@ -0,0 +1,63 @@ +package com.pholser.junit.parameters; + +import java.util.Random; + +public abstract class AbstractJDKSourceOfRandomness implements SourceOfRandomness { + private final Random random; + + protected AbstractJDKSourceOfRandomness(Random random) { + this.random = random; + } + + @Override + public boolean nextBoolean() { + return random.nextBoolean(); + } + + @Override + public void nextBytes(byte[] bytes) { + random.nextBytes(bytes); + } + + @Override + public byte[] nextBytes(int length) { + byte[] bytes = new byte[length]; + nextBytes(bytes); + return bytes; + } + + @Override + public double nextDouble() { + return random.nextDouble(); + } + + @Override + public float nextFloat() { + return random.nextFloat(); + } + + @Override + public double nextGaussian() { + return random.nextGaussian(); + } + + @Override + public int nextInt() { + return random.nextInt(); + } + + @Override + public int nextInt(int n) { + return random.nextInt(n); + } + + @Override + public long nextLong() { + return random.nextLong(); + } + + @Override + public void setSeed(long seed) { + random.setSeed(seed); + } +} diff --git a/src/main/java/com/pholser/junit/parameters/ExtractedBy.java b/src/main/java/com/pholser/junit/parameters/ExtractedBy.java new file mode 100644 index 000000000..884fd3ebb --- /dev/null +++ b/src/main/java/com/pholser/junit/parameters/ExtractedBy.java @@ -0,0 +1,13 @@ +package com.pholser.junit.parameters; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Target(PARAMETER) +@Retention(RUNTIME) +public @interface ExtractedBy { + Class value(); +} diff --git a/src/main/java/com/pholser/junit/parameters/ForAll.java b/src/main/java/com/pholser/junit/parameters/ForAll.java new file mode 100644 index 000000000..6afcce0fd --- /dev/null +++ b/src/main/java/com/pholser/junit/parameters/ForAll.java @@ -0,0 +1,16 @@ +package com.pholser.junit.parameters; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import org.junit.experimental.theories.ParametersSuppliedBy; + +@Target(PARAMETER) +@Retention(RUNTIME) +@ParametersSuppliedBy(GeneratingParameterSupplier.class) +public @interface ForAll { + int sampleSize() default 100; +} diff --git a/src/main/java/com/pholser/junit/parameters/GeneratingParameterSupplier.java b/src/main/java/com/pholser/junit/parameters/GeneratingParameterSupplier.java new file mode 100644 index 000000000..56fa2e11d --- /dev/null +++ b/src/main/java/com/pholser/junit/parameters/GeneratingParameterSupplier.java @@ -0,0 +1,84 @@ +package com.pholser.junit.parameters; + +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.experimental.theories.ParameterSignature; +import org.junit.experimental.theories.ParameterSupplier; +import org.junit.experimental.theories.PotentialAssignment; + +public class GeneratingParameterSupplier extends ParameterSupplier { + private static final Map, RandomValueExtractor> extractors = + new HashMap, RandomValueExtractor>(); + + static { + extractors.put(int.class, new RandomValueExtractor() { + @Override + public Object randomValue(SourceOfRandomness random) { + return random.nextInt(); + } + }); + extractors.put(Integer.class, extractors.get(int.class)); + extractors.put(double.class, new RandomValueExtractor() { + @Override + public Object randomValue(SourceOfRandomness random) { + return random.nextDouble(); + } + }); + extractors.put(Double.class, extractors.get(double.class)); + extractors.put(String.class, new RandomValueExtractor() { + @Override + public Object randomValue(SourceOfRandomness random) { + try { + return new String(random.nextBytes(16), "US-ASCII"); + } catch (UnsupportedEncodingException ex) { + throw new AssertionError(ex); + } + } + }); + } + + private final SourceOfRandomness random; + + public GeneratingParameterSupplier() { + this(new SecureJDKSourceOfRandomness()); + } + + public GeneratingParameterSupplier(SourceOfRandomness random) { + this.random = random; + } + + @Override + public List getValueSources(ParameterSignature sig) { + List potentials = new ArrayList(); + ForAll generationParms = sig.getAnnotation(ForAll.class); + RandomValueExtractor extractor = extractor(sig); + + for (int i = 0; i < generationParms.sampleSize(); ++i) { + Object generated = extractor.randomValue(random); + potentials.add(PotentialAssignment.forValue(String.valueOf(generated), generated)); + } + return potentials; + } + + private RandomValueExtractor extractor(ParameterSignature sig) { + ExtractedBy extractedBy = sig.getAnnotation(ExtractedBy.class); + + if (extractedBy != null) + return instantiate(extractedBy.value()); + return extractors.get(sig.getType()); + } + + private RandomValueExtractor instantiate(Class type) { + try { + return type.newInstance(); + } catch (InstantiationException ex) { + throw new IllegalStateException(ex); + } catch (IllegalAccessException ex) { + throw new IllegalStateException(ex); + } + } +} diff --git a/src/main/java/com/pholser/junit/parameters/JDKSourceOfRandomness.java b/src/main/java/com/pholser/junit/parameters/JDKSourceOfRandomness.java new file mode 100644 index 000000000..109e69951 --- /dev/null +++ b/src/main/java/com/pholser/junit/parameters/JDKSourceOfRandomness.java @@ -0,0 +1,9 @@ +package com.pholser.junit.parameters; + +import java.util.Random; + +public class JDKSourceOfRandomness extends AbstractJDKSourceOfRandomness { + public JDKSourceOfRandomness() { + super(new Random()); + } +} diff --git a/src/main/java/com/pholser/junit/parameters/RandomValueExtractor.java b/src/main/java/com/pholser/junit/parameters/RandomValueExtractor.java new file mode 100644 index 000000000..c118d77c3 --- /dev/null +++ b/src/main/java/com/pholser/junit/parameters/RandomValueExtractor.java @@ -0,0 +1,5 @@ +package com.pholser.junit.parameters; + +interface RandomValueExtractor { + Object randomValue(SourceOfRandomness random); +} diff --git a/src/main/java/com/pholser/junit/parameters/SecureJDKSourceOfRandomness.java b/src/main/java/com/pholser/junit/parameters/SecureJDKSourceOfRandomness.java new file mode 100644 index 000000000..f78e021e3 --- /dev/null +++ b/src/main/java/com/pholser/junit/parameters/SecureJDKSourceOfRandomness.java @@ -0,0 +1,13 @@ +package com.pholser.junit.parameters; + +import java.security.SecureRandom; + +public class SecureJDKSourceOfRandomness extends AbstractJDKSourceOfRandomness { + public SecureJDKSourceOfRandomness() { + super(new SecureRandom()); + } + + public SecureJDKSourceOfRandomness(byte[] seed) { + super(new SecureRandom(seed)); + } +} diff --git a/src/main/java/com/pholser/junit/parameters/SourceOfRandomness.java b/src/main/java/com/pholser/junit/parameters/SourceOfRandomness.java new file mode 100644 index 000000000..d71338bc5 --- /dev/null +++ b/src/main/java/com/pholser/junit/parameters/SourceOfRandomness.java @@ -0,0 +1,23 @@ +package com.pholser.junit.parameters; + +public interface SourceOfRandomness { + boolean nextBoolean(); + + void nextBytes(byte[] bytes); + + byte[] nextBytes(int length); + + double nextDouble(); + + float nextFloat(); + + double nextGaussian(); + + int nextInt(); + + int nextInt(int n); + + long nextLong(); + + void setSeed(long seed); +} diff --git a/src/test/java/com/pholser/junit/parameters/WhenMarkingTheoryParametersAsForAll.java b/src/test/java/com/pholser/junit/parameters/WhenMarkingTheoryParametersAsForAll.java new file mode 100644 index 000000000..f0f0268bb --- /dev/null +++ b/src/test/java/com/pholser/junit/parameters/WhenMarkingTheoryParametersAsForAll.java @@ -0,0 +1,123 @@ +package com.pholser.junit.parameters; + +import static org.junit.Assert.*; + +import java.util.Date; + +import org.junit.Test; +import org.junit.experimental.theories.Theories; +import org.junit.experimental.theories.Theory; +import org.junit.runner.JUnitCore; +import org.junit.runner.RunWith; + +public class WhenMarkingTheoryParametersAsForAll { + @Test + public void shouldFeedDefaultNumberOfRandomIntsToAMarkedIntParameter() throws Exception { + JUnitCore.runClasses(ForDefaultNumberOfInts.class); + + assertEquals(ForAll.class.getMethod("sampleSize").getDefaultValue(), ForDefaultNumberOfInts.iterations); + } + + @RunWith(Theories.class) + public static class ForDefaultNumberOfInts { + static int iterations; + + @Theory + public void shouldHold(@ForAll int i) { + ++iterations; + } + } + + @Test + public void shouldFeedDefaultNumberOfRandomDoublesToAMarkedDoubleParameter() throws Exception { + JUnitCore.runClasses(ForDefaultNumberOfDoubles.class); + + assertEquals(ForAll.class.getMethod("sampleSize").getDefaultValue(), ForDefaultNumberOfDoubles.iterations); + } + + @RunWith(Theories.class) + public static class ForDefaultNumberOfDoubles { + static int iterations; + + @Theory + public void shouldHold(@ForAll double d) { + ++iterations; + } + } + + @Test + public void shouldFeedDefaultNumberOfRandomStringsToAMarkedStringParameter() throws Exception { + JUnitCore.runClasses(ForDefaultNumberOfStrings.class); + + assertEquals(ForAll.class.getMethod("sampleSize").getDefaultValue(), ForDefaultNumberOfStrings.iterations); + } + + @RunWith(Theories.class) + public static class ForDefaultNumberOfStrings { + static int iterations; + + @Theory + public void shouldHold(@ForAll String s) { + ++iterations; + } + } + + @Test + public void shouldAllowDifferentNumberOfRandomValuesOnAMarkedParameter() { + JUnitCore.runClasses(ForDefaultNumberOfStrings.class); + + assertEquals(200, ForDefaultNumberOfStrings.iterations); + } + + @RunWith(Theories.class) + public static class ForSpecifiedNumberOfStrings { + static int iterations; + + @Theory + public void shouldHold(@ForAll(sampleSize = 200) String s) { + ++iterations; + } + } + + @Test + public void shouldAllowMultipleForAllParmsOnATheoryMethod() { + JUnitCore.runClasses(MultipleForAlls.class); + + assertEquals(15, MultipleForAlls.iterations); + } + + @RunWith(Theories.class) + public static class MultipleForAlls { + static int iterations; + + @Theory + public void shouldHold(@ForAll(sampleSize = 3) int i, @ForAll(sampleSize = 5) int j) { + ++iterations; + } + } + + @Test + public void shouldAllowNonPrimitiveTypesToBeRandomlyGenerated() { + JUnitCore.runClasses(NonPrimitiveParameter.class); + + assertEquals(100, RandomDateExtractor.numberOfCalls); + } + + @RunWith(Theories.class) + public static class NonPrimitiveParameter { + @Theory + public void shouldHold(@ForAll @ExtractedBy(RandomDateExtractor.class) Date d) { + assertNotNull(d); + } + } + + static class RandomDateExtractor implements RandomValueExtractor { + static int numberOfCalls; + + @Override + public Object randomValue(SourceOfRandomness random) { + ++numberOfCalls; + return new Date(random.nextLong()); + } + } +}