├── .gitignore ├── LICENSE ├── README.rst ├── pom.xml └── src ├── main └── java │ └── works │ └── hypothesis │ ├── ByteUtils.java │ ├── DistributionalTestData.java │ ├── Flaky.java │ ├── FloatRangeGenerator.java │ ├── Frozen.java │ ├── Hypothesis.java │ ├── HypothesisDataDistribution.java │ ├── HypothesisException.java │ ├── HypothesisPredicate.java │ ├── HypothesisSettings.java │ ├── HypothesisTestFunction.java │ ├── HypothesisUniformDataDistributions.java │ ├── Interval.java │ ├── NoSuchExample.java │ ├── Status.java │ ├── StopTest.java │ ├── TestData.java │ ├── TestDataForBuffer.java │ ├── TestDataRule.java │ ├── TestRunner.java │ └── strategies │ ├── BooleanStrategy.java │ ├── BytesStrategy.java │ ├── IntegerStrategy.java │ ├── ListStrategy.java │ ├── Strategies.java │ └── Strategy.java └── test └── java └── works └── hypothesis ├── TestBooleansAndIntegers.java ├── TestByteUtils.java ├── TestDataRuleTest.java ├── TestExampleQuality.java ├── TestFloatRange.java ├── TestSortingAList.java └── TestTestData.java /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | *.swp 3 | *.swo 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2016 David R. MacIver 2 | 3 | This program is free software: you can redistribute it and/or modify 4 | it under the terms of the GNU Affero General Public License as published by 5 | the Free Software Foundation, either version 3 of the License, or 6 | (at your option) any later version. 7 | 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU Affero General Public License for more details. 12 | 13 | You should have received a copy of the GNU Affero General Public License 14 | along with this program. If not, see . 15 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ========================================= 2 | Hypothesis for Java feasibility prototype 3 | ========================================= 4 | 5 | `Hypothesis `_ is a modern property based testing system designed for 6 | mainstream languages. The original version is for Python, where it works extremely well. 7 | 8 | This is a very rough prototype of what Hypothesis could look like in Java. It's not even close to being production 9 | ready software. It implements the core algorithm and a very crude API. It has only been lightly tested. It exists to 10 | prove that the concept is feasible, and it achieves that. 11 | 12 | Things that it currently supports: 13 | 14 | 1. The core Hypothesis engine with example generation and shrinking. 15 | 2. Working JUnit integration. 16 | 3. A very small prototype data generator library. 17 | 18 | Things that are currently unimplemented: 19 | 20 | 1. The example database. The model used is the Hypothesis one where data is represented in an easy to serialize format, 21 | so implementing this is a Simple Matter Of Code, but it doesn't affect the feasibility so I haven't bothered yet. 22 | 2. The example mutator and shrinker are significantly less sophisticated than the Python one. There's no technical 23 | reason for this to be case, they're just based on an earlier prototype of the concept that works well enough for a 24 | tech demo. 25 | 3. Most of the data generation library. This is in many ways where the bulk of the work in porting an implementation 26 | lies. 27 | 4. Decent example printing. Right now it just uses toString, which is not ideal. Additionally it's not obvious what 28 | the best way to hook printing into JUnit is. 29 | 5. A license that you would want to use it under. 30 | 31 | Example usage 32 | ------------- 33 | 34 | The Hypothesis Java prototype is build on JUnit's `Rules `_ 35 | functionality. 36 | 37 | You use a TestDataRule as a source of data. Within a test you can then call draw on it with a strategy 38 | argument to get an example drawn from that strategy. You can call draw as many times as you want for 39 | tests that need multiple values. 40 | 41 | Here's an example of testing a sorting function using this: 42 | 43 | .. code-block:: java 44 | 45 | import org.junit.Rule; 46 | import org.junit.Test; 47 | 48 | import java.util.Comparator; 49 | import java.util.Iterator; 50 | import java.util.List; 51 | 52 | import static com.drmaciver.hypothesis.generators.Generators.integers; 53 | import static com.drmaciver.hypothesis.generators.Generators.lists; 54 | import static org.junit.Assert.assertTrue; 55 | 56 | public class TestSortingAList { 57 | @Rule 58 | public final TestDataRule data = new TestDataRule(); 59 | 60 | @Test 61 | public void testIsSortedAfterSorting(){ 62 | List ls = data.draw(lists(integers())); 63 | ls.sort(Comparator.naturalOrder()); 64 | assertSorted(ls); 65 | } 66 | 67 | // Utility assertion function. Doesn't use any Hypothesis functionality. 68 | private > void assertSorted(List elements){ 69 | if(elements.isEmpty()) return; 70 | Iterator it = elements.iterator(); 71 | T previous = it.next(); 72 | while(it.hasNext()){ 73 | T current = it.next(); 74 | assertTrue(previous.compareTo(current) <= 0); 75 | previous = current; 76 | } 77 | } 78 | 79 | } 80 | 81 | 82 | Licensing 83 | --------- 84 | 85 | This is currently under the AGPL. That is not intended as a long term licensing strategy but gives me more 86 | flexibility for future plans. The most likely future licensing path is that there will be a core version available 87 | under the MPLv2 or Apache, with a proprietary extension library containing most of the strategies and some useful 88 | extensions. I could be persuaded to make the whole thing open source under Apache/MPLv2 but the persuasion would have 89 | to be financial. 90 | 91 | Future Plans 92 | ------------ 93 | 94 | I'm not very interested in supporting another fully fledged property based testing implementation of the calibre 95 | of the Python version of Hypothesis for free. As such this library is likely to remain a prototype without some 96 | financial backing to change that, in the form of paid development and/or ongoing support contracts. 97 | 98 | If you are interested in a fully fledged Hypothesis for Java, please `contact me `_ to 99 | discuss terms. 100 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | Hypothesis 4 | Hypothesis 5 | 0.0.1-SNAPSHOT 6 | 7 | src/main/java 8 | src/test/java 9 | 10 | 11 | maven-compiler-plugin 12 | 3.3 13 | 14 | 1.8 15 | 1.8 16 | 17 | 18 | 19 | 20 | 21 | 22 | junit 23 | junit 24 | 4.12 25 | compile 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/ByteUtils.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | public class ByteUtils { 4 | static byte[] deleteInterval(byte[] x, int start, int end) { 5 | final byte[] result = new byte[x.length - (end - start)]; 6 | System.arraycopy(x, 0, result, 0, start); 7 | System.arraycopy(x, end, result, start, x.length - end); 8 | return result; 9 | } 10 | 11 | static byte[] sortInterval(byte[] x, int start, int end) { 12 | assert start <= end; 13 | assert end <= x.length; 14 | final byte[] result = x.clone(); 15 | final int[] counter = new int[256]; 16 | for (int i = start; i < end; i++) { 17 | final int b = unsigned(x[i]); 18 | counter[b]++; 19 | } 20 | int index = start; 21 | for (int i = 0; i < 256; i++) { 22 | for (int j = 0; j < counter[i]; j++) 23 | result[index++] = (byte) i; 24 | } 25 | return result; 26 | } 27 | 28 | public static byte[] swap(byte[] buffer, int i, int j) { 29 | final byte[] result = buffer.clone(); 30 | final byte c = result[i]; 31 | result[i] = result[j]; 32 | result[j] = c; 33 | return result; 34 | } 35 | 36 | static int unsigned(byte b) { 37 | return b & 0xff; 38 | } 39 | 40 | static byte[] zeroInterval(byte[] x, int start, int end) { 41 | final byte[] result = x.clone(); 42 | for (int i = start; i < end; i++) 43 | result[i] = (byte) 0; 44 | return result; 45 | } 46 | 47 | public static int intFromBytes(byte[] bytes, int i) { 48 | int result = 0; 49 | for (int j = 0; j < 4; j++) { 50 | result *= 256; 51 | result += ByteUtils.unsigned(bytes[i + j]); 52 | } 53 | return result; 54 | } 55 | 56 | public static void intToBytes(byte[] bytes, int index, int value) { 57 | for (int j = 3; j >= 0; j--) { 58 | bytes[index + j] = (byte) (value & 0xff); 59 | value >>>= 8; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/DistributionalTestData.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * Created by david on 4/9/16. 7 | */ 8 | public class DistributionalTestData extends TestData { 9 | private final Random random; 10 | 11 | public DistributionalTestData(int maxSize, Random random) { 12 | super(maxSize); 13 | this.random = random; 14 | } 15 | 16 | @Override 17 | protected byte[] doDrawBytes(int n, HypothesisDataDistribution distribution) { 18 | byte[] result = new byte[n]; 19 | distribution.generateData(random, result, 0, n); 20 | return result; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/Flaky.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | public class Flaky extends RuntimeException { 4 | 5 | /** 6 | * 7 | */ 8 | private static final long serialVersionUID = 1L; 9 | 10 | public Flaky(String string) { 11 | super(string); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/FloatRangeGenerator.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import works.hypothesis.strategies.Strategy; 4 | 5 | import java.util.Random; 6 | 7 | /** 8 | * Created by david on 4/9/16. 9 | */ 10 | public class FloatRangeGenerator implements Strategy { 11 | private final float right; 12 | private final float left; 13 | private final HypothesisDataDistribution distribution; 14 | 15 | FloatRangeGenerator(float left, float right) { 16 | this.left = left; 17 | this.right = right; 18 | this.distribution = new HypothesisDataDistribution() { 19 | 20 | @Override 21 | public void generateData(Random random, byte[] target, int index, int nbytes) { 22 | if (nbytes != 4) { 23 | throw new RuntimeException("Bad number of bytes " + nbytes); 24 | } 25 | float f = random.nextFloat() * (right - left) + left; 26 | ByteUtils.intToBytes(target, index, Float.floatToIntBits(f)); 27 | } 28 | }; 29 | } 30 | 31 | 32 | @Override 33 | public Float doDraw(TestData data) { 34 | byte[] value = data.drawBytes(4, this.distribution); 35 | float f = Float.intBitsToFloat(ByteUtils.intFromBytes(value, 0)); 36 | data.assume(f >= left); 37 | data.assume(f <= right); 38 | return f; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/Frozen.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | /** 4 | * Created by david on 4/9/16. 5 | */ 6 | class Frozen extends HypothesisException { 7 | /** 8 | * A mutation method has been called on a frozen TestDataForBuffer. 9 | */ 10 | private static final long serialVersionUID = 1L; 11 | 12 | public Frozen(String arg0) { 13 | super(arg0); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/Hypothesis.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import works.hypothesis.strategies.Strategy; 4 | 5 | public class Hypothesis { 6 | public static T find(Strategy generator, HypothesisPredicate condition) throws NoSuchExample { 7 | return Hypothesis.find(generator, condition, null); 8 | } 9 | 10 | public static T find(final Strategy generator, final HypothesisPredicate condition, 11 | final HypothesisSettings settings) throws NoSuchExample { 12 | final byte[] buffer = TestRunner.findInterestingBuffer(new HypothesisTestFunction() { 13 | public void runTest(TestData data) { 14 | final T value = generator.doDraw(data); 15 | if (condition.test(value)) { 16 | data.markInteresting(); 17 | } 18 | } 19 | }, settings); 20 | if (buffer == null) { 21 | throw new NoSuchExample(); 22 | } else { 23 | try { 24 | final T result = generator.doDraw(new TestDataForBuffer(buffer)); 25 | if (!condition.test(result)) { 26 | throw new Flaky("Result " + result + " did not satisfy condition."); 27 | } 28 | return result; 29 | } catch (StopTest e) { 30 | throw new Flaky("Generator drew a different amount of data on retry"); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/HypothesisDataDistribution.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * Created by david on 4/9/16. 7 | */ 8 | public interface HypothesisDataDistribution { 9 | void generateData(Random random, byte[] target, int index, int nbytes); 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/HypothesisException.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | public class HypothesisException extends RuntimeException { 4 | 5 | /** 6 | * 7 | */ 8 | private static final long serialVersionUID = 1L; 9 | 10 | public HypothesisException() { 11 | super(); 12 | } 13 | 14 | public HypothesisException(String arg0) { 15 | super(arg0); 16 | } 17 | 18 | public HypothesisException(String arg0, Throwable arg1) { 19 | super(arg0, arg1); 20 | } 21 | 22 | public HypothesisException(String arg0, Throwable arg1, boolean arg2, boolean arg3) { 23 | super(arg0, arg1, arg2, arg3); 24 | } 25 | 26 | public HypothesisException(Throwable arg0) { 27 | super(arg0); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/HypothesisPredicate.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | public interface HypothesisPredicate { 4 | boolean test(T value); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/HypothesisSettings.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | public class HypothesisSettings { 4 | private int bufferSize = 8 * 1024; 5 | 6 | private int mutations = 50; 7 | 8 | private int maxExamples = 200; 9 | 10 | private int maxShrinks = 2000; 11 | 12 | private boolean debug = false; 13 | 14 | public int getBufferSize() { 15 | return bufferSize; 16 | } 17 | 18 | public int getMaxExamples() { 19 | return maxExamples; 20 | } 21 | 22 | public int getMaxShrinks() { 23 | return maxShrinks; 24 | } 25 | 26 | public int getMutations() { 27 | return mutations; 28 | } 29 | 30 | public boolean isDebug() { 31 | return debug; 32 | } 33 | 34 | public void setBufferSize(int bufferSize) { 35 | this.bufferSize = bufferSize; 36 | } 37 | 38 | public void setDebug(boolean debug) { 39 | this.debug = debug; 40 | } 41 | 42 | public void setMaxExamples(int maxExamples) { 43 | this.maxExamples = maxExamples; 44 | } 45 | 46 | public void setMaxShrinks(int maxShrinks) { 47 | this.maxShrinks = maxShrinks; 48 | } 49 | 50 | public void setMutations(int mutations) { 51 | this.mutations = mutations; 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/HypothesisTestFunction.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | public interface HypothesisTestFunction { 4 | void runTest(TestData data); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/HypothesisUniformDataDistributions.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import java.util.Random; 4 | 5 | /** 6 | * Created by david on 4/9/16. 7 | */ 8 | public class HypothesisUniformDataDistributions implements HypothesisDataDistribution { 9 | public static final HypothesisUniformDataDistributions INSTANCE = new HypothesisUniformDataDistributions(); 10 | 11 | @Override 12 | public void generateData(Random random, byte[] target, int index, int nbytes) { 13 | for (int j = 0; j < nbytes; j++) { 14 | target[index + j] = (byte) random.nextInt(256); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/Interval.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | /** 4 | * Created by david on 4/9/16. 5 | */ 6 | class Interval implements Comparable { 7 | final int start; 8 | 9 | final int end; 10 | 11 | public Interval(int start, int end) { 12 | super(); 13 | this.start = start; 14 | this.end = end; 15 | } 16 | 17 | public int compareTo(Interval other) { 18 | if (length() < other.length()) 19 | return -1; 20 | if (length() > other.length()) 21 | return 1; 22 | if (start < other.start) 23 | return -1; 24 | if (start > other.start) 25 | return 1; 26 | return 0; 27 | } 28 | 29 | @Override 30 | public boolean equals(Object obj) { 31 | if (this == obj) 32 | return true; 33 | if (obj == null) 34 | return false; 35 | if (getClass() != obj.getClass()) 36 | return false; 37 | final Interval other = (Interval) obj; 38 | return end == other.end && start == other.start; 39 | } 40 | 41 | @Override 42 | public int hashCode() { 43 | final int prime = 31; 44 | int result = 1; 45 | result = prime * result + end; 46 | result = prime * result + start; 47 | return result; 48 | } 49 | 50 | int length() { 51 | return end - start; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/NoSuchExample.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | public class NoSuchExample extends Exception { 4 | 5 | /** 6 | * 7 | */ 8 | private static final long serialVersionUID = 1; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/Status.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | /** 4 | * Created by david on 4/9/16. 5 | */ 6 | enum Status { 7 | OVERRUN, INVALID, VALID, INTERESTING 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/StopTest.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | /** 4 | * Created by david on 4/9/16. 5 | */ 6 | class StopTest extends HypothesisException { 7 | /** 8 | * A condition that ends the current test has occurred. 9 | */ 10 | private static final long serialVersionUID = 1L; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/TestData.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import works.hypothesis.strategies.Strategy; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | /** 11 | * Created by david on 4/9/16. 12 | */ 13 | public abstract class TestData implements Comparable { 14 | final List intervalStarts = new ArrayList(); 15 | final List intervals = new ArrayList(); 16 | byte[] record; 17 | boolean frozen = false; 18 | int index = 0; 19 | Status status = Status.VALID; 20 | int checksum = -1; 21 | 22 | public TestData(int maxSize) { 23 | this.record = new byte[maxSize]; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "TestData{" + 29 | "record=" + Arrays.toString(record) + 30 | ", frozen=" + frozen + 31 | ", index=" + index + 32 | '}'; 33 | } 34 | 35 | public int compareTo(TestData other) { 36 | if (!(frozen && other.frozen)) { 37 | throw new RuntimeException("Cannot compare non frozen TestDataForBuffer"); 38 | } 39 | if (intervals.size() < other.intervals.size()) 40 | return -1; 41 | if (intervals.size() > other.intervals.size()) 42 | return 1; 43 | if (this.index < other.index) 44 | return -1; 45 | if (this.index > other.index) 46 | return 1; 47 | for (int i = 0; i < index; i++) { 48 | final int c = ByteUtils.unsigned(record[i]); 49 | final int d = ByteUtils.unsigned(other.record[i]); 50 | if (c < d) 51 | return -1; 52 | if (c > d) 53 | return 1; 54 | } 55 | return 0; 56 | } 57 | 58 | private void assertNotFrozen(String name) { 59 | if (frozen) { 60 | throw new Frozen("Cannot call " + name + " on a frozen TestDataForBuffer."); 61 | } 62 | } 63 | 64 | public T draw(Strategy generator) { 65 | startExample(); 66 | final T result = generator.doDraw(this); 67 | stopExample(); 68 | return result; 69 | } 70 | 71 | public byte drawByte() { 72 | return drawBytes(1)[0]; 73 | } 74 | 75 | public byte[] drawBytes(int n) { 76 | return this.drawBytes(n, HypothesisUniformDataDistributions.INSTANCE); 77 | } 78 | 79 | public byte[] drawBytes(int n, HypothesisDataDistribution distribution) { 80 | assertNotFrozen("drawBytes"); 81 | startExample(); 82 | if (index + n > record.length) { 83 | status = Status.OVERRUN; 84 | freeze(); 85 | throw new StopTest(); 86 | } 87 | final byte[] result = doDrawBytes(n, distribution); 88 | System.arraycopy(result, 0, record, index, n); 89 | index += n; 90 | stopExample(); 91 | return result; 92 | } 93 | 94 | void checkIntegrity() { 95 | if (!frozen) return; 96 | final int hashCode = Arrays.hashCode(record); 97 | if (hashCode != checksum) { 98 | throw new RuntimeException("TestData.record has been modified."); 99 | } 100 | } 101 | 102 | protected abstract byte[] doDrawBytes(int n, HypothesisDataDistribution distribution); 103 | 104 | public void freeze() { 105 | if (frozen) 106 | return; 107 | frozen = true; 108 | record = Arrays.copyOfRange(record, 0, index); 109 | checksum = Arrays.hashCode(record); 110 | Collections.sort(intervals); 111 | } 112 | 113 | public Status getStatus() { 114 | return status; 115 | } 116 | 117 | public int index() { 118 | return index; 119 | } 120 | 121 | public boolean isFrozen() { 122 | return frozen; 123 | } 124 | 125 | public void markInteresting() { 126 | assertNotFrozen("markInteresting"); 127 | if (status == Status.VALID) { 128 | status = Status.INTERESTING; 129 | } 130 | throw new StopTest(); 131 | } 132 | 133 | public void markInvalid() { 134 | assertNotFrozen("markInvalid"); 135 | if (status == Status.VALID) { 136 | status = Status.INVALID; 137 | } 138 | throw new StopTest(); 139 | } 140 | 141 | public void assume(boolean condition) { 142 | if (!condition) this.markInvalid(); 143 | } 144 | 145 | public void startExample() { 146 | assertNotFrozen("startExample"); 147 | intervalStarts.add(index); 148 | } 149 | 150 | public void stopExample() { 151 | assertNotFrozen("stopExample"); 152 | final int k = intervalStarts.remove(intervalStarts.size() - 1); 153 | if (k != index) { 154 | final Interval interval = new Interval(k, index); 155 | if (intervals.size() == 0 || intervals.get(intervals.size() - 1) != interval) 156 | intervals.add(interval); 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/TestDataForBuffer.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import java.util.Arrays; 4 | 5 | public class TestDataForBuffer extends TestData { 6 | 7 | private final byte[] buffer; 8 | 9 | public TestDataForBuffer(byte[] buffer) { 10 | super(buffer.length); 11 | this.buffer = buffer.clone(); 12 | } 13 | 14 | @Override 15 | protected byte[] doDrawBytes(int n, HypothesisDataDistribution distribution) { 16 | return Arrays.copyOfRange(buffer, index(), index() + n); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/TestDataRule.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import works.hypothesis.strategies.Strategy; 4 | import org.junit.AssumptionViolatedException; 5 | import org.junit.rules.TestRule; 6 | import org.junit.runner.Description; 7 | import org.junit.runners.model.Statement; 8 | 9 | import java.util.logging.Logger; 10 | 11 | public class TestDataRule extends Object implements TestRule { 12 | private final HypothesisSettings settings; 13 | 14 | private TestData data = null; 15 | 16 | private Throwable lastError = null; 17 | 18 | private Logger logger; 19 | private int index = 0; 20 | 21 | public TestDataRule() { 22 | this(new HypothesisSettings()); 23 | } 24 | 25 | public TestDataRule(HypothesisSettings settings) { 26 | super(); 27 | this.settings = settings; 28 | } 29 | 30 | public Statement apply(final Statement base, final Description description) { 31 | return new Statement() { 32 | @Override 33 | public void evaluate() throws Throwable { 34 | logger = null; 35 | index = 0; 36 | final TestRunner runner = new TestRunner(new HypothesisTestFunction() { 37 | public void runTest(TestData d) { 38 | data = d; 39 | try { 40 | base.evaluate(); 41 | } catch (final AssumptionViolatedException e) { 42 | d.markInvalid(); 43 | } catch (final HypothesisException e) { 44 | throw e; 45 | } catch (final Throwable t) { 46 | if (!d.isFrozen()) { 47 | lastError = t; 48 | d.markInteresting(); 49 | } 50 | } 51 | } 52 | }, settings); 53 | runner.run(); 54 | if (runner.lastData.getStatus() == Status.INTERESTING) { 55 | logger = Logger 56 | .getLogger(description.getTestClass().getName() + '.' + description.getDisplayName()); 57 | index = 0; 58 | assert lastError != null; 59 | data = new TestDataForBuffer(runner.lastData.record); 60 | base.evaluate(); 61 | throw new Flaky("Expected error: " + lastError.toString()); 62 | } 63 | } 64 | }; 65 | } 66 | 67 | public T draw(Strategy generator) { 68 | final T result = getData().draw(generator); 69 | final String string = result.toString(); 70 | if (logger != null) { 71 | logger.info("Draw #" + (++index) + ": " + string); 72 | } 73 | return result; 74 | } 75 | 76 | TestData getData() { 77 | assert data != null; 78 | return data; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/TestRunner.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Arrays; 5 | import java.util.List; 6 | import java.util.Random; 7 | 8 | class TestRunner { 9 | final Random random; 10 | final HypothesisTestFunction _testFunction; 11 | final HypothesisSettings settings; 12 | int changed = 0; 13 | int shrinks = 0; 14 | int validExamples = 0; 15 | TestData lastData = null; 16 | 17 | public TestRunner(HypothesisTestFunction _testFunction, HypothesisSettings settings) { 18 | super(); 19 | this._testFunction = _testFunction; 20 | if (settings == null) 21 | settings = new HypothesisSettings(); 22 | this.settings = settings; 23 | random = new Random(); 24 | } 25 | 26 | static byte[] findInterestingBuffer(HypothesisTestFunction test, HypothesisSettings settings) { 27 | final TestRunner runner = new TestRunner(test, settings); 28 | runner.run(); 29 | if (runner.lastData.getStatus() == Status.INTERESTING) { 30 | runner.lastData.checkIntegrity(); 31 | assert runner.lastData.frozen; 32 | return runner.lastData.record; 33 | } 34 | return null; 35 | } 36 | 37 | private void _run() { 38 | newBuffer(); 39 | int mutations = 0; 40 | int generation = 0; 41 | while (validExamples <= settings.getMaxExamples() && lastData.getStatus() != Status.INTERESTING && generation <= settings.getMutations()) { 42 | if (mutations >= settings.getMutations()) { 43 | generation += 1; 44 | mutations = 0; 45 | incorporateNewBuffer(mutateDataToNewBuffer()); 46 | } else { 47 | newBuffer(); 48 | } 49 | } 50 | if (lastData.getStatus() != Status.INTERESTING) { 51 | return; 52 | } 53 | 54 | // We have successfully found an interesting buffer and shrinking starts 55 | // here 56 | 57 | int changeCounter = -1; 58 | while (changed > changeCounter) { 59 | changeCounter = changed; 60 | assert lastData.getStatus() == Status.INTERESTING; 61 | for (int i = 0; i < lastData.intervals.size(); ) { 62 | final Interval interval = lastData.intervals.get(i); 63 | if (!incorporateNewBuffer(ByteUtils.deleteInterval(lastData.record, interval.start, interval.end))) { 64 | i++; 65 | } 66 | } 67 | if (changed > changeCounter) 68 | continue; 69 | for (int i = 0; i < lastData.intervals.size(); i++) { 70 | final Interval interval = lastData.intervals.get(i); 71 | incorporateNewBuffer(ByteUtils.zeroInterval(lastData.record, interval.start, interval.end)); 72 | } 73 | for (int i = 0; i < lastData.intervals.size(); i++) { 74 | final Interval interval = lastData.intervals.get(i); 75 | incorporateNewBuffer(ByteUtils.sortInterval(lastData.record, interval.start, interval.end)); 76 | } 77 | if (changed > changeCounter) 78 | continue; 79 | 80 | for (int i = 0; i < lastData.record.length - 8; i++) { 81 | incorporateNewBuffer(ByteUtils.zeroInterval(lastData.record, i, i + 8)); 82 | } 83 | for (int i = 0; i < lastData.record.length; i++) { 84 | if (lastData.record[i] == 0) 85 | continue; 86 | final byte[] buf = lastData.record.clone(); 87 | buf[i]--; 88 | if (!incorporateNewBuffer(buf)) 89 | break; 90 | for (int c = 0; c < ByteUtils.unsigned(lastData.record[i]) - 1; c++) { 91 | buf[i] = (byte) c; 92 | if (incorporateNewBuffer(buf)) 93 | break; 94 | } 95 | } 96 | 97 | for (int i = 0; i < lastData.record.length - 1; i++) { 98 | incorporateNewBuffer(ByteUtils.sortInterval(lastData.record, i, i + 2)); 99 | } 100 | if (changed > changeCounter) 101 | continue; 102 | for (int i = 0; i < lastData.record.length; i++) { 103 | incorporateNewBuffer(ByteUtils.deleteInterval(lastData.record, i, i + 1)); 104 | } 105 | for (int i = 0; i < lastData.record.length; i++) { 106 | if (lastData.record[i] == 0) { 107 | final byte[] buf = lastData.record.clone(); 108 | for (int j = i; j >= 0; j--) { 109 | if (buf[j] != 0) { 110 | buf[j]--; 111 | incorporateNewBuffer(buf); 112 | break; 113 | } else { 114 | buf[j] = (byte) 255; 115 | } 116 | } 117 | } 118 | } 119 | if (changed > changeCounter) 120 | continue; 121 | final List> buckets = new ArrayList>(); 122 | for (int i = 0; i < 256; i++) 123 | buckets.add(new ArrayList()); 124 | for (int i = 0; i < lastData.record.length; i++) { 125 | buckets.get(ByteUtils.unsigned(lastData.record[i])).add(i); 126 | } 127 | for (final List bucket : buckets) { 128 | for (final int j : bucket) { 129 | for (final int k : bucket) { 130 | if (j < k) { 131 | final byte[] buf = lastData.record.clone(); 132 | if (buf[j] == buf[k]) { 133 | if (buf[j] == 0) { 134 | if (j > 0 && buf[j - 1] != 0 && buf[k - 1] != 0) { 135 | buf[j - 1]--; 136 | buf[k - 1]--; 137 | buf[j] = (byte) 255; 138 | buf[k] = (byte) 255; 139 | incorporateNewBuffer(buf); 140 | continue; 141 | } 142 | } else { 143 | for (byte c = 0; c < buf[j]; c++) { 144 | buf[j] = c; 145 | buf[k] = c; 146 | if (incorporateNewBuffer(buf)) 147 | break; 148 | } 149 | } 150 | } 151 | } 152 | } 153 | } 154 | } 155 | if (changed > changeCounter) 156 | continue; 157 | for (int i = 0; i < lastData.record.length; i++) { 158 | if (lastData.record[i] == 0) 159 | continue; 160 | for (int j = i + 1; j < lastData.record.length; j++) { 161 | if (ByteUtils.unsigned(lastData.record[i]) > ByteUtils.unsigned(lastData.record[j])) { 162 | incorporateNewBuffer(ByteUtils.swap(lastData.record, i, j)); 163 | } 164 | if (lastData.record[i] != 0 && lastData.record[j] != 0) { 165 | final byte[] buf = lastData.record.clone(); 166 | buf[i]--; 167 | buf[j]--; 168 | incorporateNewBuffer(buf); 169 | } 170 | } 171 | } 172 | } 173 | } 174 | 175 | boolean considerNewTestData(TestData data) { 176 | if (lastData.getStatus().compareTo(data.getStatus()) < 0) { 177 | return true; 178 | } 179 | if (lastData.getStatus().compareTo(data.getStatus()) > 0) { 180 | return false; 181 | } 182 | if (data.getStatus() == Status.INVALID) { 183 | return data.index() >= lastData.index(); 184 | } 185 | if (data.getStatus() == Status.OVERRUN) { 186 | return data.index() <= lastData.index(); 187 | } 188 | return data.getStatus() != Status.INTERESTING || lastData.compareTo(data) > 0; 189 | } 190 | 191 | boolean incorporateNewBuffer(byte[] buffer) { 192 | assert buffer.length <= settings.getBufferSize(); 193 | if (Arrays.equals(buffer, lastData.record)) 194 | return false; 195 | final TestData data = new TestDataForBuffer(buffer); 196 | testFunction(data); 197 | if (considerNewTestData(data)) { 198 | if (lastData.getStatus() == Status.INTERESTING) { 199 | shrinks++; 200 | } 201 | changed++; 202 | lastData = data; 203 | if (settings.isDebug()) 204 | if (shrinks >= settings.getMaxShrinks()) 205 | throw new StopShrinking(); 206 | return true; 207 | } 208 | return false; 209 | } 210 | 211 | private byte[] mutateDataToNewBuffer() { 212 | final int n = Math.min(lastData.record.length, lastData.index()); 213 | if (n == 0) { 214 | return new byte[0]; 215 | } 216 | if (n == 1) { 217 | final byte[] result = new byte[1]; 218 | random.nextBytes(result); 219 | return result; 220 | } 221 | final byte[] result = lastData.record.clone(); 222 | if (lastData.getStatus() == Status.OVERRUN) { 223 | for (int i = 0; i < result.length; i++) { 224 | if (result[i] == 0) 225 | continue; 226 | switch (random.nextInt(3)) { 227 | case 0: 228 | result[i] = 0; 229 | break; 230 | case 1: 231 | result[i] = (byte) random.nextInt(ByteUtils.unsigned(result[i])); 232 | break; 233 | case 2: 234 | continue; 235 | } 236 | } 237 | return result; 238 | } 239 | if (lastData.intervals.size() <= 1 || random.nextInt(3) == 0) { 240 | int u, v; 241 | if (random.nextBoolean() || lastData.intervals.size() <= 1) { 242 | u = random.nextInt(lastData.record.length); 243 | v = u + random.nextInt(lastData.record.length - u); 244 | } else { 245 | final Interval in = lastData.intervals.get(random.nextInt(lastData.intervals.size())); 246 | u = in.start; 247 | v = in.end; 248 | } 249 | switch (random.nextInt(3)) { 250 | case 0: 251 | for (int i = u; i < v; i++) { 252 | result[i] = 0; 253 | } 254 | break; 255 | case 1: 256 | for (int i = u; i < v; i++) { 257 | result[i] = (byte) 255; 258 | } 259 | break; 260 | case 2: 261 | for (int i = u; i < v; i++) { 262 | result[i] = (byte) random.nextInt(256); 263 | } 264 | break; 265 | } 266 | } else { 267 | final int i = random.nextInt(lastData.intervals.size() - 1); 268 | final int j = i + 1 + random.nextInt(lastData.intervals.size() - 1 - i); 269 | final Interval int1 = lastData.intervals.get(i); 270 | final Interval int2 = lastData.intervals.get(j); 271 | assert int2.length() <= int1.length(); 272 | System.arraycopy(lastData.record, int2.start, result, int1.start, int2.length()); 273 | if (int1.length() != int2.length()) { 274 | System.arraycopy(lastData.record, int1.end, result, int1.start + int2.length(), 275 | lastData.record.length - int1.end); 276 | } 277 | } 278 | return result; 279 | } 280 | 281 | void newBuffer() { 282 | final TestData data = new DistributionalTestData(settings.getBufferSize(), this.random); 283 | testFunction(data); 284 | data.freeze(); 285 | lastData = data; 286 | } 287 | 288 | void run() { 289 | try { 290 | _run(); 291 | } catch (final StopShrinking ignored) { 292 | 293 | } 294 | } 295 | 296 | private void testFunction(TestData data) { 297 | try { 298 | _testFunction.runTest(data); 299 | } catch (final StopTest ignored) { 300 | } 301 | data.freeze(); 302 | if (data.status.compareTo(Status.VALID) >= 0) 303 | validExamples++; 304 | } 305 | 306 | static class StopShrinking extends RuntimeException { 307 | 308 | /** 309 | * 310 | */ 311 | private static final long serialVersionUID = 1L; 312 | 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/strategies/BooleanStrategy.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis.strategies; 2 | 3 | import works.hypothesis.TestData; 4 | 5 | /** 6 | * Created by david on 15/08/2016. 7 | */ 8 | public class BooleanStrategy implements Strategy { 9 | @Override 10 | public Boolean doDraw(TestData data) { 11 | return (data.drawByte() & 1) != 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/strategies/BytesStrategy.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis.strategies; 2 | 3 | import works.hypothesis.TestData; 4 | 5 | public class BytesStrategy implements Strategy { 6 | private final int n; 7 | 8 | public BytesStrategy(int n) { 9 | super(); 10 | this.n = n; 11 | } 12 | 13 | public byte[] doDraw(TestData data) { 14 | return data.drawBytes(n); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/strategies/IntegerStrategy.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis.strategies; 2 | 3 | import works.hypothesis.ByteUtils; 4 | import works.hypothesis.TestData; 5 | 6 | public class IntegerStrategy implements Strategy { 7 | IntegerStrategy() { 8 | } 9 | 10 | public Integer doDraw(TestData data) { 11 | return ByteUtils.intFromBytes(data.drawBytes(4), 0); 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/strategies/ListStrategy.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis.strategies; 2 | 3 | import works.hypothesis.TestData; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | public class ListStrategy implements Strategy> { 9 | 10 | private final Strategy elementGenerator; 11 | 12 | ListStrategy(Strategy elementGenerator) { 13 | this.elementGenerator = elementGenerator; 14 | } 15 | 16 | public List doDraw(TestData data) { 17 | final List result = new ArrayList(); 18 | while (true) { 19 | data.startExample(); 20 | if (data.drawByte() <= 50) { 21 | data.stopExample(); 22 | break; 23 | } 24 | result.add(this.elementGenerator.doDraw(data)); 25 | data.stopExample(); 26 | } 27 | return result; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/strategies/Strategies.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis.strategies; 2 | 3 | /** 4 | * Created by david on 4/18/16. 5 | */ 6 | public interface Strategies { 7 | static ListStrategy lists(Strategy elementGenerator) { 8 | return new ListStrategy(elementGenerator); 9 | } 10 | 11 | static Strategy integers() { 12 | return new IntegerStrategy(); 13 | } 14 | static Strategy booleans() { return new BooleanStrategy(); } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/works/hypothesis/strategies/Strategy.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis.strategies; 2 | 3 | import works.hypothesis.TestData; 4 | 5 | @FunctionalInterface 6 | public interface Strategy { 7 | T doDraw(TestData data); 8 | } 9 | -------------------------------------------------------------------------------- /src/test/java/works/hypothesis/TestBooleansAndIntegers.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | 6 | import static works.hypothesis.strategies.Strategies.booleans; 7 | import static works.hypothesis.strategies.Strategies.integers; 8 | import static junit.framework.TestCase.assertFalse; 9 | 10 | public class TestBooleansAndIntegers { 11 | @Rule 12 | public final TestDataRule data = new TestDataRule(); 13 | 14 | @Test 15 | public void testBooleansAreNotIntegers(){ 16 | Integer anInt = data.draw(integers()); 17 | Boolean aBool = data.draw(booleans()); 18 | assertFalse(anInt.equals(aBool)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/works/hypothesis/TestByteUtils.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import works.hypothesis.strategies.BytesStrategy; 4 | import works.hypothesis.strategies.Strategy; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertArrayEquals; 9 | 10 | /** 11 | * Created by david on 4/9/16. 12 | */ 13 | public class TestByteUtils { 14 | @Rule 15 | public final TestDataRule data = new TestDataRule(); 16 | 17 | public final Strategy INTBYTES = new BytesStrategy(4); 18 | 19 | @Test 20 | public void testBidirectionalConversion() { 21 | byte[] bytes = data.draw(INTBYTES); 22 | int i = ByteUtils.intFromBytes(bytes, 0); 23 | byte[] rebytes = new byte[4]; 24 | ByteUtils.intToBytes(rebytes, 0, i); 25 | assertArrayEquals(bytes, rebytes); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/java/works/hypothesis/TestDataRuleTest.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import works.hypothesis.strategies.Strategy; 4 | import works.hypothesis.strategies.Strategies; 5 | import org.junit.Assert; 6 | import org.junit.Rule; 7 | import org.junit.Test; 8 | 9 | import java.util.List; 10 | 11 | public class TestDataRuleTest { 12 | private static final Strategy INTEGER = Strategies.integers(); 13 | private static final Strategy> INTEGERS = Strategies.lists(INTEGER); 14 | 15 | @Rule 16 | public final TestDataRule data = new TestDataRule(); 17 | 18 | @Test 19 | public void testAssociative3() { 20 | int x = data.draw(INTEGER); 21 | int y = data.draw(INTEGER); 22 | int z = data.draw(INTEGER); 23 | 24 | Assert.assertEquals((x + y) + z, x + (y + z)); 25 | } 26 | 27 | 28 | @Test 29 | public void testReversibleLists() { 30 | List xs = data.draw(INTEGERS); 31 | int sumLeft = 0; 32 | int sumRight = 0; 33 | for(int i = 0; i < xs.size(); i++){ 34 | sumLeft += xs.get(i); 35 | sumRight += xs.get(xs.size() - i - 1); 36 | } 37 | Assert.assertEquals(sumLeft, sumRight); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/works/hypothesis/TestExampleQuality.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import works.hypothesis.strategies.BytesStrategy; 4 | import works.hypothesis.strategies.Strategies; 5 | import org.junit.Test; 6 | 7 | import java.util.List; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | import static org.junit.Assert.assertTrue; 11 | 12 | public class TestExampleQuality { 13 | 14 | static int sum(List t) { 15 | int c = 0; 16 | for (final int i : t) 17 | c += i; 18 | return c; 19 | } 20 | 21 | @Test 22 | public void testLexicographicBytes() throws NoSuchExample { 23 | final byte[] data = Hypothesis.find(new BytesStrategy(1000), new HypothesisPredicate() { 24 | public boolean test(byte[] t) { 25 | int c = 0; 26 | for (final byte b : t) { 27 | if (b > 0) 28 | c++; 29 | } 30 | return c >= 100; 31 | } 32 | }); 33 | assertEquals(1000, data.length); 34 | for (int i = 0; i < 1000; i++) { 35 | if (i < 900) 36 | assertEquals(0, data[i]); 37 | else 38 | assertEquals(1, data[i]); 39 | } 40 | 41 | } 42 | 43 | @Test 44 | public void testSummingIntegers() throws NoSuchExample { 45 | final int n = 1000000; 46 | final HypothesisSettings settings = new HypothesisSettings(); 47 | settings.setDebug(true); 48 | final List result = Hypothesis.find(Strategies.lists(Strategies.integers()), 49 | new HypothesisPredicate>() { 50 | public boolean test(List t) { 51 | return sum(t) >= n; 52 | } 53 | }, settings); 54 | assertEquals(n, sum(result)); 55 | } 56 | 57 | 58 | @Test 59 | public void testFloatShrinksTowardsZero() throws NoSuchExample { 60 | float result = Hypothesis.find(new FloatRangeGenerator(0, 1), new HypothesisPredicate() { 61 | @Override 62 | public boolean test(Float value) { 63 | return value > 0; 64 | } 65 | }); 66 | assertEquals(0.0, result, 0.01); 67 | } 68 | 69 | 70 | @Test 71 | public void testNonAssociativeFloats() throws NoSuchExample { 72 | final HypothesisSettings settings = new HypothesisSettings(); 73 | settings.setMaxExamples(10000); 74 | 75 | final List result = Hypothesis.find(Strategies.lists(new FloatRangeGenerator(0, 1)), 76 | new HypothesisPredicate>() { 77 | public boolean test(List t) { 78 | if (t.size() < 3) return false; 79 | float x = t.get(0); 80 | float y = t.get(1); 81 | float z = t.get(2); 82 | float u = (x + y) + z; 83 | float v = x + (y + z); 84 | return u != v; 85 | } 86 | }, settings); 87 | assertEquals(3, result.size()); 88 | assertTrue(result.get(0) + result.get(1) + result.get(2) < 1); 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/test/java/works/hypothesis/TestFloatRange.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import works.hypothesis.strategies.BytesStrategy; 4 | import works.hypothesis.strategies.Strategy; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | import static org.junit.Assert.assertTrue; 10 | 11 | /** 12 | * Created by david on 4/9/16. 13 | */ 14 | public class TestFloatRange { 15 | @Rule 16 | public final TestDataRule data = new TestDataRule(); 17 | 18 | public final Strategy FLOATBYTES = new BytesStrategy(4); 19 | public final Strategy FLOAT01 = new FloatRangeGenerator(0, 1); 20 | 21 | 22 | @Test 23 | public void testDrawIsStable() { 24 | byte[] bytes = data.draw(FLOATBYTES); 25 | float x = new TestDataForBuffer(bytes).draw(FLOAT01); 26 | float y = new TestDataForBuffer(bytes).draw(FLOAT01); 27 | assertEquals(x, y, 0.0); 28 | } 29 | 30 | @Test 31 | public void testDrawTrivial() { 32 | float x = data.draw(FLOAT01); 33 | assertTrue(x >= 0.0); 34 | assertTrue(x <= 1.0); 35 | } 36 | 37 | @Test 38 | public void testDrawTrivialOtherRange() { 39 | float x = data.draw(new FloatRangeGenerator(10, 12)); 40 | assertTrue(x >= 10.0); 41 | assertTrue(x <= 12.0); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/works/hypothesis/TestSortingAList.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import works.hypothesis.strategies.Strategies; 6 | 7 | import java.util.Comparator; 8 | import java.util.Iterator; 9 | import java.util.List; 10 | 11 | import static org.junit.Assert.assertTrue; 12 | 13 | public class TestSortingAList { 14 | @Rule 15 | public final TestDataRule data = new TestDataRule(); 16 | 17 | @Test 18 | public void testIsSortedAfterSorting(){ 19 | List ls = data.draw(Strategies.lists(Strategies.integers())); 20 | ls.sort(Comparator.naturalOrder()); 21 | assertSorted(ls); 22 | } 23 | 24 | // Utility assertion function. Doesn't use any Hypothesis functionality. 25 | private > void assertSorted(List elements){ 26 | if(elements.isEmpty()) return; 27 | Iterator it = elements.iterator(); 28 | T previous = it.next(); 29 | while(it.hasNext()){ 30 | T current = it.next(); 31 | assertTrue(previous.compareTo(current) <= 0); 32 | previous = current; 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/works/hypothesis/TestTestData.java: -------------------------------------------------------------------------------- 1 | package works.hypothesis; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | 6 | /** 7 | * Created by david on 4/9/16. 8 | */ 9 | public class TestTestData { 10 | 11 | @Test 12 | public void testRecordsBufferDraw() { 13 | byte[] buffer = new byte[256]; 14 | for (int i = 0; i < buffer.length; i++) buffer[i] = (byte) i; 15 | TestData data = new TestDataForBuffer(buffer); 16 | Assert.assertArrayEquals(new byte[]{0}, data.drawBytes(1)); 17 | Assert.assertArrayEquals(new byte[]{1, 2}, data.drawBytes(2)); 18 | Assert.assertArrayEquals(new byte[]{3, 4, 5}, data.drawBytes(3)); 19 | Assert.assertEquals(6, data.index); 20 | data.freeze(); 21 | Assert.assertArrayEquals(new byte[]{0, 1, 2, 3, 4, 5}, data.record); 22 | } 23 | 24 | } 25 | --------------------------------------------------------------------------------