├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── main │ ├── resources │ │ └── META-INF │ │ │ └── services │ │ │ ├── org.codehaus.groovy.transform.ASTTransformation │ │ │ └── org.codehaus.groovy.runtime.ExtensionModule │ └── groovy │ │ └── spock │ │ └── genesis │ │ ├── generators │ │ ├── Permutable.groovy │ │ ├── InfiniteIterator.groovy │ │ ├── UnmodifiableIterator.groovy │ │ ├── InfiniteGenerator.groovy │ │ ├── values │ │ │ ├── NullGenerator.groovy │ │ │ ├── DoubleGenerator.groovy │ │ │ ├── ValueGenerator.groovy │ │ │ ├── CharacterGenerator.groovy │ │ │ ├── DateGenerator.groovy │ │ │ ├── WholeNumberGenerator.groovy │ │ │ ├── RandomElementGenerator.groovy │ │ │ ├── IntegerGenerator.groovy │ │ │ ├── ByteArrayGenerator.groovy │ │ │ ├── LongGenerator.groovy │ │ │ └── StringGenerator.groovy │ │ ├── FactoryGenerator.groovy │ │ ├── TransformingGenerator.groovy │ │ ├── ObjectIteratorGenerator.groovy │ │ ├── GeneratorUtils.groovy │ │ ├── LimitedGenerator.groovy │ │ ├── FilteredGenerator.groovy │ │ ├── SequentialMultisourceGenerator.groovy │ │ ├── GeneratorDecorator.groovy │ │ ├── CyclicGenerator.groovy │ │ ├── IterableGenerator.groovy │ │ ├── composites │ │ │ ├── TupleGenerator.groovy │ │ │ ├── ListGenerator.groovy │ │ │ ├── DefinedMapGenerator.groovy │ │ │ ├── PojoGenerator.groovy │ │ │ ├── RandomMapGenerator.groovy │ │ │ └── PermutationGenerator.groovy │ │ ├── MultiSourceGenerator.groovy │ │ └── Generator.groovy │ │ ├── transform │ │ ├── Iterations.groovy │ │ └── GenASTTransformation.groovy │ │ ├── extension │ │ └── ExtensionMethods.groovy │ │ └── Gen.groovy ├── test │ └── groovy │ │ └── spock │ │ └── genesis │ │ ├── generators │ │ ├── test │ │ │ ├── CloseableIterable.groovy │ │ │ └── Pojo.java │ │ ├── values │ │ │ ├── ValueGeneratorSpec.groovy │ │ │ ├── DoubleGeneratorSpec.groovy │ │ │ ├── WholeNumberGeneratorTest.groovy │ │ │ ├── RandomElementGeneratorSpec.groovy │ │ │ ├── ByteArrayGeneratorSpec.groovy │ │ │ ├── DateGeneratorSpec.groovy │ │ │ ├── WholeNumberGeneratorSpec.groovy │ │ │ ├── LongGeneratorSpec.groovy │ │ │ ├── IntegerGeneratorSpec.groovy │ │ │ └── StringGeneratorSpec.groovy │ │ ├── InfiniteGeneratorSpec.groovy │ │ ├── TransformingGeneratorSpec.groovy │ │ ├── SequentialMultisourceGeneratorSpec.groovy │ │ ├── composites │ │ │ ├── TupleGeneratorSpec.groovy │ │ │ ├── ListGeneratorSpec.groovy │ │ │ ├── PermutationGeneratorSpec.groovy │ │ │ ├── DefinedMapGeneratorSpec.groovy │ │ │ ├── RandomMapGeneratorSpec.groovy │ │ │ └── PojoGeneratorSpec.groovy │ │ ├── CyclicGeneratorSpec.groovy │ │ ├── LimitedGeneratorSpec.groovy │ │ ├── MultiSourceGeneratorSpec.groovy │ │ ├── FilteredGeneratorSpec.groovy │ │ ├── GeneratorDecoratorSpec.groovy │ │ ├── IterableGeneratorSpec.groovy │ │ └── GeneratorUtilsSpec.groovy │ │ ├── extension │ │ └── ExtensionMethodsSpec.groovy │ │ ├── transform │ │ └── IterationsSpec.groovy │ │ └── SamplesSpec.groovy └── docs │ ├── development.adoc │ ├── index.adoc │ ├── output.adoc │ ├── cardinality.adoc │ ├── intro.adoc │ ├── combine.adoc │ ├── values.adoc │ └── composites.adoc ├── circle.yml ├── config └── codenarc │ └── codenarc.groovy ├── LICENSE.txt ├── gradlew.bat ├── README.md └── gradlew /.gitignore: -------------------------------------------------------------------------------- 1 | /.README.md.html 2 | 3 | .gradle/ 4 | build/ 5 | 6 | *.log 7 | *.class 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Bijnagte/spock-genesis/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.codehaus.groovy.transform.ASTTransformation: -------------------------------------------------------------------------------- 1 | spock.genesis.transform.GenASTTransformation 2 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/test/CloseableIterable.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.test 2 | 3 | interface CloseableIterable extends Closeable, Iterable { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/Permutable.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | interface Permutable extends Iterable { 4 | Generator permute() 5 | Generator permute(int maxDepth) 6 | } 7 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule: -------------------------------------------------------------------------------- 1 | moduleName=spock-genesis-module 2 | moduleVersion=0.1.0 3 | extensionClasses=spock.genesis.extension.ExtensionMethods 4 | #staticExtensionClasses= -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon May 05 23:27:42 CDT 2014 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14-all.zip 7 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/InfiniteIterator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | abstract class InfiniteIterator extends UnmodifiableIterator { 7 | boolean hasNext() { 8 | true 9 | } 10 | abstract E next() 11 | } 12 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/UnmodifiableIterator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | abstract class UnmodifiableIterator implements Iterator { 7 | abstract boolean hasNext() 8 | abstract E next() 9 | 10 | @Override 11 | void remove() { 12 | throw new UnsupportedOperationException() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/docs/development.adoc: -------------------------------------------------------------------------------- 1 | == Development 2 | 3 | === Building `spock-genesis` 4 | 5 | The only prerequisite is that you have JDK 7 or higher installed. 6 | 7 | After cloning the project, type `./gradlew clean build` (Windows: 8 | `gradlew clean build`). All build dependencies, including Gradle 9 | itself, will be downloaded automatically (unless already present). 10 | 11 | === Groovydoc 12 | 13 | `Groovydoc` can be found link:groovydoc/index.html[here]. -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/InfiniteGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | /** 6 | * A generator whose iterator is infinite. 7 | * 8 | * @param < E > the generated type 9 | */ 10 | @CompileStatic 11 | abstract class InfiniteGenerator extends Generator { 12 | 13 | abstract InfiniteIterator iterator() 14 | 15 | boolean isFinite() { 16 | false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/values/ValueGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import spock.lang.Specification 4 | 5 | class ValueGeneratorSpec extends Specification { 6 | 7 | def 'always returns the same value'() { 8 | setup: 9 | def generator = new ValueGenerator(value) 10 | expect: 11 | generator.take(20).every { it.is(value) } 12 | where: 13 | value << [[], [some: 'map'], 1, null, new Date()] 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/values/NullGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.generators.InfiniteIterator 5 | 6 | @CompileStatic 7 | class NullGenerator extends ValueGenerator { 8 | 9 | NullGenerator() { 10 | super(null) 11 | } 12 | 13 | InfiniteIterator iterator() { 14 | new InfiniteIterator() { 15 | @Override 16 | E next() { 17 | null 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/values/DoubleGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.generators.InfiniteGenerator 5 | import spock.genesis.generators.InfiniteIterator 6 | 7 | @CompileStatic 8 | class DoubleGenerator extends InfiniteGenerator { 9 | 10 | InfiniteIterator iterator() { 11 | new InfiniteIterator() { 12 | @Override 13 | Double next() { 14 | random.nextDouble() 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/values/DoubleGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import spock.lang.Specification 4 | 5 | class DoubleGeneratorSpec extends Specification { 6 | def 'setting seed produces the same sequences for different generators' () { 7 | given: 8 | def a = new DoubleGenerator().seed(seed).take(100).realized 9 | def b = new DoubleGenerator().seed(seed).take(100).realized 10 | expect: 11 | a == b 12 | where: 13 | seed << [Long.MIN_VALUE, 100, Long.MAX_VALUE] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/docs/index.adoc: -------------------------------------------------------------------------------- 1 | = Spock-Genesis 2 | :revnumber: {releaseVersion} 3 | :numbered: 4 | :imagesDir: images/ 5 | :baseDir: ../.. 6 | :stem: 7 | 8 | :sourceDir: {baseDir}/src/main/groovy 9 | :testDir: {baseDir}/src/test/groovy 10 | 11 | Mostly lazy data generators for property based testing using the 12 | https://github.com/spockframework/spock[Spock] test framework 13 | 14 | include::intro.adoc[] 15 | 16 | include::values.adoc[] 17 | 18 | include::composites.adoc[] 19 | 20 | include::combine.adoc[] 21 | 22 | include::cardinality.adoc[] 23 | 24 | include::output.adoc[] 25 | 26 | include::development.adoc[] 27 | 28 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/values/WholeNumberGeneratorTest.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import spock.lang.Specification 4 | 5 | class WholeNumberGeneratorTest extends Specification { 6 | def 'setting seed produces the same sequences for different generators' () { 7 | given: 8 | def a = new WholeNumberGenerator().seed(seed).take(100).realized 9 | def b = new WholeNumberGenerator().seed(seed).take(100).realized 10 | expect: 11 | a == b 12 | where: 13 | seed << [Long.MIN_VALUE, 100, Long.MAX_VALUE] 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/values/ValueGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.generators.InfiniteGenerator 5 | import spock.genesis.generators.InfiniteIterator 6 | 7 | @CompileStatic 8 | class ValueGenerator extends InfiniteGenerator { 9 | 10 | final T value 11 | 12 | ValueGenerator(T value) { 13 | this.value = value 14 | } 15 | 16 | InfiniteIterator iterator() { 17 | new InfiniteIterator() { 18 | @Override 19 | T next() { 20 | value 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/InfiniteGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import spock.lang.Specification 4 | 5 | class InfiniteGeneratorSpec extends Specification { 6 | 7 | def 'is infinite'() { 8 | setup: 9 | def generator = new InfiniteGenerator() { 10 | InfiniteIterator iterator() { 11 | new InfiniteIterator() { 12 | def next() {} 13 | } 14 | } 15 | def next() {} 16 | } 17 | expect: 18 | generator.iterator().hasNext() == true 19 | generator.isFinite() == false 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/values/CharacterGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class CharacterGenerator extends RandomElementGenerator { 7 | static final String DEFAULT_CHARACTERS = (' '..'~').join('') 8 | 9 | CharacterGenerator() { 10 | super(DEFAULT_CHARACTERS.collect { it as char }) 11 | } 12 | 13 | CharacterGenerator(Collection potentialCharacters) { 14 | super(potentialCharacters) 15 | } 16 | 17 | CharacterGenerator(String potentialCharacters) { 18 | super(potentialCharacters.collect { it as char }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/values/RandomElementGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import spock.lang.Specification 4 | 5 | 6 | class RandomElementGeneratorSpec extends Specification { 7 | def'setting seed produces the same sequences for different generators' () { 8 | given: 9 | def elements = ['a', 'b', 'c', 'd'] 10 | def a = new RandomElementGenerator(elements).seed(seed).take(100).realized 11 | def b = new RandomElementGenerator(elements).seed(seed).take(100).realized 12 | expect: 13 | a == b 14 | where: 15 | seed << [Long.MIN_VALUE, 100, Long.MAX_VALUE] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/test/Pojo.java: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.test; 2 | 3 | import java.util.Objects; 4 | 5 | public final class Pojo { 6 | 7 | private String a; 8 | 9 | public String getA() { 10 | return a; 11 | } 12 | 13 | public void setA(String a) { 14 | this.a = a; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object o) { 19 | if (this == o) return true; 20 | if (o == null || getClass() != o.getClass()) return false; 21 | Pojo pojo = (Pojo) o; 22 | return Objects.equals(getA(), pojo.getA()); 23 | } 24 | 25 | @Override 26 | public int hashCode() { 27 | return Objects.hash(a); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/FactoryGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | /** 6 | * A infinite generator that returns the results of invoking a Closure 7 | * @param < T > the generated type 8 | */ 9 | @CompileStatic 10 | class FactoryGenerator extends InfiniteGenerator { 11 | 12 | final Closure factory 13 | 14 | FactoryGenerator(Closure factory) { 15 | this.factory = factory 16 | } 17 | 18 | @Override 19 | InfiniteIterator iterator() { 20 | new InfiniteIterator() { 21 | @Override 22 | T next() { 23 | factory.call() 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/TransformingGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import spock.lang.Specification 4 | 5 | class TransformingGeneratorSpec extends Specification { 6 | 7 | UnmodifiableIterator iterator = Mock() 8 | Generator supplier = Stub { 9 | iterator() >> iterator 10 | } 11 | 12 | def 'transforming generator calls closure with next value'() { 13 | setup: 14 | def transform = { val -> val + 1 } 15 | def generator = new TransformingGenerator(supplier, transform) 16 | when: 17 | def result = generator.iterator().next() 18 | then: 19 | 1 * iterator.next() >> 20 20 | result == 21 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/SequentialMultisourceGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import spock.genesis.Gen 4 | import spock.lang.Specification 5 | 6 | 7 | class SequentialMultisourceGeneratorSpec extends Specification { 8 | 9 | def 'setting seed returns the same values with 2 generators configured the same'() { 10 | given: 11 | def generatorA = new SequentialMultisourceGenerator(Gen.string(10), Gen.integer).seed(seed).take(10).realized 12 | def generatorB = new SequentialMultisourceGenerator(Gen.string(10), Gen.integer).seed(seed).take(10).realized 13 | expect: 14 | generatorA == generatorB 15 | where: 16 | seed = 100L 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | java: 3 | version: 4 | oraclejdk7 5 | 6 | test: 7 | override: 8 | - ./gradlew check 9 | post: 10 | # Archive the junit files 11 | - cp build/test-results/*.xml $CIRCLE_TEST_REPORTS/ || true 12 | 13 | # Archive the codenarc files 14 | - cp build/reports/codenarc/*.html $CIRCLE_TEST_REPORTS/ || true 15 | 16 | # Archive the jar files that were output 17 | - cp build/libs/spock-genesis-*.jar $CIRCLE_ARTIFACTS/ || true 18 | 19 | - bash <(curl -s https://codecov.io/bash) 20 | 21 | deployment: 22 | release: 23 | tag: /v[0-9]+(\.[0-9]+)*/ 24 | owner: Bijnagte 25 | commands: 26 | - ./gradlew bintrayUpload alldocs publishGhPages -PbintrayUser=$BINTRAY_USER -PbintrayApiKey=$BINTRAY_KEY 27 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/transform/Iterations.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.transform 2 | 3 | import java.lang.annotation.ElementType 4 | import java.lang.annotation.Retention 5 | import java.lang.annotation.RetentionPolicy 6 | import java.lang.annotation.Target 7 | 8 | /** 9 | * Annotation to mark Spock feature methods that should have the date providers in the where block limited. 10 | * See {@link GenASTTransformation} for implementation. 11 | */ 12 | @Retention(RetentionPolicy.SOURCE) 13 | @Target([ElementType.METHOD, ElementType.TYPE]) 14 | @interface Iterations { 15 | /** 16 | * The number of iterations to limit to or default to 100. 17 | */ 18 | int value() default 100 //the default value is not used by the AST transformation but is here for reference 19 | } 20 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/TransformingGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class TransformingGenerator extends GeneratorDecorator { 7 | private final Closure transform 8 | 9 | TransformingGenerator(Generator iterable, Closure transform) { 10 | super(iterable) 11 | this.transform = transform 12 | } 13 | 14 | UnmodifiableIterator iterator() { 15 | final Iterator ITERATOR = super.iterator() 16 | new UnmodifiableIterator() { 17 | @Override 18 | boolean hasNext() { 19 | ITERATOR.hasNext() 20 | } 21 | 22 | T next() { 23 | transform.call(ITERATOR.next()) 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/ObjectIteratorGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class ObjectIteratorGenerator extends Generator { 7 | private final Object object 8 | 9 | ObjectIteratorGenerator(object) { 10 | this.object = object 11 | } 12 | 13 | @Override 14 | UnmodifiableIterator iterator() { 15 | new UnmodifiableIterator() { 16 | final private Iterator iterator = object.iterator() 17 | @Override 18 | boolean hasNext() { 19 | iterator.hasNext() 20 | } 21 | 22 | @Override 23 | Object next() { 24 | iterator.next() 25 | } 26 | } 27 | } 28 | 29 | @Override 30 | boolean isFinite() { true } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/values/ByteArrayGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import spock.lang.Specification 4 | 5 | class ByteArrayGeneratorSpec extends Specification { 6 | 7 | def 'setting seed produces the same sequences for different generators' () { 8 | given: 9 | def a = new ByteArrayGenerator().seed(seed).take(100).realized 10 | def b = new ByteArrayGenerator(ByteArrayGenerator.DEFAULT_LENGTH_LIMIT).seed(seed).take(100).realized 11 | def c = new ByteArrayGenerator(0, ByteArrayGenerator.DEFAULT_LENGTH_LIMIT).seed(seed).take(100).realized 12 | def d = new ByteArrayGenerator((0..ByteArrayGenerator.DEFAULT_LENGTH_LIMIT) as IntRange).seed(seed).take(100).realized 13 | expect: 14 | a == b 15 | b == c 16 | c == d 17 | where: 18 | seed << [Long.MIN_VALUE, 100, Long.MAX_VALUE] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/GeneratorUtils.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileDynamic 4 | import groovy.transform.CompileStatic 5 | 6 | @CompileStatic 7 | class GeneratorUtils { 8 | 9 | static boolean anyFinite(Collection iterables) { 10 | iterables.any { isFinite(it) } 11 | } 12 | 13 | static boolean anyFinite(Iterable... iterables) { 14 | iterables.any { Iterable iterable -> isFinite(iterable) } 15 | } 16 | 17 | static boolean allFinite(Collection iterables) { 18 | iterables.every { isFinite(it) } 19 | } 20 | 21 | static boolean allFinite(Iterable... iterators) { 22 | iterators.every { Iterable iterable -> isFinite(iterable) } 23 | } 24 | 25 | @CompileDynamic 26 | static boolean isFinite(Iterable iterable) { 27 | !iterable.iterator().hasNext() || iterable.respondsTo('isFinite') && iterable.isFinite() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/values/DateGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import spock.lang.Specification 4 | 5 | class DateGeneratorSpec extends Specification { 6 | 7 | def 'generate in range'() { 8 | setup: 9 | final sample = 10 10 | when: 11 | def generator = new DateGenerator(low, high) 12 | def results = generator.take(sample).realized 13 | 14 | then: 15 | results.size() == sample 16 | results.every { it <= high && it >= low } 17 | where: 18 | low | high 19 | new Date() | new Date() + 10 20 | } 21 | 22 | def 'setting seed produces the same sequences for different generators' () { 23 | given: 24 | def a = new DateGenerator().seed(seed).take(100).realized 25 | def b = new DateGenerator().seed(seed).take(100).realized 26 | expect: 27 | a == b 28 | where: 29 | seed << [Long.MIN_VALUE, 100, Long.MAX_VALUE] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/values/DateGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.generators.InfiniteGenerator 5 | import spock.genesis.generators.InfiniteIterator 6 | 7 | @CompileStatic 8 | class DateGenerator extends InfiniteGenerator { 9 | 10 | final LongGenerator millisProvider 11 | 12 | DateGenerator() { 13 | this.millisProvider = new LongGenerator() 14 | } 15 | 16 | DateGenerator(Date minDate, Date maxDate) { 17 | this.millisProvider = new LongGenerator(minDate.time, maxDate.time) 18 | } 19 | 20 | InfiniteIterator iterator() { 21 | final Iterator TIME = millisProvider.iterator() 22 | new InfiniteIterator() { 23 | @Override 24 | Date next() { 25 | new Date(TIME.next()) 26 | } 27 | } 28 | } 29 | 30 | @Override 31 | DateGenerator seed(Long seed) { 32 | super.seed(seed) 33 | millisProvider.seed(seed) 34 | this 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /config/codenarc/codenarc.groovy: -------------------------------------------------------------------------------- 1 | ruleset { 2 | ruleset('rulesets/basic.xml') 3 | ruleset('rulesets/braces.xml') 4 | ruleset('rulesets/concurrency.xml') 5 | ruleset('rulesets/convention.xml') { 6 | NoDef(enabled: false) 7 | } 8 | ruleset('rulesets/design.xml') 9 | ruleset('rulesets/dry.xml') 10 | //ruleset('rulesets/enhanced.xml') 11 | ruleset('rulesets/exceptions.xml') 12 | ruleset('rulesets/formatting.xml') { 13 | ClassJavadoc { 14 | enabled = false 15 | } 16 | } 17 | ruleset('rulesets/generic.xml') 18 | ruleset('rulesets/groovyism.xml') 19 | ruleset('rulesets/imports.xml') 20 | ruleset('rulesets/junit.xml') 21 | ruleset('rulesets/logging.xml') 22 | ruleset('rulesets/naming.xml') 23 | ruleset('rulesets/security.xml') { 24 | InsecureRandom(enabled: false) 25 | } 26 | ruleset('rulesets/serialization.xml') 27 | ruleset('rulesets/size.xml') { 28 | CrapMetric { 29 | maxMethodCrapScore = 20 30 | maxClassAverageMethodCrapScore = 20 31 | } 32 | } 33 | ruleset('rulesets/unnecessary.xml') { 34 | UnnecessaryCollectCall(enabled: false) 35 | } 36 | ruleset('rulesets/unused.xml') 37 | } -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/LimitedGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.extension.ExtensionMethods 5 | 6 | @CompileStatic 7 | class LimitedGenerator extends GeneratorDecorator { 8 | 9 | final int iterationLimit 10 | 11 | LimitedGenerator(Iterable iterable, int iterationLimit) { 12 | super(ExtensionMethods.toGenerator(iterable)) 13 | this.iterationLimit = iterationLimit 14 | } 15 | 16 | UnmodifiableIterator iterator() { 17 | final Iterator ITERATOR = super.iterator() 18 | new UnmodifiableIterator() { 19 | private int iteration = 0 20 | 21 | @Override 22 | boolean hasNext() { 23 | ITERATOR.hasNext() && iteration < iterationLimit 24 | } 25 | 26 | @Override 27 | E next() { 28 | iteration++ 29 | ITERATOR.next() 30 | } 31 | } 32 | } 33 | 34 | @Override 35 | boolean isFinite() { 36 | true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Dylan Bijnagte 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/values/WholeNumberGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.generators.InfiniteGenerator 5 | import spock.genesis.generators.InfiniteIterator 6 | 7 | @CompileStatic 8 | class WholeNumberGenerator extends InfiniteGenerator { 9 | 10 | final int min 11 | final int magnitude 12 | 13 | WholeNumberGenerator(int max = Integer.MAX_VALUE) { 14 | this(0, max) 15 | } 16 | 17 | WholeNumberGenerator(int min, int max) { 18 | if (min < 0 || max < min) { 19 | throw new IllegalArgumentException("min $min must be 0 or greater and max $max must be greater than min") 20 | } 21 | this.min = min 22 | int magnitude = max - min 23 | this.magnitude = magnitude == Integer.MAX_VALUE ? magnitude : magnitude + 1 24 | } 25 | 26 | WholeNumberGenerator(IntRange range) { 27 | this(range.from, range.to) 28 | } 29 | 30 | InfiniteIterator iterator() { 31 | new InfiniteIterator() { 32 | @Override 33 | Integer next() { 34 | min + random.nextInt(magnitude) 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/composites/TupleGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.composites 2 | 3 | import spock.genesis.Gen 4 | import spock.lang.Specification 5 | import spock.lang.Unroll 6 | 7 | class TupleGeneratorSpec extends Specification { 8 | 9 | @Unroll 10 | def 'tuple generator makes lists from source generators'() { 11 | setup: 12 | def generator = new TupleGenerator(sources) 13 | when: 14 | def results = generator.realized 15 | then: 16 | results == expected 17 | where: 18 | sources || expected 19 | [['a', 'b'], [1, 2], [5, 6, 7]] || [['a', 1, 5], ['b', 2, 6]] 20 | ['hello!'.toGenerator(), [1, 2, 3, 4, 5]] || [['h', 1], ['e', 2], ['l', 3], ['l', 4], ['o', 5]] 21 | } 22 | 23 | def 'setting seed returns the same values with 2 generators configured the same'() { 24 | given: 25 | def generatorA = new TupleGenerator(Gen.string(10), Gen.integer).seed(seed).take(10).realized 26 | def generatorB = new TupleGenerator(Gen.string(10), Gen.integer).seed(seed).take(10).realized 27 | expect: 28 | generatorA == generatorB 29 | where: 30 | seed = 100L 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/CyclicGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import spock.lang.Specification 4 | 5 | class CyclicGeneratorSpec extends Specification { 6 | 7 | def 'generate repeats'() { 8 | setup: 9 | def generator = new CyclicGenerator(iterable).take(limit) 10 | when: 11 | def result = generator.realized 12 | then: 13 | result == expected 14 | 15 | where: 16 | limit | iterable || expected 17 | 5 | [1, 2, 3] || [1, 2, 3, 1, 2] 18 | 3 | [null] || [null, null, null] 19 | 4 | [a: 1, b: 2] || [[a: 1], [b: 2], [a: 1], [b: 2]].collect { it.entrySet().first() } 20 | 10 | 'hi!' || ['h', 'i', '!', 'h', 'i', '!', 'h', 'i', '!', 'h'] 21 | } 22 | 23 | def 'has next is false if the iterator has no entries'() { 24 | setup: 25 | def iterable = [] 26 | def generator = new CyclicGenerator(iterable) 27 | expect: 28 | generator.iterator().hasNext() == false 29 | } 30 | 31 | def 'generator is not finite'() { 32 | expect: 33 | new CyclicGenerator([1]).isFinite() == false 34 | new CyclicGenerator([]).isFinite() == true 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/values/RandomElementGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.generators.InfiniteGenerator 5 | import spock.genesis.generators.InfiniteIterator 6 | 7 | /** A lazy infinite {@code Generator} that returns a random element from a source Collection 8 | * @param < E > the generated type 9 | */ 10 | @CompileStatic 11 | class RandomElementGenerator extends InfiniteGenerator { 12 | 13 | final List elementSource 14 | final WholeNumberGenerator indexSource 15 | 16 | RandomElementGenerator(Collection elementSource) { 17 | this.elementSource = elementSource.toList().asImmutable() 18 | this.indexSource = new WholeNumberGenerator(this.elementSource.size() - 1) 19 | } 20 | 21 | @Override 22 | InfiniteIterator iterator() { 23 | new InfiniteIterator() { 24 | private final Iterator indexIterator = indexSource.iterator() 25 | 26 | @Override 27 | E next() { 28 | elementSource[indexIterator.next()] 29 | } 30 | } 31 | } 32 | 33 | @Override 34 | RandomElementGenerator seed(Long seed) { 35 | indexSource.seed(seed) 36 | super.seed(seed) 37 | this 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/LimitedGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import spock.lang.Specification 4 | 5 | class LimitedGeneratorSpec extends Specification { 6 | 7 | Generator wrapped = Mock() 8 | UnmodifiableIterator iterator = Mock() 9 | 10 | def setup() { 11 | wrapped.iterator() >> iterator 12 | } 13 | 14 | def 'limits the wrapped iterator to the length specified'() { 15 | setup: 16 | 17 | def generator = new LimitedGenerator(wrapped, limit) 18 | iterator.hasNext() >> true 19 | when: 20 | def result = generator.collect() 21 | then: 22 | limit * iterator.next() >> 'a' 23 | result.size() == limit 24 | result.every { it == 'a' } 25 | where: 26 | limit << [0, 10, 20] 27 | } 28 | 29 | def 'if the wrapped iterator runs out before the length then stop'() { 30 | setup: 31 | def generator = new LimitedGenerator(wrapped, 20) 32 | when: 33 | def result = generator.collect() 34 | then: 35 | 1 * iterator.hasNext() >> false 36 | 0 * iterator.next() 37 | result == [] 38 | } 39 | 40 | def 'is finite'() { 41 | expect: 42 | new LimitedGenerator(wrapped, 20).isFinite() == true 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/docs/output.adoc: -------------------------------------------------------------------------------- 1 | == Output 2 | Some times you need to control the output of a generator. 3 | 4 | === Seeding random generation 5 | 6 | For random generators it can be useful to control the seed for random generation. 7 | This will cause a consistent sequence of values to be generated by an equivalent generator. 8 | 9 | [source,groovy] 10 | ---- 11 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=seed,indent=0] 12 | ---- 13 | 14 | Setting the seed to different values will vary the output but will always produce the same sequence. 15 | [source,groovy] 16 | ---- 17 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=differentseed,indent=0] 18 | ---- 19 | 20 | === Using `with` 21 | 22 | You can use `with` if you would like to set some property of the 23 | generated value. 24 | 25 | IMPORTANT: `with` is different from the default groovy implementation! It always returns a new generator. 26 | 27 | [source,groovy] 28 | ---- 29 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=datewith,indent=0] 30 | ---- 31 | 32 | === Transforming with `map` 33 | 34 | Sometimes output needs to be converted to another type. the `map` method works 35 | like groovy's `collect` but will return a new generator that lazily performs the 36 | transformation. An example would be calling the `toString()` method. 37 | 38 | [source,groovy] 39 | ---- 40 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=transform,indent=0] 41 | ---- -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/values/IntegerGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.generators.InfiniteGenerator 5 | import spock.genesis.generators.InfiniteIterator 6 | 7 | @CompileStatic 8 | class IntegerGenerator extends InfiniteGenerator { 9 | 10 | final long min 11 | final long max 12 | 13 | IntegerGenerator(int min = Integer.MIN_VALUE, int max = Integer.MAX_VALUE) { 14 | assert min < max 15 | this.min = min 16 | this.max = max 17 | } 18 | 19 | IntegerGenerator(IntRange range) { 20 | this.min = range.from 21 | this.max = range.to 22 | } 23 | 24 | InfiniteIterator iterator() { 25 | new InfiniteIterator() { 26 | @Override 27 | Integer next() { 28 | long magnitude = max - min + 1 29 | if (magnitude <= Integer.MAX_VALUE) { 30 | int val = random.nextInt(magnitude as int) 31 | val + min 32 | } else { 33 | while (true) { 34 | int val = random.nextInt() 35 | if (val >= min && val <= max) { 36 | return val 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/composites/ListGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.composites 2 | 3 | import spock.genesis.Gen 4 | import spock.genesis.generators.values.IntegerGenerator 5 | import spock.genesis.generators.values.NullGenerator 6 | import spock.genesis.generators.values.StringGenerator 7 | import spock.genesis.transform.Iterations 8 | import spock.lang.Specification 9 | 10 | class ListGeneratorSpec extends Specification { 11 | 12 | def 'list generator next'() { 13 | setup: 14 | def generator = new ListGenerator(gen, limit) 15 | when: 16 | List result = generator.iterator().next() 17 | then: 18 | result.size() <= limit 19 | where: 20 | gen | limit 21 | new IntegerGenerator() | 40 22 | new StringGenerator(20) | 40 23 | new NullGenerator() | 1 24 | new IntegerGenerator() | 0 25 | } 26 | 27 | @Iterations 28 | def 'setting seed returns the same values with 2 generators configured the same'() { 29 | given: 30 | def generatorA = new ListGenerator(Gen.string(10)).seed(seed).take(10).realized 31 | def generatorB = new ListGenerator(Gen.string(10)).seed(seed).take(10).realized 32 | expect: 33 | generatorA == generatorB 34 | where: 35 | seed = 100L 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/FilteredGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.extension.ExtensionMethods 5 | 6 | /** 7 | * A lazy generator that returns the next value from the wrapped iterator that satisfies a predicate Closure. 8 | * @param < E > the generated type 9 | */ 10 | @CompileStatic 11 | class FilteredGenerator extends GeneratorDecorator { 12 | 13 | private final Closure predicate 14 | 15 | FilteredGenerator(Iterable iterable, Closure predicate) { 16 | super(ExtensionMethods.toGenerator(iterable)) 17 | this.predicate = predicate 18 | } 19 | 20 | UnmodifiableIterator iterator() { 21 | new UnmodifiableIterator() { 22 | private E nextVal 23 | boolean hasNext() { 24 | if (nextVal == null) { 25 | nextVal = findNext() 26 | } 27 | nextVal != null 28 | } 29 | 30 | E next() { 31 | if (nextVal == null) { 32 | findNext() 33 | } else { 34 | E val = nextVal 35 | nextVal = null 36 | val 37 | } 38 | } 39 | 40 | private E findNext() { 41 | generator.find(predicate) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/docs/cardinality.adoc: -------------------------------------------------------------------------------- 1 | == Cardinality 2 | 3 | Once you chose a generator, you can tell the generator to produce a 4 | given number of values, lets see how. 5 | 6 | === @Iterations 7 | 8 | If you want to limit the number of iterations that will be run you can use the 9 | `@Iterations` annotation. It is particularly useful when using infinite generators. 10 | It can be applied to the Specification or to individual features. If no value is 11 | provided it will default to 100. 12 | 13 | .Class 14 | [source,groovy] 15 | ---- 16 | include::{testDir}/spock/genesis/transform/IterationsSpec.groovy[tags=class,indent=0] 17 | ---- 18 | .Feature 19 | [source,groovy] 20 | ---- 21 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=iterations,indent=0] 22 | ---- 23 | 24 | === Once 25 | 26 | If you only want to produce a given value only once, then use `once`. 27 | 28 | [source,groovy] 29 | ---- 30 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=once,indent=0] 31 | ---- 32 | 33 | === Using `multiply by` 34 | 35 | You can tell how many items to generate from a given generator by 36 | using the `*` operator: 37 | 38 | [source,groovy] 39 | ---- 40 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=multiplyby,indent=0] 41 | ---- 42 | 43 | === Take 44 | 45 | If you know in advanced how many items you will need then use `take`. 46 | 47 | [source,groovy] 48 | ---- 49 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=take,indent=0] 50 | ---- 51 | -------------------------------------------------------------------------------- /src/docs/intro.adoc: -------------------------------------------------------------------------------- 1 | == Intro 2 | 3 | Providing test data, especially when attempting to test for a wide 4 | range of inputs is tedious if not impossible to do by hand. Generating 5 | inputs allows for more thorough testing without a dramatic increase in 6 | effort. In Spock 7 | http://spockframework.github.io/spock/docs/1.0/data_driven_testing.html[data 8 | driven tests] can have data provided by any 9 | http://docs.oracle.com/javase/7/docs/api/java/util/Iterable.html[Iterable]. Spock 10 | Genesis provides a variety of classes that extend from 11 | https://github.com/Bijnagte/spock-genesis/blob/master/src/main/groovy/spock/genesis/generators/Generator.groovy[Generator] 12 | which meet that interface. Where possible the generators are lazy and 13 | infinite. 14 | 15 | === License 16 | 17 | The *Spock-Genesis* project is open sourced under the 18 | https://github.com/Bijnagte/spock-genesis/blob/master/LICENSE.txt[MIT 19 | License]. 20 | 21 | === Usage 22 | 23 | [source,groovy] 24 | ---- 25 | repositories { 26 | jcenter() 27 | } 28 | 29 | dependencies { 30 | testCompile 'com.nagternal:spock-genesis:0.6.0' 31 | } 32 | ---- 33 | 34 | IMPORTANT: Change `0.6.0` with the latest available version (current 35 | version is {releaseVersion}) 36 | 37 | The primary way of constructing generators is 38 | https://github.com/Bijnagte/spock-genesis/blob/master/src/main/groovy/spock/genesis/Gen.groovy[spock.genesis.Gen] 39 | which provides static factory methods for data generators. 40 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/SequentialMultisourceGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class SequentialMultisourceGenerator extends Generator implements Closeable { 7 | 8 | private final List> generators 9 | 10 | SequentialMultisourceGenerator(Generator... iterables) { 11 | this.generators = iterables.toList() 12 | } 13 | 14 | UnmodifiableIterator iterator() { 15 | new UnmodifiableIterator() { 16 | private Iterator current 17 | private final Iterator iterators = generators.collect { it.iterator() }.iterator() 18 | 19 | boolean hasNext() { 20 | setupIterator() 21 | current.hasNext() 22 | } 23 | 24 | private void setupIterator() { 25 | while (!current?.hasNext() && iterators.hasNext()) { 26 | current = iterators.next() 27 | } 28 | } 29 | 30 | E next() { 31 | setupIterator() 32 | current.next() 33 | } 34 | } 35 | } 36 | 37 | void close() { 38 | generators.each { it.close() } 39 | } 40 | 41 | boolean isFinite() { 42 | generators.every { it.finite } 43 | } 44 | 45 | SequentialMultisourceGenerator seed(Long seed) { 46 | generators.each { it.seed(seed) } 47 | super.seed(seed) 48 | this 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/MultiSourceGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import spock.genesis.Gen 4 | import spock.lang.Specification 5 | 6 | class MultiSourceGeneratorSpec extends Specification { 7 | 8 | def 'multiple iterables run until all are empty'() { 9 | setup: 10 | def generator = new MultiSourceGenerator(iterables).seed(9809) 11 | def expectedCount = iterables*.size().sum() 12 | when: 13 | def result = generator.realized 14 | 15 | then: 'all of the elements were returned' 16 | result.size() == expectedCount 17 | iterables.every { list -> 18 | list.every { it in result } 19 | } 20 | and: 'the order does not match' 21 | result != iterables.flatten() 22 | where: 23 | iterables << [ 24 | [['a', 'b', 'c', 'd', 'e'], (1..100).collect()], 25 | [[null, 'b', 'c', [key: 'value']], [1, 2, 3, new Date()], (1..100).collect()] 26 | ] 27 | } 28 | 29 | def 'setting seed returns the same values with 2 generators configured the same'() { 30 | given: 31 | def generatorA = new MultiSourceGenerator([Gen.string(10), Gen.integer]).seed(seed).take(10).realized 32 | def generatorB = new MultiSourceGenerator([Gen.string(10), Gen.integer]).seed(seed).take(10).realized 33 | expect: 34 | generatorA == generatorB 35 | where: 36 | seed = 100L 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/GeneratorDecorator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | /** 6 | * Base {@link Generator} class for functionality that modifies a stream from the wrapped generator. 7 | * @param < E > the generated type 8 | */ 9 | @CompileStatic 10 | class GeneratorDecorator extends Generator implements Closeable { 11 | protected Generator generator 12 | final boolean finiteOverride 13 | 14 | GeneratorDecorator(Generator generator) { 15 | this.generator = generator 16 | this.finiteOverride = generator.finite 17 | } 18 | 19 | GeneratorDecorator(Generator generator, boolean finite) { 20 | this.generator = generator 21 | this.finiteOverride = finite 22 | } 23 | 24 | @Override 25 | UnmodifiableIterator iterator() { 26 | new UnmodifiableIterator() { 27 | final private Iterator iterator = generator.iterator() 28 | 29 | @Override 30 | boolean hasNext() { 31 | iterator.hasNext() 32 | } 33 | 34 | @Override 35 | E next() { 36 | iterator.next() 37 | } 38 | } 39 | } 40 | 41 | @Override 42 | boolean isFinite() { 43 | finiteOverride || !generator.iterator().hasNext() 44 | } 45 | 46 | void close() { 47 | generator.close() 48 | } 49 | 50 | GeneratorDecorator seed(Long seed) { 51 | generator.seed(seed) 52 | super.seed(seed) 53 | this 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/values/ByteArrayGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.generators.InfiniteGenerator 5 | import spock.genesis.generators.InfiniteIterator 6 | 7 | @CompileStatic 8 | class ByteArrayGenerator extends InfiniteGenerator { 9 | 10 | static final int DEFAULT_LENGTH_LIMIT = 1024 * 10 11 | final WholeNumberGenerator lengthSource 12 | 13 | ByteArrayGenerator() { 14 | this.lengthSource = new WholeNumberGenerator(DEFAULT_LENGTH_LIMIT) 15 | } 16 | 17 | ByteArrayGenerator(int maxLength) { 18 | this.lengthSource = new WholeNumberGenerator(maxLength) 19 | } 20 | 21 | ByteArrayGenerator(int minLength, int maxLength) { 22 | this.lengthSource = new WholeNumberGenerator(minLength, maxLength) 23 | } 24 | 25 | ByteArrayGenerator(IntRange range) { 26 | this.lengthSource = new WholeNumberGenerator(range) 27 | } 28 | 29 | @Override 30 | InfiniteIterator iterator() { 31 | new InfiniteIterator() { 32 | private final InfiniteIterator length = lengthSource.iterator() 33 | 34 | @Override 35 | byte[] next() { 36 | byte[] bytes = new byte[length.next()] 37 | random.nextBytes(bytes) 38 | bytes 39 | } 40 | } 41 | } 42 | 43 | @Override 44 | ByteArrayGenerator seed(Long seed) { 45 | lengthSource.seed(seed) 46 | super.seed(seed) 47 | this 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/values/WholeNumberGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | 6 | class WholeNumberGeneratorSpec extends Specification { 7 | def 'whole number generator for range'() { 8 | when: 9 | def numbers = new WholeNumberGenerator(min..max).take(1000) 10 | then: 11 | numbers.every { it >= min && it <= max } 12 | where: 13 | min | max 14 | 0 | Integer.MAX_VALUE 15 | 10 | 20 16 | } 17 | 18 | def 'whole number generator for min and max'() { 19 | when: 20 | def numbers = new WholeNumberGenerator(min, max).take(1000) 21 | then: 22 | numbers.every { it >= min && it <= max } 23 | where: 24 | min | max 25 | 0 | Integer.MAX_VALUE 26 | 10 | 20 27 | } 28 | 29 | def 'whole number generator for max'() { 30 | when: 31 | def numbers = new WholeNumberGenerator(max).take(1000) 32 | then: 33 | numbers.every { it >= 0 && it <= max } 34 | where: 35 | max << [1, 10, Integer.MAX_VALUE] 36 | } 37 | 38 | @Unroll 39 | def 'arguments must be positive and the correct order'() { 40 | when: 41 | new WholeNumberGenerator(*args) 42 | then: 43 | thrown(IllegalArgumentException) 44 | where: 45 | args << [ 46 | [2, 1], 47 | [-1, 10], 48 | [-1..10], 49 | [-1], 50 | ] 51 | } 52 | } -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/extension/ExtensionMethodsSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.extension 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.Gen 5 | import spock.genesis.generators.Generator 6 | import spock.lang.Specification 7 | import spock.lang.Unroll 8 | 9 | class ExtensionMethodsSpec extends Specification { 10 | 11 | @Unroll 12 | def 'to generator method on #type returns a generator'() { 13 | when: 14 | Generator gen = source.toGenerator() 15 | then: 16 | gen.realized == expected 17 | and: 'calling to generator is an identity method' 18 | gen.toGenerator().is(gen) 19 | where: 20 | type | source || expected 21 | 'List' | [1, 2, 3] || [1, 2, 3] 22 | 'Array' | [1, 2, 3].toArray() || [1, 2, 3] 23 | 'Set' | [1, 2, 3].toSet() || [1, 2, 3] 24 | 'Iterator' | [1, 2, 3].iterator() || [1, 2, 3] 25 | 'Enum' | Option || [Option.YES, Option.NO] 26 | 'non-enum Class' | Map || [Map] 27 | } 28 | 29 | enum Option { 30 | YES, NO 31 | } 32 | 33 | def 'to generator on generator returns the original generator through static method' () { 34 | expect: 35 | toGenerator(generator).is(generator) 36 | where: 37 | generator << [Gen.string, Gen.bytes, Gen.once('foo'), Gen.double, Gen.list(Gen.integer)] 38 | } 39 | 40 | @CompileStatic 41 | static Generator toGenerator(Iterable iterable) { 42 | iterable.toGenerator() 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/docs/combine.adoc: -------------------------------------------------------------------------------- 1 | == Combine 2 | 3 | When generating values, sometimes we may want to mix different types 4 | of generators, or introduce certain values in the generation, maybe 5 | special cases. In order to do that we need to combine generators, or 6 | values. 7 | 8 | === these 9 | 10 | The `Gen.these` method creates a generator from a set of values. This values 11 | could be any of: 12 | 13 | - java.util.Iterable 14 | - java.util.Collection 15 | - java.lang.Class 16 | - Or a variable arguments parameter 17 | 18 | When the generator produces new values it will be taking every element 19 | from the declared source in order until the source is exhausted 20 | 21 | [source,groovy] 22 | ---- 23 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=these,indent=0] 24 | ---- 25 | 26 | === then and `&` 27 | 28 | Lets say you are confortable with the values produced by a given 29 | generator but once the generator is exhausted it would be nice to 30 | continue producing values from another generator, that's exactly what 31 | the `then` method does. 32 | 33 | [source,groovy] 34 | ---- 35 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=then,indent=0] 36 | ---- 37 | 38 | The `then` method is available in any generator and chains one 39 | generator with the next one. 40 | 41 | Also you can use the `&` operator to combine to generators: 42 | 43 | [source,groovy] 44 | ---- 45 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=ampersand,indent=0] 46 | ---- 47 | 48 | === any 49 | 50 | If `these` was producing elements from a source in order, `Gen.any` 51 | produces values from a given source but in **random order**. 52 | 53 | [source,groovy] 54 | ---- 55 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=any,indent=0] 56 | ---- 57 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/FilteredGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import spock.lang.Specification 4 | 5 | class FilteredGeneratorSpec extends Specification { 6 | 7 | Iterator supplier = Mock() 8 | Iterable iterable = Stub { 9 | iterator() >> supplier 10 | } 11 | 12 | def 'calls supplier until a result that matches the predicate is found'() { 13 | setup: 14 | def predicate = { it == 7 } 15 | def generator = new FilteredGenerator(iterable, predicate) 16 | supplier.hasNext() >> true 17 | when: 18 | def result = generator.iterator().next() 19 | then: 20 | result == 7 21 | 3 * supplier.next() >>> [2, 4, 7, 8] 22 | } 23 | 24 | def 'all values are returned even with extra calls to has next'() { 25 | setup: 26 | def predicate = { arg -> true } 27 | def generator = new FilteredGenerator(iterable, predicate) 28 | supplier.hasNext() >> true 29 | supplier.next() >>> [2, 4, 7] 30 | def iterator = generator.iterator() 31 | when: 32 | iterator.hasNext() 33 | def result = iterator.next() 34 | then: 35 | result == 2 36 | when: 37 | iterator.hasNext() 38 | iterator.hasNext() 39 | result = iterator.next() 40 | then: 41 | result == 4 42 | } 43 | 44 | def 'has next is false if no match can be found before supplier is empty'() { 45 | setup: 46 | def predicate = { it == 1 } 47 | def supplier = [2, 3, 4] 48 | def generator = new FilteredGenerator(supplier, predicate) 49 | expect: 50 | generator.iterator().hasNext() == false 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/CyclicGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.extension.ExtensionMethods 5 | 6 | /** 7 | * A lazy infinite generator that repeats an iterator. 8 | * This generator keeps track of 1 iterator worth of data so infinite sources could lead to excessive memory usage. 9 | * @param < E > the generated type 10 | */ 11 | @CompileStatic 12 | class CyclicGenerator extends GeneratorDecorator { 13 | 14 | CyclicGenerator(E... array) { 15 | this(array.size() == 1 ? new ObjectIteratorGenerator(array[0]) : array.toList()) 16 | } 17 | 18 | CyclicGenerator(Iterable iterable) { 19 | super(ExtensionMethods.toGenerator(iterable)) 20 | } 21 | 22 | @Override 23 | UnmodifiableIterator iterator() { 24 | new UnmodifiableIterator() { 25 | private Iterator source = generator.iterator() 26 | private final List repeatSource = [] 27 | private boolean hasRepeated = false 28 | private boolean started = false 29 | 30 | @Override 31 | boolean hasNext() { 32 | if (started) { 33 | true 34 | } else { 35 | source.hasNext() 36 | } 37 | } 38 | 39 | @Override 40 | E next() { 41 | started = true 42 | if (!source.hasNext()) { 43 | hasRepeated = true 44 | source = repeatSource.iterator() 45 | } 46 | E val = source.next() 47 | if (!hasRepeated) { 48 | repeatSource.add(val) 49 | } 50 | val 51 | } 52 | } 53 | } 54 | boolean isFinite() { 55 | !iterator().hasNext() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/IterableGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileDynamic 4 | import groovy.transform.CompileStatic 5 | 6 | /** 7 | * A generator that wraps an {@link Iterable} to provide {@link Generator} methods. 8 | * @param < E > the generated type 9 | */ 10 | @CompileStatic 11 | class IterableGenerator extends Generator implements Closeable { 12 | 13 | private final Iterable iterable 14 | private final boolean finite 15 | 16 | IterableGenerator(Iterable iterable) { 17 | this.iterable = iterable 18 | this.finite = Generator.isInstance(iterable) ? false : true 19 | } 20 | 21 | IterableGenerator(Iterable iterable, boolean finite) { 22 | this.iterable = iterable 23 | this.finite = finite 24 | } 25 | 26 | IterableGenerator(E... array) { 27 | this.iterable = Arrays.asList(array) 28 | this.finite = true 29 | } 30 | 31 | @Override 32 | UnmodifiableIterator iterator() { 33 | new UnmodifiableIterator() { 34 | private final Iterator iterator = iterable.iterator() 35 | @Override 36 | boolean hasNext() { 37 | iterator.hasNext() 38 | } 39 | 40 | @Override 41 | E next() { 42 | iterator.next() 43 | } 44 | } 45 | } 46 | 47 | @Override 48 | boolean isFinite() { 49 | finite || GeneratorUtils.isFinite(iterable) 50 | } 51 | 52 | @CompileDynamic 53 | @Override 54 | void close() { 55 | if (iterable.respondsTo('close')) { 56 | iterable.close() 57 | } 58 | } 59 | 60 | @CompileDynamic 61 | @Override 62 | IterableGenerator seed(Long seed) { 63 | if (iterable.respondsTo('seed')) { 64 | iterable.seed(seed) 65 | } 66 | this 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/composites/TupleGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.composites 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.extension.ExtensionMethods 5 | import spock.genesis.generators.Generator 6 | import spock.genesis.generators.Permutable 7 | import spock.genesis.generators.UnmodifiableIterator 8 | 9 | @CompileStatic 10 | class TupleGenerator extends Generator> implements Closeable, Permutable { 11 | 12 | protected final List> generators 13 | 14 | TupleGenerator(List> iterables) { 15 | List> collector = [] 16 | iterables.collect(collector) { ExtensionMethods.toGenerator(it) } 17 | this.generators = collector.asImmutable() 18 | } 19 | 20 | TupleGenerator(Iterable... iterables) { 21 | this(iterables.toList()) 22 | } 23 | 24 | UnmodifiableIterator> iterator() { 25 | new UnmodifiableIterator>() { 26 | List iterators = generators*.iterator() 27 | 28 | @Override 29 | boolean hasNext() { 30 | iterators.every { it.hasNext() } 31 | } 32 | 33 | @Override 34 | List next() { 35 | iterators*.next() 36 | } 37 | } 38 | } 39 | 40 | PermutationGenerator permute() { 41 | new PermutationGenerator(generators) 42 | } 43 | 44 | PermutationGenerator permute(int maxDepth) { 45 | new PermutationGenerator(generators, maxDepth) 46 | } 47 | 48 | @Override 49 | boolean isFinite() { 50 | generators.every { it.finite } 51 | } 52 | 53 | @Override 54 | void close() { 55 | generators.each { it.close() } 56 | } 57 | 58 | @Override 59 | TupleGenerator seed(Long seed) { 60 | generators.each { it.seed(seed) } 61 | super.seed(seed) 62 | this 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/composites/PermutationGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.composites 2 | 3 | import spock.genesis.Gen 4 | 5 | import spock.lang.Specification 6 | 7 | class PermutationGeneratorSpec extends Specification { 8 | 9 | def 'generates all permutations'() { 10 | given: 11 | def generators = [[1, 2, 3].toGenerator(), ['a', 'b', 'c'].toGenerator()] 12 | def generator = new PermutationGenerator(generators) 13 | expect: 14 | generator.collect() == [ 15 | [1, 'a'], 16 | [2, 'a'], 17 | [3, 'a'], 18 | [1, 'b'], 19 | [2, 'b'], 20 | [3, 'b'], 21 | [1, 'c'], 22 | [2, 'c'], 23 | [3, 'c']] 24 | } 25 | 26 | def 'generates permutations'() { 27 | given: 28 | def generators = [[1, 2, 3].toGenerator(), ['a'].toGenerator()] 29 | def generator = new PermutationGenerator(generators) 30 | expect: 31 | generator.collect() == [ 32 | [1, 'a'], 33 | [2, 'a'], 34 | [3, 'a'], 35 | ] 36 | } 37 | 38 | def 'permutations are capped at the generator count root of 10000'() { 39 | given: 40 | def generators = [] 41 | generatorCount.times { generators.add(Gen.integer) } 42 | def generator = new PermutationGenerator(generators) 43 | expect: 44 | generator.collect().size() == permutations 45 | where: 46 | generatorCount | permutations 47 | 1 | 10000 48 | 2 | 10000 49 | 3 | 9261 50 | 4 | 10000 51 | 5 | 7776 52 | 6 | 4096 53 | 7 | 2187 54 | 8 | 6561 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/extension/ExtensionMethods.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.extension 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.generators.Generator 5 | import spock.genesis.generators.IterableGenerator 6 | import spock.genesis.generators.ObjectIteratorGenerator 7 | 8 | @CompileStatic 9 | class ExtensionMethods { 10 | 11 | static Generator multiply(Integer qty, Generator generator) { 12 | generator * qty 13 | } 14 | 15 | static Generator multiply(BigInteger qty, Generator generator) { 16 | generator * qty.toInteger() 17 | } 18 | 19 | static Generator toGenerator(Iterable self, boolean finite = false) { 20 | if (Generator.isInstance(self)) { 21 | (Generator) self 22 | } else { 23 | new IterableGenerator(self, finite) 24 | } 25 | } 26 | 27 | static Generator toGenerator(String self) { 28 | new ObjectIteratorGenerator(self) 29 | } 30 | 31 | static Generator toGenerator(Object self) { 32 | new ObjectIteratorGenerator(self) 33 | } 34 | 35 | static Generator> toGenerator(Map self) { 36 | new ObjectIteratorGenerator(self) 37 | } 38 | 39 | static Generator toGenerator(Collection self) { 40 | new IterableGenerator(self) 41 | } 42 | 43 | static Generator toGenerator(Class clazz) { 44 | if (clazz.isEnum()) { 45 | toGenerator(clazz.iterator().collect()) 46 | } else { 47 | toGenerator([clazz]) 48 | } 49 | } 50 | 51 | static Generator toGenerator(T... self) { 52 | new IterableGenerator(self) 53 | } 54 | 55 | static Generator toGenerator(Iterator self) { 56 | new IterableGenerator(new Iterable () { 57 | @Override 58 | Iterator iterator() { 59 | self 60 | } 61 | }) 62 | } 63 | 64 | static Generator toGenerator(Generator self) { 65 | self 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/composites/ListGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.composites 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.generators.Generator 5 | import spock.genesis.generators.UnmodifiableIterator 6 | import spock.genesis.generators.values.WholeNumberGenerator 7 | 8 | @CompileStatic 9 | class ListGenerator extends Generator> { 10 | 11 | static final int DEFAULT_LENGTH_LIMIT = 1000 12 | 13 | private final Generator valueSource 14 | private final WholeNumberGenerator lengthSource 15 | 16 | ListGenerator(Generator generator) { 17 | this.valueSource = generator 18 | this.lengthSource = new WholeNumberGenerator(DEFAULT_LENGTH_LIMIT) 19 | } 20 | 21 | ListGenerator(Generator generator, int maxLength) { 22 | this.valueSource = generator 23 | this.lengthSource = new WholeNumberGenerator(maxLength) 24 | } 25 | 26 | ListGenerator(Generator generator, int minLength, int maxLength) { 27 | this.valueSource = generator 28 | this.lengthSource = new WholeNumberGenerator(minLength, maxLength) 29 | } 30 | 31 | ListGenerator(Generator generator, IntRange range) { 32 | this.valueSource = generator 33 | this.lengthSource = new WholeNumberGenerator(range) 34 | } 35 | 36 | @Override 37 | UnmodifiableIterator> iterator() { 38 | new UnmodifiableIterator>() { 39 | private final Iterator source = valueSource.iterator() 40 | private final Iterator length = lengthSource.iterator() 41 | 42 | @Override 43 | boolean hasNext() { 44 | source.hasNext() 45 | } 46 | 47 | @Override 48 | List next() { 49 | Integer size = length.next() 50 | source.take(size).toList() 51 | } 52 | } 53 | } 54 | 55 | @Override 56 | ListGenerator seed(Long seed) { 57 | super.seed(seed) 58 | lengthSource.seed(seed) 59 | valueSource.seed(seed) 60 | this 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/values/LongGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | 6 | class LongGeneratorSpec extends Specification { 7 | 8 | 9 | def 'generate includes boundaries'() { 10 | setup: 11 | def generator = new LongGenerator(low, high) 12 | int sample = 1000 * (high - low) 13 | when: 14 | Set results = generator.take(sample).realized as Set 15 | then: 16 | results.find { it == low } 17 | results.find { it == high } 18 | results.every { it <= high && it >= low } 19 | where: 20 | low | high 21 | Long.MIN_VALUE | Long.MIN_VALUE + 1 22 | Long.MAX_VALUE - 1 | Long.MAX_VALUE 23 | -10 | 10 24 | } 25 | 26 | @Unroll 27 | def 'setting seed produces the same sequences for different generators'() { 28 | given: 29 | def generatorA = new LongGenerator(low, high).seed(1).take(100).realized 30 | def generatorB = new LongGenerator(low, high).seed(1).take(100).realized 31 | expect: 32 | generatorA == generatorB 33 | where: 34 | low | high | iterator 35 | Long.MIN_VALUE | Long.MAX_VALUE | LongGenerator.RandomLongIterator 36 | 0 | Long.MAX_VALUE | LongGenerator.RandomLongIterator 37 | -10 | 10 | LongGenerator.RandomLongIterator 38 | -10 | Long.MAX_VALUE | LongGenerator.RandomLongIterator 39 | } 40 | 41 | def 'chose correct iterator'() { 42 | given: 43 | def generator = new LongGenerator(low, high) 44 | expect: 45 | generator.chooseProvider(low, high).getClass() == iterator 46 | where: 47 | low | high | iterator 48 | Long.MIN_VALUE | Long.MAX_VALUE | LongGenerator.RandomLongIterator 49 | 0 | Long.MAX_VALUE | LongGenerator.ShiftedLongIterator 50 | -10 | 10 | LongGenerator.ShiftedIntegerIterator 51 | -10 | Long.MAX_VALUE | LongGenerator.RandomLongIterator 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/values/IntegerGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import spock.lang.Specification 4 | 5 | class IntegerGeneratorSpec extends Specification { 6 | 7 | def 'test limited generation'() { 8 | setup: 9 | def generator = new IntegerGenerator().take(limit) 10 | when: 11 | def results = generator.realized 12 | then: 13 | results.size() == limit 14 | results.every { 15 | it instanceof Integer 16 | } 17 | 18 | where: 19 | limit << [0, 20, 500, 20000] 20 | 21 | } 22 | 23 | def 'test limited range generation'() { 24 | setup: 25 | def range = max - min 26 | def generator = new IntegerGenerator(min, max).take(range * 2) 27 | when: 28 | def results = generator.realized 29 | then: 30 | results.size() == range * 2 31 | 32 | results.every { 33 | it <= max && 34 | it >= min 35 | } 36 | and: 'there are negative results in moderate samples' 37 | results.size() > 10 ? results.any { it < 0 } : true 38 | 39 | where: 40 | min | max 41 | 0 | 1 42 | -1 | 1 43 | -76080 | 8000 44 | } 45 | 46 | def 'test filter'() { 47 | setup: 48 | def generator = new IntegerGenerator().filter { it % 2 == 0 }.map { [it] }.take(2).repeat.take(limit) 49 | when: 50 | def results = generator.realized 51 | then: 52 | results.size() == limit 53 | where: 54 | limit << [0, 20] 55 | } 56 | 57 | def 'test with nulls'() { 58 | setup: 59 | int qty = perNull * 1000 60 | def targetNullRate = 1.0 / (perNull + 1) 61 | def generator = new IntegerGenerator().withNulls(perNull).take(qty) 62 | when: 63 | def results = generator.realized 64 | then: 65 | results.size() == qty 66 | and: 67 | int nulls = results.count(null) 68 | (nulls / qty) < (1.1 * targetNullRate) 69 | (nulls / qty) > (0.9 * targetNullRate) 70 | where: 71 | perNull << [5, 10] 72 | 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/composites/DefinedMapGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.composites 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.generators.Generator 5 | import spock.genesis.generators.Permutable 6 | import spock.genesis.generators.UnmodifiableIterator 7 | 8 | @CompileStatic 9 | class DefinedMapGenerator extends Generator> implements Permutable { 10 | 11 | private final List keys 12 | private final TupleGenerator valuesGenerator 13 | 14 | DefinedMapGenerator(Map> keysToValueGenerators) { 15 | List keys = [] 16 | List iterables = [] 17 | keysToValueGenerators.each { key, iterable -> 18 | keys << key 19 | iterables << iterable 20 | } 21 | this.keys = keys 22 | this.valuesGenerator = new TupleGenerator(iterables) 23 | } 24 | 25 | DefinedMapGenerator(List keys, TupleGenerator valuesGenerator) { 26 | int values = valuesGenerator.generators.size() 27 | if (keys.size() != values) { 28 | throw new IllegalArgumentException("Keys size (${keys.size()}) does not match values size ($values") 29 | } 30 | this.keys = keys.collect() //defensive copy 31 | this.valuesGenerator = valuesGenerator 32 | } 33 | 34 | @Override 35 | UnmodifiableIterator> iterator() { 36 | 37 | new UnmodifiableIterator>() { 38 | final private Iterator> iterator = valuesGenerator.iterator() 39 | 40 | @Override 41 | boolean hasNext() { 42 | iterator.hasNext() 43 | } 44 | 45 | @Override 46 | Map next() { 47 | List values = iterator.next() 48 | Map result = [:] 49 | keys.eachWithIndex { K key, int i -> 50 | result[key] = values[i] 51 | } 52 | result 53 | } 54 | } 55 | } 56 | 57 | DefinedMapGenerator permute() { 58 | new DefinedMapGenerator(keys, valuesGenerator.permute()) 59 | } 60 | 61 | DefinedMapGenerator permute(int maxDepth) { 62 | new DefinedMapGenerator(keys, valuesGenerator.permute(maxDepth)) 63 | } 64 | 65 | boolean isFinite() { 66 | valuesGenerator.finite 67 | } 68 | 69 | @Override 70 | DefinedMapGenerator seed(Long seed) { 71 | valuesGenerator.seed(seed) 72 | super.seed(seed) 73 | this 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/GeneratorDecoratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | 6 | class GeneratorDecoratorSpec extends Specification { 7 | 8 | def 'calling close closes wrapped generator'() { 9 | setup: 10 | Generator wrapped = Mock() 11 | def generator = new GeneratorDecorator(wrapped) 12 | when: 13 | generator.close() 14 | then: 15 | 1 * wrapped.close() 16 | } 17 | 18 | def 'has next delegates to wrapped generator'() { 19 | setup: 20 | UnmodifiableIterator iterator = Mock() 21 | Generator wrapped = Mock() 22 | def generator = new GeneratorDecorator(wrapped) 23 | when: 24 | generator.iterator().hasNext() 25 | then: 26 | 1 * wrapped.iterator() >> iterator 27 | 1 * iterator.hasNext() 28 | 0 * _ 29 | } 30 | 31 | def 'next delegates to wrapped generator'() { 32 | setup: 33 | UnmodifiableIterator iterator = Mock() 34 | Generator wrapped = Mock() 35 | def generator = new GeneratorDecorator(wrapped) 36 | when: 37 | generator.iterator().next() 38 | then: 39 | 1 * wrapped.iterator() >> iterator 40 | 1 * iterator.next() 41 | 0 * _ 42 | } 43 | 44 | @Unroll 45 | def 'isFinite if specified as such or wrapped iterator is finite '() { 46 | setup: 47 | def generator = overrideFinite != null ? new GeneratorDecorator<>(wrapped, overrideFinite) : new GeneratorDecorator<>(wrapped) 48 | expect: 49 | generator.isFinite() == expected 50 | where: 51 | overrideFinite | wrapped || expected 52 | true | new TestGenerator(finiteValue: false, hasNextValue: true) || true 53 | null | new TestGenerator(finiteValue: false, hasNextValue: true) || false 54 | null | new TestGenerator(finiteValue: false, hasNextValue: false) || true 55 | null | new TestGenerator(finiteValue: true, hasNextValue: true) || true 56 | } 57 | 58 | class TestGenerator extends Generator { 59 | boolean finiteValue 60 | boolean hasNextValue 61 | 62 | UnmodifiableIterator iterator() { 63 | new UnmodifiableIterator() { 64 | boolean hasNext() { hasNextValue } 65 | def next() { assert false } 66 | } 67 | } 68 | boolean isFinite() { finiteValue } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/IterableGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import spock.genesis.generators.test.CloseableIterable 4 | import spock.lang.Specification 5 | import spock.lang.Unroll 6 | 7 | class IterableGeneratorSpec extends Specification { 8 | def 'Iterable and array constructors iterate complete source'() { 9 | expect: 10 | new IterableGenerator([1, 2, 3]).realized == [1, 2, 3] 11 | new IterableGenerator([1, 2, 3].toSet()).realized == [1, 2, 3] 12 | new IterableGenerator([1, 2, 3].toArray()).realized == [1, 2, 3] 13 | } 14 | 15 | def 'calling close does nothing if wrapped generator does not have a close'() { 16 | setup: 17 | Iterable wrapped = Mock() 18 | def generator = new IterableGenerator(wrapped) 19 | when: 20 | generator.close() 21 | then: 22 | 0 * wrapped._ 23 | } 24 | 25 | def 'calling close closes wrapped iterable'() { 26 | setup: 27 | CloseableIterable wrapped = Mock() 28 | def generator = new IterableGenerator(wrapped) 29 | when: 30 | generator.close() 31 | then: 32 | 1 * wrapped.close() 33 | } 34 | 35 | @Unroll 36 | def 'isFinite if specified as such or wrapped iterator is finite '() { 37 | setup: 38 | def generator = overrideFinite != null ? new IterableGenerator(wrapped, overrideFinite) : new IterableGenerator(wrapped) 39 | expect: 40 | generator.isFinite() == expected 41 | where: 42 | overrideFinite | wrapped || expected 43 | false | [] || true 44 | null | [1] || true 45 | true | [1] || true 46 | false | [1] || false 47 | true | new TestGenerator(finiteValue: false, hasNextValue: true) || true 48 | null | new TestGenerator(finiteValue: false, hasNextValue: true) || false 49 | null | new TestGenerator(finiteValue: false, hasNextValue: false) || true 50 | null | new TestGenerator(finiteValue: true, hasNextValue: true) || true 51 | } 52 | 53 | class TestGenerator extends Generator { 54 | boolean finiteValue 55 | boolean hasNextValue 56 | 57 | UnmodifiableIterator iterator() { 58 | new UnmodifiableIterator() { 59 | boolean hasNext() { hasNextValue } 60 | def next() { assert false } 61 | } 62 | } 63 | boolean isFinite() { finiteValue } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/MultiSourceGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.extension.ExtensionMethods 5 | 6 | /** 7 | * A generator that returns the next value from one of its source generators at random. 8 | * @param < E > the generated type 9 | */ 10 | @CompileStatic 11 | class MultiSourceGenerator extends Generator implements Closeable { 12 | 13 | private final List> generators 14 | 15 | MultiSourceGenerator(Collection> iterables) { 16 | this.generators = iterables.collect { ExtensionMethods.toGenerator(it) } 17 | } 18 | 19 | MultiSourceGenerator(Map, Integer> weightedIterators) { 20 | List i = [] 21 | weightedIterators.each { iterable, qty -> 22 | def generator = ExtensionMethods.toGenerator(iterable) 23 | qty.times { i << generator } 24 | } 25 | this.generators = i 26 | } 27 | 28 | UnmodifiableIterator iterator() { 29 | new UnmodifiableIterator() { 30 | private final List> iterators = generators*.iterator() 31 | /** 32 | * @return true if any of the source generators has next 33 | */ 34 | @Override 35 | boolean hasNext() { 36 | iterators.any { it.hasNext() } 37 | } 38 | 39 | /** generates a value from one of the source generators 40 | * @return the generated value 41 | */ 42 | @Override 43 | E next() { 44 | boolean search = hasNext() 45 | 46 | while (search) { 47 | int i = random.nextInt(iterators.size()) 48 | def generator = iterators[i] 49 | if (generator.hasNext()) { 50 | return generator.next() 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | @SuppressWarnings('ExplicitCallToPlusMethod') 58 | MultiSourceGenerator plus(Iterable additional) { 59 | plus([additional]) 60 | } 61 | 62 | /** 63 | * Adds additional source generators to this generators source generators. 64 | * This method does not mutate but does reuse the source generators. 65 | * @param additional 66 | * @return a new MultiSourceGenerator 67 | */ 68 | MultiSourceGenerator plus(Collection> additional) { 69 | new MultiSourceGenerator(additional + generators) 70 | } 71 | 72 | void close() { 73 | generators.each { it.close() } 74 | } 75 | 76 | boolean isFinite() { 77 | generators.every { it.finite } 78 | } 79 | 80 | @Override 81 | MultiSourceGenerator seed(Long seed) { 82 | generators.each { it.seed(seed) } 83 | super.seed(seed) 84 | this 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/values/StringGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import spock.lang.Specification 4 | import spock.lang.Unroll 5 | 6 | class StringGeneratorSpec extends Specification { 7 | 8 | def 'test make string generation'() { 9 | setup: 10 | def generator = new StringGenerator(length, potentialCharacters).iterator() 11 | when: 12 | String result = generator.next() 13 | then: 14 | Set potential = potentialCharacters.toSet() 15 | result.size() <= length 16 | result.every { 17 | it in potential 18 | } 19 | 20 | where: 21 | length | potentialCharacters 22 | 0 | 'a' 23 | 1 | 'a' 24 | 100 | 'abc5$@' 25 | 100 | ('A'..'z').collect() 26 | } 27 | 28 | def 'default random string generation'() { 29 | setup: 30 | def generator = new StringGenerator().iterator() 31 | Set potential = CharacterGenerator.DEFAULT_CHARACTERS.toSet()*.toString() 32 | when: 33 | String result = generator.next() 34 | then: 35 | result.size() <= StringGenerator.DEFAULT_LENGTH_LIMIT 36 | result.every { 37 | it in potential 38 | } 39 | where: 40 | iteration << (0..1000) 41 | } 42 | 43 | @Unroll 44 | def 'pattern #pattern generation'() { 45 | setup: 46 | def generator = new StringGenerator(~pattern) 47 | when: 48 | Set results = generator.take(100).realized.toSet() 49 | then: 50 | results.size() > 1 51 | results.each { assert it ==~ pattern } 52 | where: 53 | pattern << [ 54 | /\d\w\d/, 55 | /\w*/, 56 | /[1-9][0-9]{2}-\d{3}-\d{4}/, 57 | /\d{3}-\d{3}-\d{4}\s(x|(ext))\d{3,5}/, 58 | '(https?|ftp|file)://[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|]\\d', 59 | /[A-Z][a-z]+( [A-Z][a-z]+)?/ 60 | ] 61 | } 62 | 63 | def 'setting seed produces the same sequences for different generators' () { 64 | given: 65 | def a = new StringGenerator().seed(seed).take(100).realized 66 | def b = new StringGenerator().seed(seed).take(100).realized 67 | expect: 68 | a == b 69 | where: 70 | seed << [Long.MIN_VALUE, 100, Long.MAX_VALUE] 71 | } 72 | 73 | def 'setting seed produces the same sequences for different generators using generex' () { 74 | given: 75 | def a = new StringGenerator(~/\d{3}-\d{3}-\d{4}\s(x|(ext))\d{3,5}/).seed(seed).take(100).realized 76 | def b = new StringGenerator(~/\d{3}-\d{3}-\d{4}\s(x|(ext))\d{3,5}/).seed(seed).take(100).realized 77 | expect: 78 | a == b 79 | where: 80 | seed << [Long.MIN_VALUE, 100, Long.MAX_VALUE] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/composites/PojoGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.composites 2 | 3 | import groovy.transform.CompileDynamic 4 | import groovy.transform.CompileStatic 5 | import spock.genesis.extension.ExtensionMethods 6 | import spock.genesis.generators.Generator 7 | import spock.genesis.generators.Permutable 8 | import spock.genesis.generators.UnmodifiableIterator 9 | 10 | import java.lang.reflect.Constructor 11 | 12 | @CompileStatic 13 | @SuppressWarnings(['Instanceof']) 14 | class PojoGenerator extends Generator implements Closeable, Permutable { 15 | final Class target 16 | final Generator generator 17 | 18 | PojoGenerator(Class target, Iterable generator) { 19 | this.target = target 20 | this.generator = ExtensionMethods.toGenerator(generator) 21 | } 22 | 23 | UnmodifiableIterator iterator() { 24 | new UnmodifiableIterator() { 25 | private final Iterator iterator = generator.iterator() 26 | 27 | @Override 28 | boolean hasNext() { 29 | iterator.hasNext() 30 | } 31 | 32 | @Override 33 | @CompileDynamic 34 | E next() { 35 | T params = iterator.next() 36 | Class clazz = params.getClass() 37 | if (hasConstructorFor(clazz)) { 38 | target.metaClass.invokeConstructor(params) 39 | } else if (List.isAssignableFrom(clazz)) { 40 | target.newInstance(*params) 41 | } else if (Map.isAssignableFrom(clazz)) { 42 | target.newInstance(params) 43 | } 44 | } 45 | 46 | private boolean hasConstructorFor(Class clazz) { 47 | target.constructors.any { Constructor constructor -> 48 | constructor.parameterTypes.length == 1 && 49 | constructor.parameterTypes[0].isAssignableFrom(clazz) 50 | } 51 | } 52 | } 53 | } 54 | 55 | boolean isFinite() { 56 | generator.finite 57 | } 58 | 59 | void close() { 60 | generator.close() 61 | } 62 | 63 | @Override 64 | PojoGenerator seed(Long seed) { 65 | generator.seed(seed) 66 | super.seed(seed) 67 | this 68 | } 69 | 70 | PojoGenerator permute() { 71 | if (generator instanceof Permutable) { 72 | new PojoGenerator(target, ((Permutable) generator).permute()) 73 | } else { 74 | throw new UnsupportedOperationException("generator of type ${generator.getClass()} is not permutable") 75 | } 76 | } 77 | 78 | PojoGenerator permute(int maxDepth) { 79 | if (generator instanceof Permutable) { 80 | new PojoGenerator(target, ((Permutable) generator).permute(maxDepth)) 81 | } else { 82 | throw new UnsupportedOperationException("generator of type ${generator.getClass()} is not permutable") 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/composites/RandomMapGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.composites 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.extension.ExtensionMethods 5 | import spock.genesis.generators.Generator 6 | import spock.genesis.generators.UnmodifiableIterator 7 | import spock.genesis.generators.values.WholeNumberGenerator 8 | 9 | @CompileStatic 10 | class RandomMapGenerator extends Generator> implements Closeable { 11 | 12 | static final int DEFAULT_ENTRY_LIMIT = 100 13 | final WholeNumberGenerator sizeSource 14 | final Generator keyGenerator 15 | final Generator valueGenerator 16 | 17 | RandomMapGenerator(Iterable keyGenerator, Iterable valueGenerator) { 18 | this(keyGenerator, valueGenerator, new WholeNumberGenerator(DEFAULT_ENTRY_LIMIT)) 19 | } 20 | 21 | RandomMapGenerator(Iterable keyGenerator, Iterable valueGenerator, int maxSize) { 22 | this(keyGenerator, valueGenerator, new WholeNumberGenerator(maxSize)) 23 | } 24 | 25 | RandomMapGenerator(Iterable keyGenerator, Iterable valueGenerator, int minSize, int maxSize) { 26 | this(keyGenerator, valueGenerator, new WholeNumberGenerator(minSize, maxSize)) 27 | } 28 | 29 | RandomMapGenerator(Iterable keyGenerator, Iterable valueGenerator, IntRange sizeRange) { 30 | this(keyGenerator, valueGenerator, new WholeNumberGenerator(sizeRange)) 31 | } 32 | 33 | RandomMapGenerator(Iterable keyGenerator, Iterable valueGenerator, WholeNumberGenerator sizeSource) { 34 | this.sizeSource = sizeSource 35 | this.keyGenerator = ExtensionMethods.toGenerator(keyGenerator) 36 | this.valueGenerator = ExtensionMethods.toGenerator(valueGenerator) 37 | } 38 | 39 | UnmodifiableIterator> iterator() { 40 | new UnmodifiableIterator>() { 41 | Iterator keys = keyGenerator.iterator() 42 | Iterator values = valueGenerator.iterator() 43 | Iterator sizes = sizeSource.iterator() 44 | 45 | @Override 46 | boolean hasNext() { 47 | keys.hasNext() && values.hasNext() 48 | } 49 | 50 | @Override 51 | Map next() { 52 | int targetSize = sizes.next() 53 | int i = 0 54 | Map result = [:] 55 | while (result.size() < targetSize && hasNext()) { 56 | result[keys.next()] = values.next() 57 | i++ 58 | } 59 | result 60 | } 61 | } 62 | } 63 | 64 | @Override 65 | void close() { 66 | keyGenerator.close() 67 | valueGenerator.close() 68 | } 69 | 70 | @Override 71 | boolean isFinite() { 72 | keyGenerator.finite || valueGenerator.finite 73 | } 74 | 75 | @Override 76 | RandomMapGenerator seed(Long seed) { 77 | keyGenerator.seed(seed) 78 | valueGenerator.seed(seed) 79 | sizeSource.seed(seed) 80 | super.seed(seed) 81 | this 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/transform/IterationsSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.transform 2 | 3 | import spock.genesis.Gen 4 | import spock.lang.Specification 5 | import spock.lang.Stepwise 6 | 7 | 8 | @Stepwise 9 | // tag::class[] 10 | @Iterations(5) 11 | class IterationsSpec extends Specification { 12 | 13 | static List NUMBERS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] 14 | 15 | static List VALUES = [] 16 | 17 | def 'method iterations is limited by class annotation'() { 18 | expect: 19 | value < 6 20 | where: 21 | value << NUMBERS 22 | } 23 | // end::class[] 24 | 25 | @Iterations(2) 26 | def 'method annotation less than class annotation'() { 27 | expect: 28 | value < 3 29 | where: 30 | value << NUMBERS 31 | } 32 | 33 | @Iterations(7) 34 | def 'method annotation more than class annotation'() { 35 | given: 36 | VALUES.add(value) 37 | expect: 38 | value < 8 39 | where: 40 | value << NUMBERS 41 | } 42 | 43 | def 'VALUES has 7 entries at this point'() { 44 | expect: 45 | VALUES.size() == 7 46 | when: 'reset' 47 | VALUES.clear() 48 | then: 49 | VALUES.size() == 0 50 | } 51 | 52 | @Iterations(2) 53 | def 'annotation works for data tables'() { 54 | expect: 55 | value == expected 56 | where: 57 | value | expected 58 | true | true 59 | true | true 60 | true | false //not reachable 61 | } 62 | 63 | @Iterations(2) 64 | def 'annotation works for multiple assignment'() { 65 | expect: 66 | value == expected 67 | value < 3 68 | where: 69 | [value, expected] << Gen.tuple(NUMBERS, NUMBERS) 70 | } 71 | 72 | @Iterations(2) 73 | def 'annotation works for multiple data providers'() { 74 | expect: 75 | value == expected 76 | value < 3 77 | where: 78 | value << NUMBERS 79 | expected << NUMBERS 80 | } 81 | 82 | @Iterations(4) 83 | def 'fixed value in where does not affect the iterations'() { 84 | given: 85 | VALUES.add(value) 86 | expect: 87 | value < 5 88 | where: 89 | other = 9 90 | value << NUMBERS 91 | } 92 | 93 | def 'VALUES has 4 entries at this point'() { 94 | expect: 95 | VALUES.size() == 4 96 | when: 'reset' 97 | VALUES.clear() 98 | then: 99 | VALUES.size() == 0 100 | } 101 | 102 | @Iterations 103 | def 'no value sets iterations to 100'() { 104 | given: 105 | VALUES.add(value) 106 | expect: 107 | value < 101 108 | where: 109 | value << (1..200) 110 | } 111 | 112 | def 'VALUES has 100 entries at this point'() { 113 | expect: 114 | VALUES.size() == 100 115 | when: 'reset' 116 | VALUES.clear() 117 | then: 118 | VALUES.size() == 0 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/composites/DefinedMapGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.composites 2 | 3 | import spock.genesis.Gen 4 | import spock.genesis.generators.values.IntegerGenerator 5 | import spock.genesis.generators.values.NullGenerator 6 | import spock.genesis.generators.values.StringGenerator 7 | import spock.lang.Specification 8 | 9 | class DefinedMapGeneratorSpec extends Specification { 10 | 11 | def 'generate map with generator per key'() { 12 | setup: 13 | def date = new Date() 14 | def generators = [ 15 | 1 : new IntegerGenerator(), 16 | string: new StringGenerator(20), 17 | (date): new NullGenerator() 18 | ] 19 | def generator = new DefinedMapGenerator(generators) 20 | when: 21 | def result = generator.iterator().next() 22 | then: 23 | result.keySet() == [1, date, 'string'].toSet() 24 | result[1] instanceof Integer 25 | result['string'] instanceof String 26 | result[date] == null 27 | } 28 | 29 | def 'has next is false if one field generator does not have next'() { 30 | setup: 31 | def generators = [ 32 | 1 : new IntegerGenerator(), 33 | string: new StringGenerator(20), 34 | empty : [] 35 | ] 36 | def generator = new DefinedMapGenerator(generators) 37 | expect: 38 | generator.iterator().hasNext() == false 39 | } 40 | 41 | def 'isFinite'() { 42 | expect: 43 | new DefinedMapGenerator(generators).finite == expected 44 | 45 | where: 46 | generators || expected 47 | [a: new IntegerGenerator()] || false 48 | [a: []] || true 49 | [a: new IntegerGenerator().take(1)] || true 50 | } 51 | 52 | def 'setting seed returns the same values with 2 generators configured the same'() { 53 | given: 54 | def generatorA = new DefinedMapGenerator(a: Gen.string(10), b: Gen.integer).seed(seed).take(10).realized 55 | def generatorB = new DefinedMapGenerator(a: Gen.string(10), b: Gen.integer).seed(seed).take(10).realized 56 | expect: 57 | generatorA == generatorB 58 | where: 59 | seed = 100L 60 | } 61 | 62 | def 'generates all permutations'() { 63 | given: 64 | def mapDefinition = [int: [1, 2, 3], string:['a', 'b', 'c']] 65 | def generator = new DefinedMapGenerator(mapDefinition).permute() 66 | expect: 67 | generator.collect() == [ 68 | [int: 1, string: 'a'], 69 | [int: 2, string: 'a'], 70 | [int: 3, string: 'a'], 71 | [int: 1, string: 'b'], 72 | [int: 2, string: 'b'], 73 | [int: 3, string: 'b'], 74 | [int: 1, string: 'c'], 75 | [int: 2, string: 'c'], 76 | [int: 3, string: 'c']] 77 | } 78 | 79 | def 'generates permutations'() { 80 | given: 81 | def mapDefinition = [int: [1, 2, 3], string: ['a']] 82 | def generator = new DefinedMapGenerator(mapDefinition).permute() 83 | expect: 84 | generator.collect() == [ 85 | [int: 1, string: 'a'], 86 | [int: 2, string: 'a'], 87 | [int: 3, string: 'a'], 88 | ] 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/values/LongGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.generators.Generator 5 | import spock.genesis.generators.InfiniteGenerator 6 | import spock.genesis.generators.InfiniteIterator 7 | 8 | @CompileStatic 9 | class LongGenerator extends InfiniteGenerator { 10 | 11 | final Long min 12 | final Long max 13 | private final IntegerGenerator integerGenerator 14 | 15 | LongGenerator() { 16 | this.min = Long.MIN_VALUE 17 | this.max = Long.MAX_VALUE 18 | } 19 | 20 | LongGenerator(long min, long max) { 21 | assert min < max 22 | this.min = min 23 | this.max = max 24 | def magnitude = magnitude(min, max) 25 | if (magnitude <= Integer.MAX_VALUE) { 26 | integerGenerator = new IntegerGenerator(0, magnitude as int) 27 | } 28 | } 29 | 30 | InfiniteIterator chooseProvider(long min, long max) { 31 | BigInteger magnitude = magnitude(min, max) 32 | if (magnitude <= Integer.MAX_VALUE) { 33 | new ShiftedIntegerIterator(min as long) 34 | } else if (magnitude <= Long.MAX_VALUE) { 35 | new ShiftedLongIterator(magnitude as long, min as long) 36 | } else { 37 | new RandomLongIterator() 38 | } 39 | } 40 | 41 | private static BigInteger magnitude(long min, long max) { 42 | max.toBigInteger() - min.toBigInteger() 43 | } 44 | 45 | InfiniteIterator iterator() { 46 | final InfiniteIterator CANDIDATE_PROVIDER = chooseProvider(min, max) 47 | new InfiniteIterator() { 48 | @Override 49 | Long next() { 50 | while (true) { 51 | Long val = CANDIDATE_PROVIDER.next() 52 | if (val >= min && val <= max) { 53 | return val 54 | } 55 | } 56 | } 57 | } 58 | } 59 | 60 | class RandomLongIterator extends InfiniteIterator { 61 | Long next() { 62 | random.nextLong() 63 | } 64 | } 65 | 66 | class ShiftedLongIterator extends InfiniteIterator { 67 | final long magnitude 68 | final long shift 69 | 70 | ShiftedLongIterator(long magnitude, long shift) { 71 | this.magnitude = magnitude 72 | this.shift = shift 73 | } 74 | 75 | Long next() { 76 | // error checking and 2^x checking removed for simplicity. 77 | long bits 78 | long val 79 | 80 | boolean seek = true 81 | while (seek) { 82 | bits = (random.nextLong() << 1) >>> 1 83 | val = bits % magnitude 84 | seek = bits - val + (magnitude - 1) < 0L 85 | } 86 | shift + val 87 | } 88 | } 89 | 90 | class ShiftedIntegerIterator extends InfiniteIterator { 91 | final InfiniteIterator iterator 92 | final long shift 93 | 94 | ShiftedIntegerIterator(long shift) { 95 | iterator = integerGenerator.iterator() 96 | this.shift = shift 97 | } 98 | 99 | Long next() { 100 | shift + iterator.next() 101 | } 102 | } 103 | 104 | @Override 105 | Generator seed(Long seed) { 106 | super.seed(seed) 107 | integerGenerator?.seed(seed) 108 | this 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/docs/values.adoc: -------------------------------------------------------------------------------- 1 | == Values 2 | 3 | Values could be of any simple type such as a String, Integer, 4 | Byte...etc Before using any built-in generator remember to add the 5 | following import: 6 | 7 | [source,groovy] 8 | ---- 9 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=genimport,indent=0] 10 | ---- 11 | 12 | Then you should be able to generate a simple value from the available 13 | generators: 14 | 15 | [source,groovy] 16 | ---- 17 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=factorymethods,indent=0] 18 | ---- 19 | 20 | These examples are creating only the next available generated value 21 | from the corresponding generator. This way of using simple types 22 | generators doesn't put any constraint to the generated value apart 23 | from generate a specific type of value. We'll see later on how to add 24 | some boundaries to some of the value generators. For the time being, 25 | if for example, we would like a string we won't care about the length 26 | of the string. 27 | 28 | === Strings 29 | 30 | Like we saw in the previous section if we don't care about the length 31 | or the content and we just wanted to generate a string then it's 32 | enough to call `Gen.getString()` or in a more `groovier` way 33 | `Gen.string` 34 | 35 | **By length** 36 | 37 | In case we wanted to restrict the generated word length, we could use 38 | one of the following methods: 39 | 40 | [source,groovy] 41 | .Restricting string length 42 | ---- 43 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=stringlength,indent=0] 44 | ---- 45 | 46 | <1> Establishing the maximum string length 47 | <2> Establishing both minimum and maximum length 48 | 49 | **By pattern** 50 | 51 | [source,groovy] 52 | .From a string pattern 53 | ---- 54 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=stringregex,indent=0] 55 | ---- 56 | 57 | === Numbers 58 | 59 | `spock.genesis.Gen` has access to several number generators. All basic 60 | number types have a direct method to generate a random value without 61 | establishing any restriction. 62 | 63 | [source,groovy] 64 | ---- 65 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=numbers,indent=0] 66 | ---- 67 | 68 | For integers there are a couple more methods to set the boundaries of 69 | the generated values directly. 70 | 71 | [source,groovy] 72 | ---- 73 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=integerlength,indent=0] 74 | ---- 75 | 76 | <1> Using a Groovy range to establish min and max boundaries 77 | <2> Establishing min and max using two parameters 78 | 79 | === Date 80 | 81 | In many applications could be handy to generate dates to validate some 82 | principles. For instance when booking a room to make sure the system 83 | doesn't accept any check-out done the same date or before as the 84 | check-in right ? 85 | 86 | [source,groovy] 87 | ---- 88 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=dates,indent=0] 89 | ---- 90 | 91 | === From value 92 | 93 | You've seen several ways of creating values from simple data 94 | types. But if you still wanted to create an infinite lazy generator 95 | for a given value you can use `Gen.value` 96 | 97 | [source,groovy] 98 | ---- 99 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=value,indent=0] 100 | ---- 101 | 102 | === From enum 103 | 104 | If you already have an enum type and you would like to generate random values 105 | from it, then you could use `these`: 106 | 107 | [source,groovy] 108 | ---- 109 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=fromenum,indent=0] 110 | ---- 111 | 112 | NOTE: `these` in general can generate values taken from a given 113 | source, in this case the source is an enum. To know more about `these` 114 | check the section `combine`. 115 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/composites/PermutationGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.composites 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.generators.Generator 5 | import spock.genesis.generators.Permutable 6 | import spock.genesis.generators.UnmodifiableIterator 7 | 8 | @CompileStatic 9 | class PermutationGenerator extends TupleGenerator implements Permutable { 10 | 11 | static final int MAX_PERMUTATIONS = 10000 12 | private final int maxDepth 13 | 14 | PermutationGenerator(List> generators, int maxDepth) { 15 | super(generators) 16 | this.maxDepth = maxDepth 17 | } 18 | 19 | PermutationGenerator(List> generators) { 20 | this(generators, pickMaxDepth(generators)) 21 | } 22 | 23 | @Override 24 | UnmodifiableIterator> iterator() { 25 | final List> ITERATORS = generators.collect { generator -> 26 | generator.take(maxDepth).iterator() 27 | } 28 | 29 | new UnmodifiableIterator>() { 30 | private final List> emitted = [] 31 | private final List> currentEmmited = [] 32 | private int column = 0 33 | private E value 34 | private Iterator> emittedIterator 35 | 36 | @Override 37 | boolean hasNext() { 38 | if (!emitted) { 39 | //first 40 | ITERATORS.every { it.hasNext() } 41 | } else if (emittedIterator?.hasNext() || ITERATORS[column].hasNext()) { 42 | true 43 | } else { 44 | //find a column that still has values to generate 45 | while (!ITERATORS[column].hasNext() && column + 1 < ITERATORS.size()) { 46 | column++ 47 | } 48 | if (ITERATORS[column].hasNext()) { 49 | emittedIterator = null 50 | emitted.addAll(currentEmmited) // the previous columns emitted tuples for substitution 51 | currentEmmited.clear() 52 | true 53 | } else { 54 | false 55 | } 56 | } 57 | } 58 | 59 | @Override 60 | List next() { 61 | if (emitted) { 62 | if (!emittedIterator?.hasNext()) { // done with the value 63 | value = ITERATORS[column].next() // get the next value 64 | emittedIterator = emitted.iterator() 65 | } 66 | 67 | List result = emittedIterator.next().collect() // copy the next previously emitted tuple 68 | result[column] = value // substitute the value 69 | currentEmmited << result // keep track of values for this column 70 | result.collect() 71 | } else { //first 72 | List result = ITERATORS.collect { it.next() } 73 | emitted << result 74 | result.collect() 75 | } 76 | } 77 | } 78 | } 79 | 80 | private static int pickMaxDepth(List> generators) { 81 | Math.floor(nthRoot(MAX_PERMUTATIONS, generators.size())) 82 | } 83 | 84 | private static double nthRoot(int base, int exponent) { 85 | double tolerance = 0.01 86 | double approximation = 1 87 | double root 88 | while (true) { 89 | root = ((exponent - 1) * approximation + base / (approximation) ** (exponent - 1)) / exponent 90 | if ((root - approximation).abs() < tolerance) { 91 | break 92 | } 93 | approximation = root 94 | } 95 | root 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/Generator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.extension.ExtensionMethods 5 | import spock.genesis.generators.values.NullGenerator 6 | 7 | /** 8 | * An Iterator that generates a type (usually lazily) 9 | * @param < E > the generated type 10 | */ 11 | @CompileStatic 12 | abstract class Generator implements Iterable, Closeable { 13 | protected final Random random = new Random() 14 | 15 | /** 16 | * Wraps this generator in a generator that returns values that matches the supplied predicate 17 | * @param predicate 18 | * @return a FilteredGenerator 19 | */ 20 | FilteredGenerator filter(Closure predicate) { 21 | new FilteredGenerator(this, predicate) 22 | } 23 | 24 | @SuppressWarnings(['UnnecessaryPublicModifier']) // needed for generic parsing issue 25 | public TransformingGenerator map(Closure transform) { 26 | new TransformingGenerator(this, transform) 27 | } 28 | 29 | TransformingGenerator with(Closure transform) { 30 | Closure withClosure = { generatedValue -> 31 | generatedValue.with(transform) 32 | generatedValue 33 | } 34 | new TransformingGenerator(this, withClosure) 35 | } 36 | 37 | LimitedGenerator take(int qty) { 38 | new LimitedGenerator(this, qty) 39 | } 40 | 41 | SequentialMultisourceGenerator then(Iterable... iterables) { 42 | Generator[] all = new Generator[iterables.length + 1] 43 | all[0] = this 44 | for (int i = 0; i < iterables.length; i++) { 45 | all[i + 1] = ExtensionMethods.toGenerator(iterables[i]) 46 | 47 | } 48 | new SequentialMultisourceGenerator(all) 49 | } 50 | 51 | CyclicGenerator repeat() { 52 | new CyclicGenerator(this) 53 | } 54 | 55 | LimitedGenerator multiply(int qty) { 56 | take(qty) 57 | } 58 | 59 | CyclicGenerator getRepeat() { 60 | repeat() 61 | } 62 | 63 | MultiSourceGenerator getWithNulls() { 64 | withNulls(100) 65 | } 66 | 67 | /**Wraps this generator in a {@link spock.genesis.generators.MultiSourceGenerator} that randomly returns nulls 68 | * @param resultsPerNull the average number of results from this generator per null result 69 | * @return {@link spock.genesis.generators.MultiSourceGenerator} 70 | */ 71 | @SuppressWarnings('SpaceAroundMapEntryColon') 72 | MultiSourceGenerator withNulls(int resultsPerNull) { 73 | Map weightedGenerators = [(this): resultsPerNull, (new NullGenerator()): 1] 74 | new MultiSourceGenerator(weightedGenerators) 75 | } 76 | 77 | MultiSourceGenerator and(Iterable iterable) { 78 | if (MultiSourceGenerator.isAssignableFrom(this.getClass())) { 79 | ((MultiSourceGenerator) this) + iterable 80 | } else { 81 | new MultiSourceGenerator([this, iterable]) 82 | } 83 | } 84 | 85 | abstract UnmodifiableIterator iterator() 86 | 87 | /** 88 | * If false then the generator may still terminate when iterated 89 | * @return true if the Generator will terminate 90 | */ 91 | boolean isFinite() { 92 | !iterator().hasNext() 93 | } 94 | 95 | List getRealized() { 96 | this.collect().asList() 97 | } 98 | 99 | @SuppressWarnings('EmptyMethodInAbstractClass') 100 | void close() { } 101 | 102 | /** 103 | * Set the {@link Random} seed for this generator and all contained generators. 104 | * This method mutates the generator! 105 | * @param seed 106 | * @return this generator 107 | */ 108 | Generator seed(Long seed) { 109 | random.setSeed(seed) 110 | this 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/generators/values/StringGenerator.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.values 2 | 3 | import com.mifmif.common.regex.Generex 4 | import groovy.transform.CompileStatic 5 | import spock.genesis.generators.InfiniteGenerator 6 | import spock.genesis.generators.InfiniteIterator 7 | 8 | import java.util.regex.Pattern 9 | 10 | /** 11 | * lazy infinite {@link java.lang.String} generator 12 | */ 13 | @CompileStatic 14 | class StringGenerator extends InfiniteGenerator { 15 | 16 | static final int DEFAULT_LENGTH_LIMIT = 1024 17 | 18 | private final CharacterGenerator charGenerator 19 | private final WholeNumberGenerator lengthSource 20 | private final Generex generex 21 | 22 | StringGenerator() { 23 | this.lengthSource = new WholeNumberGenerator(DEFAULT_LENGTH_LIMIT) 24 | this.charGenerator = new CharacterGenerator() 25 | } 26 | 27 | StringGenerator(int maxLength) { 28 | this.lengthSource = new WholeNumberGenerator(maxLength) 29 | this.charGenerator = new CharacterGenerator() 30 | } 31 | 32 | StringGenerator(int minLength, int maxLength) { 33 | this.lengthSource = new WholeNumberGenerator(minLength, maxLength) 34 | this.charGenerator = new CharacterGenerator() 35 | } 36 | 37 | StringGenerator(String potentialCharacters) { 38 | this.lengthSource = new WholeNumberGenerator(DEFAULT_LENGTH_LIMIT) 39 | this.charGenerator = new CharacterGenerator(potentialCharacters) 40 | } 41 | 42 | StringGenerator(int maxLength, String potentialCharacters) { 43 | this.lengthSource = new WholeNumberGenerator(maxLength) 44 | this.charGenerator = new CharacterGenerator(potentialCharacters) 45 | } 46 | 47 | StringGenerator(int minLength, int maxLength, String potentialCharacters) { 48 | this.lengthSource = new WholeNumberGenerator(minLength, maxLength) 49 | this.charGenerator = new CharacterGenerator(potentialCharacters) 50 | } 51 | 52 | StringGenerator(Collection potentialCharacters) { 53 | this.lengthSource = new WholeNumberGenerator(DEFAULT_LENGTH_LIMIT) 54 | this.charGenerator = new CharacterGenerator(potentialCharacters) 55 | } 56 | 57 | StringGenerator(int maxLength, Collection potentialCharacters) { 58 | this.lengthSource = new WholeNumberGenerator(maxLength) 59 | this.charGenerator = new CharacterGenerator(potentialCharacters) 60 | } 61 | 62 | StringGenerator(int minLength, int maxLength, Collection potentialCharacters) { 63 | this.lengthSource = new WholeNumberGenerator(minLength, maxLength) 64 | this.charGenerator = new CharacterGenerator(potentialCharacters) 65 | } 66 | 67 | StringGenerator(Pattern regex) { 68 | generex = new Generex(regex.pattern()) 69 | } 70 | 71 | InfiniteIterator iterator() { 72 | new InfiniteIterator() { 73 | private final InfiniteIterator charIterator = charGenerator?.iterator() 74 | private final InfiniteIterator length = lengthSource?.iterator() 75 | 76 | @Override 77 | String next() { 78 | if (charIterator) { 79 | makeString(length.next()) 80 | } else { 81 | generex.random() 82 | } 83 | } 84 | 85 | private String makeString(int length) { 86 | def sb = new StringBuffer(length) 87 | for (int i = 0; i < length; i++) { 88 | sb.append(charIterator.next()) 89 | } 90 | sb.toString() 91 | } 92 | } 93 | } 94 | 95 | @Override 96 | StringGenerator seed(Long seed) { 97 | super.seed(seed) 98 | charGenerator?.seed(seed) 99 | lengthSource?.seed(seed) 100 | generex?.setSeed(seed) 101 | this 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/GeneratorUtilsSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators 2 | 3 | import spock.genesis.generators.values.IntegerGenerator 4 | import spock.lang.Specification 5 | import spock.lang.Unroll 6 | 7 | /** 8 | * Created by dylan on 13/01/16. 9 | */ 10 | class GeneratorUtilsSpec extends Specification { 11 | 12 | @Unroll 13 | def 'all iterator is finite or empty'() { 14 | expect: 15 | GeneratorUtils.allFinite(iterables) == expected 16 | where: 17 | iterables || expected 18 | [['a', 'b'], [1, 2], [5, 6, 7]] || false 19 | [[]] || true 20 | [[], [1, 2, 3, 4, 5]] || false 21 | [new IterableGenerator([1, 2, 3, 4, 5])] || true 22 | [new IterableGenerator([1, 2, 3, 4, 5]), []] || true 23 | [new IntegerGenerator(), []] || false 24 | } 25 | 26 | @Unroll 27 | def 'all iterator as varargs is finite or empty'() { 28 | expect: 29 | GeneratorUtils.allFinite(*iterables) == expected 30 | where: 31 | iterables || expected 32 | [['a', 'b'], [1, 2], [5, 6, 7]] || false 33 | [[], [1, 2, 3, 4, 5]] || false 34 | [new IterableGenerator([1, 2, 3, 4, 5])] || true 35 | [new IterableGenerator([1, 2, 3, 4, 5]), []] || true 36 | [new IntegerGenerator(), []] || false 37 | } 38 | 39 | @Unroll 40 | def 'any iterator is finite or empty'() { 41 | expect: 42 | GeneratorUtils.anyFinite(iterators) == expected 43 | where: 44 | iterators || expected 45 | [['a', 'b'], [1, 2], [5, 6, 7]] || false 46 | [[]] || true 47 | [[], [1, 2, 3, 4, 5]] || true 48 | [new IterableGenerator([1, 2, 3, 4, 5])] || true 49 | [new IterableGenerator([1, 2, 3, 4, 5]), []] || true 50 | [new IntegerGenerator(), []] || true 51 | } 52 | 53 | @Unroll 54 | def "any iterator as var args is finite or empty"() { 55 | expect: 56 | GeneratorUtils.anyFinite(*iterators) == expected 57 | where: 58 | iterators || expected 59 | [['a', 'b'], [1, 2], [5, 6, 7]] || false 60 | [[], [1, 2, 3, 4, 5]] || true 61 | [new IterableGenerator([1, 2, 3, 4, 5])] || true 62 | [new IterableGenerator([1, 2, 3, 4, 5]), []] || true 63 | [new IntegerGenerator(), []] || true 64 | } 65 | 66 | 67 | @Unroll 68 | def 'is finite iterator'() { 69 | expect: 70 | GeneratorUtils.isFinite([]) == true 71 | GeneratorUtils.isFinite([1]) == false 72 | } 73 | 74 | @Unroll 75 | def 'is finite implements Generator'() { 76 | setup: 77 | //anonymous class issue with where variables 78 | def has = hasNext 79 | def finite = isFinite 80 | 81 | def generator = new Generator() { 82 | UnmodifiableIterator iterator() { 83 | new UnmodifiableIterator() { 84 | boolean hasNext() { has } 85 | def next() {} 86 | } 87 | } 88 | boolean isFinite() { finite } 89 | } 90 | expect: 91 | GeneratorUtils.isFinite(generator) == expected 92 | where: 93 | hasNext | isFinite || expected 94 | true | true || true 95 | false | true || true 96 | true | false || false 97 | false | false || true 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/composites/RandomMapGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.composites 2 | 3 | import spock.genesis.Gen 4 | import spock.genesis.generators.Generator 5 | import spock.genesis.generators.test.CloseableIterable 6 | import spock.genesis.generators.values.IntegerGenerator 7 | import spock.genesis.generators.values.StringGenerator 8 | import spock.genesis.transform.Iterations 9 | import spock.lang.Specification 10 | 11 | class RandomMapGeneratorSpec extends Specification { 12 | 13 | def 'generates keys and values of the expected types'() { 14 | setup: 15 | def generator = new RandomMapGenerator(new StringGenerator(10), new IntegerGenerator()) 16 | when: 17 | Map result = generator.iterator().next() 18 | then: 19 | result.each { key, value -> 20 | key instanceof String 21 | value instanceof Integer 22 | } 23 | } 24 | 25 | def 'calling close does nothing if the generators have no close method'() { 26 | setup: 27 | Iterable keyGenerator = Mock() 28 | Iterable valueGenerator = Mock() 29 | 30 | def generator = new RandomMapGenerator(keyGenerator, valueGenerator) 31 | when: 32 | generator.close() 33 | then: 34 | 0 * keyGenerator._ 35 | 0 * valueGenerator._ 36 | } 37 | 38 | def 'calling close calls close on key and value generators'() { 39 | setup: 40 | CloseableIterable keyGenerator = Mock() 41 | CloseableIterable valueGenerator = Mock() 42 | def generator = new RandomMapGenerator(keyGenerator, valueGenerator) 43 | when: 44 | generator.close() 45 | then: 46 | 1 * keyGenerator.close() 47 | 1 * valueGenerator.close() 48 | } 49 | 50 | @Iterations 51 | def 'generates map that is limited to max size'() { 52 | expect: 53 | result instanceof Map 54 | result.size() <= 100 55 | result.size() >= 0 56 | result.every { key, value -> 57 | key instanceof String && value instanceof Integer 58 | } 59 | where: 60 | result << new RandomMapGenerator(Gen.string(10), Gen.integer, 100) 61 | } 62 | 63 | @Iterations 64 | def 'generates map that is limited to min and max size'() { 65 | expect: 66 | result instanceof Map 67 | result.size() <= 100 68 | result.size() >= 90 69 | result.every { key, value -> 70 | key instanceof String && value instanceof Integer 71 | } 72 | where: 73 | result << new RandomMapGenerator(Gen.string(10), Gen.integer, 90, 100) 74 | } 75 | 76 | @Iterations 77 | def 'generates map that is limited to size range'() { 78 | expect: 79 | result instanceof Map 80 | result.size() <= 100 81 | result.size() >= 90 82 | result.every { key, value -> 83 | key instanceof String && value instanceof Integer 84 | } 85 | where: 86 | result << new RandomMapGenerator(Gen.string(10), Gen.integer, 90..100) 87 | } 88 | 89 | def 'close closes sources'() { 90 | given: 91 | Generator keys = Mock() 92 | Generator values = Mock() 93 | def generator = new RandomMapGenerator(keys, values, 10) 94 | when: 95 | generator.close() 96 | then: 97 | 1 * keys.close() 98 | 1 * values.close() 99 | 0 * _ 100 | } 101 | 102 | def 'setting seed returns the same values with 2 generators configured the same'() { 103 | given: 104 | def generatorA = new RandomMapGenerator(Gen.string(10), Gen.integer, 10..20).seed(seed).take(10).realized 105 | def generatorB = new RandomMapGenerator(Gen.string(10), Gen.integer, 10..20).seed(seed).take(10).realized 106 | expect: 107 | generatorA == generatorB 108 | where: 109 | seed = 100L 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Spock Genesis 2 | =============== 3 | Mostly lazy data generators for property based testing using the [Spock](http://spockframework.org) test framework 4 | 5 | Providing test data, especially when attempting to test for a wide range of inputs is tedious if not impossible to do by hand. 6 | Generating inputs allows for more thorough testing without a dramatic increase in effort. 7 | In Spock [data driven tests] (http://spock-framework.readthedocs.org/en/latest/data_driven_testing.html#data-pipes) can have data provided by any [Iterator](http://docs.oracle.com/javase/7/docs/api/java/util/Iterator.html). 8 | Spock Genesis provides a variety of classes that extend from [Generator](./src/main/groovy/spock/genesis/generators/Generator.groovy) which meet that interface. 9 | Where possible the generators are lazy and infinite. 10 | 11 | [![build status](https://circleci.com/gh/Bijnagte/spock-genesis.svg?style=shield&circle-token=ccab052d8c597ae916463f8319738d89e3d8a640)]() 12 | [![codecov](https://codecov.io/gh/Bijnagte/spock-genesis/branch/master/graph/badge.svg)](https://codecov.io/gh/Bijnagte/spock-genesis) 13 | [![JCenter](https://api.bintray.com/packages/dylanbijnagte/nagternal/spock-genesis/images/download.svg) ](https://bintray.com/dylanbijnagte/nagternal/spock-genesis/_latestVersion) 14 | [![Maven Central](https://img.shields.io/maven-central/v/com.nagternal/spock-genesis.svg?maxAge=2592000)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22com.nagternal%22%20AND%20a%3A%22spock-genesis%22) 15 | 16 | Usage 17 | ----- 18 | From gradle: 19 | 20 | ```groovy 21 | repositories { 22 | jcenter() 23 | } 24 | 25 | dependencies { 26 | testCompile 'com.nagternal:spock-genesis:0.6.0' 27 | } 28 | ``` 29 | 30 | The primary way of constructing generators is [spock.genesis.Gen](./src/main/groovy/spock/genesis/Gen.groovy) which provides static factory methods for data generators. 31 | 32 | ```groovy 33 | @Unroll 34 | def 'test reverse #string'() { 35 | when: 36 | def reversed = string.reverse() 37 | 38 | then: 39 | reversed.size() == string.size() 40 | if (string) { 41 | string.eachWithIndex { letter, i -> 42 | letter == reversed[-(i + 1)] 43 | } 44 | } 45 | reversed.reverse() == string 46 | 47 | where: 48 | string << Gen.these('', 'foo').then(Gen.string).take(10000) 49 | } 50 | ``` 51 | 52 | Given a Person class create a generator that can supply instances: 53 | 54 | ```groovy 55 | Gen.type(Person, 56 | id: Gen.integer(200..10000), 57 | name: Gen.string(~/[A-Z][a-z]+( [A-Z][a-z]+)?/), 58 | birthDate: Gen.date(Date.parse('MM/dd/yyyy','01/01/1940'), new Date()), 59 | title: Gen.these('', null).then(Gen.any('Dr.', 'Mr.', 'Ms.', 'Mrs.')), 60 | gender: Gen.character('MFTU')) 61 | ``` 62 | 63 | Documentation 64 | ------------- 65 | 66 | Check Spock-Genesis documentation is 67 | [here](https://Bijnagte.github.io/spock-genesis) or see 68 | [SamplesSpec](./src/test/groovy/spock/genesis/SamplesSpec.groovy) for 69 | more examples 70 | 71 | Change log 72 | ---------- 73 | ### 0.2.0 74 | * Update dependencies 75 | 76 | ### 0.3.0 77 | * Add support for using regular expressions for String generation. Thanks to Generex 78 | * Using Groovy constructor selection for single arg Pojo construction 79 | 80 | ### 0.4.0 81 | * improve toGenerator extension methods 82 | * better error handling for POJO construction 83 | * isFinite method to determine if generator will terminate 84 | 85 | ### 0.5.0 86 | * switch to Iterable from Iterator 87 | * improve performance of long generation using min and/or max 88 | * improved documentation (thanks to @mariogarcia) 89 | * [@Iterations](./src/main/groovy/spock/genesis/transform/Iterations.groovy) annotation 90 | * updated dependencies 91 | * ability to set the seed of random generators 92 | 93 | ### 0.6.0 94 | * permute to generate combinations from composite generators 95 | 96 | Building Spock Genesis 97 | -------------- 98 | The only prerequisite is that you have JDK 7 or higher installed. 99 | 100 | After cloning the project, type `./gradlew clean build` (Windows: `gradlew clean build`). All build dependencies, 101 | including [Gradle](http://www.gradle.org) itself, will be downloaded automatically (unless already present). 102 | 103 | Resources 104 | --------- 105 | * Spock Homepage -- http://spockframework.org 106 | * GitHub -- https://github.com/Bijnagte/spock-genesis 107 | * Generex -- https://github.com/mifmif/Generex 108 | -------------------------------------------------------------------------------- /src/docs/composites.adoc: -------------------------------------------------------------------------------- 1 | == Composites 2 | 3 | Generate values of basic types is great but sometimes we may want to 4 | create instances of more complex types such as maps, lists, or pojos. 5 | 6 | === Tuple 7 | 8 | A tuple is a finite ordered list of elements. As we'll see afterwards 9 | you can create random sized lists with the `list` generator, but if 10 | you only wanted to create a fixed sized lists with fixed types, 11 | `tuple` could be your best option. 12 | 13 | [source,groovy] 14 | .Tuple 15 | ---- 16 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=tuple,indent=0] 17 | ---- 18 | 19 | In this example we're creating a tuple (a fixed list) of three elments 20 | of types: Integer, Integer and String. 21 | 22 | === List 23 | 24 | As we've just seen you can create fixed lists with `tuple` but if you 25 | want to vary the size of the list, or use random element types then 26 | you should be using `list`. A list needs a given value generator to 27 | take its elements from and could have some size constraints like the 28 | minimum or/and maximum number of elements. A basic list generator 29 | without any size boundaries: 30 | 31 | [source,groovy] 32 | .Simple list 33 | ---- 34 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=simplelist,indent=0] 35 | ---- 36 | 37 | <1> `list(valueGenerator)` in this case we are using the `integer` 38 | generator to create values of type `Integer` 39 | 40 | This example generates random sized lists with values taken from the 41 | value generator passed as parameter. 42 | 43 | **List length** 44 | 45 | On the other hand if you want to establish some size boundaries you 46 | could use `list(valueGenerator,min,max)` or 47 | `list(valueGenerator,max)`. 48 | 49 | [source,groovy] 50 | .List with size boundaries 51 | ---- 52 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=sizedlist,indent=0] 53 | ---- 54 | 55 | <1> Creating a list with a minimum size of 1 and a maximum of 5 56 | <2> It should be an instance of list 57 | <3> It should have a minimum size of 1 58 | <4> It should have a maximum size of 5 59 | <5> All elements should be of type integer 60 | 61 | === Map 62 | 63 | You can create instances of `java.util.Map` and also specify which 64 | type of values should be used per `key-value` entry. 65 | 66 | [source,groovy] 67 | .Map 68 | ---- 69 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=mapgenerator,indent=0] 70 | ---- 71 | 72 | <1> Declaring a `map` generator 73 | <2> Declaring `id` will be a long 74 | <3> Declaring `name` will be a string 75 | <4> Declaring `age` will be an integer between 0 and 120. Then we get 76 | `next()` map generated value 77 | 78 | === Type 79 | 80 | Given a class we may want to create a generator that can supply 81 | instances of that type. Here is an example of a given class: 82 | 83 | [source,groovy] 84 | .Data 85 | ---- 86 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=data,indent=0] 87 | ---- 88 | 89 | The following generator creates instances of the previous type and it 90 | has defined a different generator for each field: 91 | 92 | [source,groovy] 93 | .Generator 94 | ---- 95 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=typegenerator,indent=0] 96 | ---- 97 | 98 | <1> Create generator 99 | <2> Take next instance 100 | 101 | In the following example the type we would like to get instances from 102 | has a non default constructor: 103 | 104 | [source,groovy] 105 | .TupleData 106 | ---- 107 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=tupledata,indent=0] 108 | ---- 109 | 110 | But that is not an issue, as long as we respect the number of 111 | arguments after declaring the class. 112 | 113 | [source,groovy] 114 | .Generator 115 | ---- 116 | include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=typegenerator2,indent=0] 117 | ---- 118 | 119 | NOTE: Notice here we are generating the same name for the `i` field 120 | over and over again (42) 121 | 122 | 123 | === Combinations with `permute` 124 | Under some conditions you may want to test all combinations of inputs. Tuple, map with fixed keys, and type generators all 125 | implement the 126 | https://github.com/Bijnagte/spock-genesis/blob/master/src/main/groovy/spock/genesis/generators/Permutable.groovy[Permutable] 127 | interface to accomplish that in a lazy fashion. 128 | 129 | IMPORTANT: Iterating through all combinations expands the number of iterations exponentially. 130 | 131 | Outputs are produced using a depth first algorithm. Setting the max depth limits the number of values that are produced for 132 | each input. 133 | 134 | [source,groovy] 135 | .using a map generated POJO 136 | ---- 137 | include::{testDir}/spock/genesis/generators/composites/PojoGeneratorSpec.groovy[tags=mapgenerator,indent=0] 138 | ---- 139 | 140 | <1> Declaring a generator 141 | <2> Call `permute` and get all of the values 142 | <3> The result contains all of the combinations 143 | <4> Call `permute` setting a max depth of 2 and get all the values 144 | <5> The result only contains the combinations of the first 2 values from each field 145 | 146 | If no max depth is specified then the formula stem:[ |__root(n)(10000)__| ] is used to determine the max depth 147 | 148 | .number of iterations 149 | |=== 150 | | input count | depth | iterations 151 | | 1 | 10000 | 10000 152 | | 2 | 100 | 10000 153 | | 3 | 21 | 9261 154 | | 4 | 10 | 10000 155 | | 5 | 6 | 7776 156 | | 6 | 4 | 4096 157 | | 7 | 3 | 2187 158 | | 8 | 3 | 6561 159 | |=== 160 | 161 | Groovy provides a http://docs.groovy-lang.org/latest/html/api/groovy/util/GroovyCollections.html#combinations(java.lang.Iterable)[combinations] method that provides a similar capability but it is not lazy and does not control for the 162 | exponential issue. -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/transform/GenASTTransformation.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.transform 2 | 3 | import groovy.transform.CompileStatic 4 | import org.codehaus.groovy.ast.ASTNode 5 | import org.codehaus.groovy.ast.AnnotatedNode 6 | import org.codehaus.groovy.ast.AnnotationNode 7 | import org.codehaus.groovy.ast.ClassHelper 8 | import org.codehaus.groovy.ast.ClassNode 9 | import org.codehaus.groovy.ast.MethodNode 10 | import org.codehaus.groovy.ast.ModuleNode 11 | import org.codehaus.groovy.ast.expr.ArgumentListExpression 12 | import org.codehaus.groovy.ast.expr.ConstantExpression 13 | import org.codehaus.groovy.ast.expr.ConstructorCallExpression 14 | import org.codehaus.groovy.ast.expr.Expression 15 | import org.codehaus.groovy.ast.stmt.BlockStatement 16 | import org.codehaus.groovy.ast.stmt.ReturnStatement 17 | import org.codehaus.groovy.control.CompilePhase 18 | import org.codehaus.groovy.control.SourceUnit 19 | import org.codehaus.groovy.transform.ASTTransformation 20 | import org.codehaus.groovy.transform.GroovyASTTransformation 21 | import org.spockframework.compiler.AstNodeCache 22 | import spock.genesis.generators.LimitedGenerator 23 | 24 | /** 25 | * Global AST transformation to modify data providers after Spock compilation 26 | */ 27 | @CompileStatic 28 | @GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION) 29 | @SuppressWarnings(['Instanceof', 'CrapMetric']) 30 | class GenASTTransformation implements ASTTransformation { 31 | 32 | final static AstNodeCache NODE_CACHE = new AstNodeCache() 33 | final static ClassNode ITERATIONS_ANNOTATION = ClassHelper.makeWithoutCaching(Iterations) 34 | final static ClassNode LIMITED_CLASS_GENERATOR = ClassHelper.makeWithoutCaching(LimitedGenerator) 35 | 36 | @Override 37 | void visit(ASTNode[] nodes, SourceUnit sourceUnit) { 38 | ModuleNode module = (ModuleNode) nodes[0] 39 | List classes = module.classes 40 | 41 | for (ClassNode clazz in classes) { 42 | if (isSpec(clazz)) { 43 | def classIterationsAnnotation = findIterationsAnnotation(clazz) 44 | Integer classIterations 45 | if (classIterationsAnnotation) { 46 | classIterations = getNumberOfIterations(classIterationsAnnotation) 47 | } 48 | Map featureIterations = getFeatureIterations(clazz, classIterations) 49 | 50 | if (featureIterations) { 51 | modifyDataProviders(clazz, featureIterations) 52 | } 53 | } 54 | } 55 | } 56 | 57 | private Map getFeatureIterations(ClassNode clazz, Integer classIterations) { 58 | Map featureIterations = [:] 59 | for (MethodNode method in clazz.methods) { 60 | if (isFeature(method)) { 61 | Integer methodIterations = classIterations 62 | def iterationsAnnotation = findIterationsAnnotation(method) 63 | if (iterationsAnnotation) { 64 | methodIterations = getNumberOfIterations(iterationsAnnotation) 65 | } 66 | if (methodIterations != null) { 67 | featureIterations.put(method.name, methodIterations) 68 | } 69 | } 70 | } 71 | featureIterations 72 | } 73 | 74 | private void modifyDataProviders(ClassNode clazz, Map featureIterations) { 75 | Set modifyFeatureNames = featureIterations.keySet() 76 | 77 | for (MethodNode method in clazz.methods) { 78 | if (isDataProvider(method)) { 79 | String featureName = getFeatureName(method.name) 80 | if (modifyFeatureNames.contains(featureName)) { 81 | int limit = featureIterations.get(featureName) 82 | if (method.code instanceof BlockStatement) { 83 | BlockStatement code = (BlockStatement) method.code 84 | if (code.statements[0] instanceof ReturnStatement) { 85 | // all data providers should be only a single return statement but making sure no exceptions 86 | // get thrown getting to this point 87 | ReturnStatement returnStatement = (ReturnStatement) code.statements[0] 88 | returnStatement.expression = wrapWithLimitedGenerator(returnStatement.expression, limit) 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | @SuppressWarnings(['UnnecessarySubstring']) 97 | private String getFeatureName(String dataProviderName) { 98 | // spock names methods the same as the feature with prov and a number appended 99 | // feature: $spock_feature_0_9 100 | // 1st provider: $spock_feature_0_9prov0 101 | // 2nd provider: $spock_feature_0_9prov1 102 | int featureEnd = dataProviderName.indexOf('prov') 103 | if (featureEnd != -1) { 104 | dataProviderName.substring(0, featureEnd) 105 | } 106 | } 107 | 108 | private AnnotationNode findAnnotation(ClassNode annotation, AnnotatedNode annotatedNode) { 109 | annotatedNode.annotations.find { it.classNode.isDerivedFrom(annotation) } 110 | } 111 | 112 | private boolean isDataProvider(MethodNode method) { 113 | //spock annotates the methods created from the where block with DataProviderMetadata 114 | findAnnotation(NODE_CACHE.DataProviderMetadata, method) 115 | } 116 | 117 | private boolean isFeature(MethodNode method) { 118 | //spock annotates the feature methods with FeatureMetadata 119 | findAnnotation(NODE_CACHE.FeatureMetadata, method) 120 | } 121 | 122 | private AnnotationNode findIterationsAnnotation(AnnotatedNode annotatedNode) { 123 | findAnnotation(ITERATIONS_ANNOTATION, annotatedNode) 124 | } 125 | 126 | private boolean isSpec(ClassNode clazz) { 127 | clazz.isDerivedFrom(NODE_CACHE.Specification) 128 | } 129 | 130 | private Integer getNumberOfIterations(AnnotationNode iterationsAnnotation) { 131 | if (iterationsAnnotation) { 132 | def value = iterationsAnnotation.getMember('value') 133 | if (value instanceof ConstantExpression) { 134 | ConstantExpression valueExpression = (ConstantExpression) value 135 | (Integer) valueExpression.value 136 | } else { 137 | 100 138 | } 139 | } 140 | } 141 | 142 | private static Expression wrapWithLimitedGenerator(Expression expression, int limit) { 143 | def args = new ArgumentListExpression(expression, new ConstantExpression(limit)) 144 | new ConstructorCallExpression(LIMITED_CLASS_GENERATOR, args) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/generators/composites/PojoGeneratorSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis.generators.composites 2 | 3 | import groovy.transform.EqualsAndHashCode 4 | import groovy.transform.ToString 5 | import spock.genesis.Gen 6 | import spock.genesis.generators.test.Pojo 7 | import spock.lang.Specification 8 | 9 | class PojoGeneratorSpec extends Specification { 10 | 11 | def 'has next true if param generator has next'() { 12 | setup: 13 | def generator = new PojoGenerator(null, iterable) 14 | expect: 15 | iterable.iterator().hasNext() == generator.iterator().hasNext() 16 | where: 17 | iterable << [[1], []] 18 | } 19 | 20 | def 'make tuple constructor object'() { 21 | setup: 22 | def tuple = [string, integer] 23 | def generator = new PojoGenerator(TupleConstructorObj, [tuple]) 24 | 25 | when: 26 | def result = generator.iterator().next() 27 | then: 28 | result instanceof TupleConstructorObj 29 | result.string == string 30 | result.integer == integer 31 | where: 32 | string | integer 33 | 'A' | 0 34 | null | 0 35 | 'B' | null 36 | } 37 | 38 | def 'make map constructor object'() { 39 | setup: 40 | def map = [string: string, integer: integer] 41 | def generator = new PojoGenerator(MapConstructorObj, [map]) 42 | when: 43 | def result = generator.iterator().next() 44 | then: 45 | result instanceof MapConstructorObj 46 | result.string == string 47 | result.integer == integer 48 | where: 49 | string | integer 50 | 'A' | 0 51 | null | 0 52 | 'B' | null 53 | 54 | } 55 | 56 | def 'make default constructor object'() { 57 | setup: 58 | def map = [string: string, integer: integer] 59 | def generator = new PojoGenerator(DefaultConstructorObj, [map]) 60 | when: 61 | def result = generator.iterator().next() 62 | then: 63 | result instanceof DefaultConstructorObj 64 | result.string == string 65 | result.integer == integer 66 | where: 67 | string | integer 68 | 'A' | 0 69 | null | 0 70 | 'B' | null 71 | } 72 | 73 | def 'make default constructor final java object'() { 74 | setup: 75 | def map = [a: 'A'] 76 | def generator = new PojoGenerator(Pojo, [map]) 77 | when: 78 | Pojo result = generator.iterator().next() 79 | then: 80 | result.a == 'A' 81 | } 82 | 83 | def 'make default constructor object with extra arg in map'() { 84 | setup: 85 | def map = [a: 'A', i: 1] 86 | def generator = new PojoGenerator(Pojo, [map]) 87 | when: 88 | generator.iterator().next() 89 | then: 90 | def ex = thrown(MissingPropertyException) 91 | ex.message.contains('No such property: i for class') 92 | } 93 | 94 | def 'make single arg constructor object'() { 95 | setup: 96 | def generator = new PojoGenerator(SingleArgConstructorObj, [arg]) 97 | when: 98 | def result = generator.iterator().next() 99 | then: 100 | result instanceof SingleArgConstructorObj 101 | if (arg instanceof String) { 102 | result.string == arg 103 | } else if (arg instanceof Integer) { 104 | result.integer == arg 105 | } else { 106 | assert false 107 | } 108 | where: 109 | arg << ['A', 1, 'Test', 200] 110 | } 111 | 112 | def 'make var args constructor object succeeds'() { 113 | setup: 114 | Integer[] args = [10, 1, 77, 200] 115 | def generator = new PojoGenerator(VarArgsConstructorObj, [args]) 116 | when: 117 | def result = generator.iterator().next() 118 | then: 119 | result instanceof VarArgsConstructorObj 120 | result.args == args 121 | } 122 | 123 | def 'make var args constructor object succeeds if array wrapped in a list'() { 124 | setup: 125 | Integer[] args = [10, 1, 77, 200] 126 | def argList = [args] 127 | def generator = new PojoGenerator(VarArgsConstructorObj, [argList]) 128 | when: 129 | def result = generator.iterator().next() 130 | then: 131 | result instanceof VarArgsConstructorObj 132 | result.args == args 133 | } 134 | 135 | def 'setting seed returns the same values with 2 generators configured the same'() { 136 | setup: 137 | def generatorA = new PojoGenerator(Pojo, Gen.map(a: Gen.string)).seed(seed).take(10).realized 138 | def generatorB = new PojoGenerator(Pojo, Gen.map(a: Gen.string)).seed(seed).take(10).realized 139 | expect: 140 | generatorA == generatorB 141 | where: 142 | seed = 100L 143 | 144 | } 145 | 146 | // tag::mapgenerator[] 147 | def 'permute is possible with map generator'() { 148 | setup: 149 | def generator = Gen.type(MapConstructorObj, string: ['a', 'b'], integer: [1,2,3]) // <1> 150 | when: 151 | List results = generator.permute().collect() // <2> 152 | then: // <3> 153 | results == [ 154 | new MapConstructorObj(string: 'a', integer: 1), 155 | new MapConstructorObj(string: 'b', integer: 1), 156 | new MapConstructorObj(string: 'a', integer: 2), 157 | new MapConstructorObj(string: 'b', integer: 2), 158 | new MapConstructorObj(string: 'a', integer: 3), 159 | new MapConstructorObj(string: 'b', integer: 3), 160 | ] 161 | when: 'only to depth of 2' 162 | List results2 = generator.permute(2).collect() // <4> 163 | then: // <5> 164 | results2 == [ 165 | new MapConstructorObj(string: 'a', integer: 1), 166 | new MapConstructorObj(string: 'b', integer: 1), 167 | new MapConstructorObj(string: 'a', integer: 2), 168 | new MapConstructorObj(string: 'b', integer: 2), 169 | ] 170 | } 171 | // end::mapgenerator[] 172 | 173 | def 'permute is possible with tuple generator'() { 174 | setup: 175 | def generator = new PojoGenerator(TupleConstructorObj, Gen.tuple(['a', 'b'], [1, 2, 3])) 176 | expect: 177 | generator.permute().collect() == [ 178 | new TupleConstructorObj('a', 1), 179 | new TupleConstructorObj('b', 1), 180 | new TupleConstructorObj('a', 2), 181 | new TupleConstructorObj('b', 2), 182 | new TupleConstructorObj('a', 3), 183 | new TupleConstructorObj('b', 3), 184 | ] 185 | and: 'only to depth of 2' 186 | generator.permute(2).collect() == [ 187 | new TupleConstructorObj('a', 1), 188 | new TupleConstructorObj('b', 1), 189 | new TupleConstructorObj('a', 2), 190 | new TupleConstructorObj('b', 2), 191 | ] 192 | } 193 | 194 | def 'permute does not work on iterable'() { 195 | setup: 196 | def generator = new PojoGenerator(TupleConstructorObj, [['a', 1], ['b', 2]]) 197 | when: 198 | generator.permute() 199 | then: 200 | thrown(UnsupportedOperationException) 201 | when: 202 | generator.permute(1) 203 | then: 204 | thrown(UnsupportedOperationException) 205 | } 206 | 207 | @EqualsAndHashCode 208 | @ToString 209 | static class TupleConstructorObj { 210 | String string 211 | Integer integer 212 | 213 | TupleConstructorObj(String string, Integer integer) { 214 | this.string = string 215 | this.integer = integer 216 | } 217 | } 218 | 219 | @EqualsAndHashCode 220 | @ToString 221 | static class MapConstructorObj { 222 | String string 223 | Integer integer 224 | 225 | MapConstructorObj(Map args) { 226 | string = args.string 227 | integer = args.integer 228 | } 229 | } 230 | 231 | static class VarArgsConstructorObj { 232 | Integer[] args 233 | 234 | VarArgsConstructorObj(Integer... args) { 235 | this.args = args 236 | } 237 | } 238 | 239 | static class DefaultConstructorObj { 240 | String string 241 | Integer integer 242 | } 243 | 244 | static class SingleArgConstructorObj { 245 | String string 246 | Integer integer 247 | 248 | SingleArgConstructorObj(String string) { 249 | this.string = string 250 | } 251 | 252 | SingleArgConstructorObj(Integer integer) { 253 | this.integer = integer 254 | } 255 | } 256 | } -------------------------------------------------------------------------------- /src/test/groovy/spock/genesis/SamplesSpec.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis 2 | 3 | import groovy.transform.Immutable 4 | import spock.genesis.extension.ExtensionMethods 5 | import spock.genesis.generators.Generator 6 | import spock.genesis.generators.MultiSourceGenerator 7 | import spock.genesis.transform.Iterations 8 | import spock.lang.Specification 9 | import spock.lang.Unroll 10 | import spock.util.mop.Use 11 | 12 | import static spock.genesis.Gen.* 13 | 14 | 15 | // tag::genimport[] 16 | //static import generator factory methods 17 | // end::genimport[] 18 | 19 | class SamplesSpec extends Specification { 20 | 21 | // tag::factorymethods[] 22 | def 'using static factory methods'() { 23 | expect: 24 | string.iterator().next() instanceof String 25 | bytes.iterator().next() instanceof byte[] 26 | getDouble().iterator().next() instanceof Double 27 | integer.iterator().next() instanceof Integer 28 | getLong().iterator().next() instanceof Long 29 | character.iterator().next() instanceof Character 30 | date.iterator().next() instanceof Date 31 | } 32 | // end::factorymethods[] 33 | 34 | // tag::stringlength[] 35 | def 'create a string by length'() { 36 | when: 'establishing max string length' 37 | def shortWord = string(5).iterator().next() // <1> 38 | 39 | then: 'word size should be less equal than max' 40 | shortWord.size() <= 5 41 | 42 | when: 'establishing min and max word size' 43 | def largerWord = string(5,10).iterator().next() // <2> 44 | 45 | then: 'word should be larger equal min' 46 | largerWord.size() >= 5 47 | 48 | and: 'word should be less equal max' 49 | largerWord.size() <= 10 50 | } 51 | // end::stringlength[] 52 | 53 | // tag::numbers[] 54 | def 'generate numbers'() { 55 | expect: 56 | getDouble().iterator().next() instanceof Double 57 | integer.iterator().next() instanceof Integer 58 | getLong().iterator().next() instanceof Long 59 | bytes.iterator().next() instanceof byte[] 60 | } 61 | // end::numbers[] 62 | 63 | // tag::integerlength[] 64 | def 'create an integer with min and max'() { 65 | when: 'establishing max possible number' 66 | def firstNumber = integer(5..10).iterator().next() // <1> 67 | 68 | then: 'generated number will be less equals than max' 69 | firstNumber >= 5 70 | firstNumber <= 10 71 | 72 | when: 'establishing min and max valid numbers' 73 | def secondNumber = integer(5,10).iterator().next() // <2> 74 | 75 | then: 'generated number must be between both numbers' 76 | secondNumber >= 5 77 | secondNumber <= 10 78 | } 79 | // end::integerlength[] 80 | 81 | // tag::value[] 82 | def 'create a value using the value() method'() { 83 | expect: 'to get several copies of a value' 84 | value(0).take(2).collect() == [0,0] 85 | 86 | and: 'to get just one' 87 | value(0).iterator().next() == 0 88 | } 89 | // end::value[] 90 | 91 | // tag::dates[] 92 | def 'create a new date value range'() { 93 | given: "yesterday's reference and tomorrow's" 94 | def yesterday = new Date() - 1 95 | def tomorrow = new Date() + 1 96 | 97 | when: 'getting a new date' 98 | def newDate = date(yesterday, tomorrow).iterator().next() 99 | 100 | then: 'new date should be between boundaries' 101 | tomorrow.after(newDate) 102 | newDate.after(yesterday) 103 | } 104 | // end::dates[] 105 | 106 | // tag::ampersand[] 107 | def 'create multi source generator with & operator'() { 108 | setup: 109 | def gen = string(100) & integer & date 110 | expect: 111 | gen instanceof MultiSourceGenerator 112 | gen.any { it instanceof Integer } 113 | gen.any { it instanceof String } 114 | gen.any { it instanceof Date } 115 | } 116 | // end::ampersand[] 117 | 118 | // tag::multiplyby[] 119 | def 'multiply by int limits the quantity generated'() { 120 | setup: 121 | def gen = string * 3 122 | when: 123 | def results = gen.collect() 124 | then: 125 | results.size() == 3 126 | } 127 | // end::multiplyby[] 128 | 129 | @Use(ExtensionMethods) 130 | def 'multiply int by generator limits the quantity generated'() { 131 | setup: 132 | def gen = 3 * string 133 | when: 134 | def results = gen.collect() 135 | then: 136 | results.size() == 3 137 | } 138 | 139 | @Unroll('convert #source.class to generator') 140 | @Use(ExtensionMethods) //this Extension will be added by the groovy runtime 141 | def 'convert Collection, Array, Iterator, and Iterable to generator using method added by extension'() { 142 | setup: 143 | def gen = source.toGenerator() 144 | expect: 145 | Generator.isAssignableFrom(gen.class) 146 | when: 147 | def results = gen.collect() 148 | then: 149 | results == [1, 2, 3] 150 | where: 151 | source << [ 152 | [1, 2, 3], 153 | [1, 2, 3].toArray(), 154 | [1, 2, 3].iterator(), 155 | new Iterable() { Iterator iterator() { [1, 2, 3].iterator() } } 156 | ] 157 | } 158 | 159 | @Use(ExtensionMethods) 160 | def 'convert iterable to generator'() { 161 | setup: 162 | def source = [1, 2, 3] 163 | def iterable = new Iterable() { 164 | Iterator iterator() { source.iterator() } 165 | } 166 | 167 | def gen = iterable.toGenerator() 168 | expect: 169 | iterable instanceof Collection == false 170 | Generator.isAssignableFrom(gen.class) 171 | when: 172 | def results = gen.collect() 173 | then: 174 | results == source 175 | } 176 | 177 | // tag::tuple[] 178 | def 'generate a tuple'() { 179 | when: 'generating a tuple of numbers' 180 | def tuple = tuple(integer, integer, string).iterator().next() 181 | 182 | then: 'make sure we get a list of the expected size' 183 | tuple.size() == 3 184 | 185 | and: 'the type of the members are the expected' 186 | tuple.first() instanceof Integer 187 | tuple.get(1) instanceof Integer 188 | tuple.last() instanceof String 189 | } 190 | // end::tuple[] 191 | 192 | // tag::simplelist[] 193 | def 'generate a simple list'() { 194 | when: 'generating a simple list' 195 | def list = list(integer).iterator().next() // <1> 196 | 197 | then: 'we only can be sure about the type of the list' 198 | list instanceof List 199 | 200 | and: 'the type of elements due to the value generator used' 201 | list.every { it instanceof Integer } 202 | } 203 | // end::simplelist[] 204 | 205 | // tag::sizedlist[] 206 | def 'generate a list with size boundaries'() { 207 | when: 'establishing the list definition' 208 | def list = list(integer, 1, 5).iterator().next() // <1> 209 | 210 | then: 'it should obey the following assertions' 211 | list instanceof List // <2> 212 | list.size() >= 1 // <3> 213 | list.size() <= 5 // <4> 214 | list.every { it instanceof Integer } // <5> 215 | 216 | } 217 | // end::sizedlist[] 218 | 219 | // tag::mapgenerator[] 220 | def 'generate a map'() { 221 | when: 'defining a map with different fields' 222 | def myMap = map( // <1> 223 | id: getLong(), // <2> 224 | name: string, // <3> 225 | age: integer(0, 120)).iterator().next() // <4> 226 | 227 | then: 'we should get instances of map' 228 | myMap instanceof Map 229 | 230 | and: 'the fields should follow the generators rules' 231 | myMap.id instanceof Long 232 | myMap.name instanceof String 233 | myMap.age instanceof Integer 234 | } 235 | // end::mapgenerator[] 236 | 237 | // tag::data[] 238 | static class Data { 239 | String s 240 | Integer i 241 | Date d 242 | } 243 | // end::data[] 244 | 245 | // tag::typegenerator[] 246 | def 'generate type with map'() { 247 | setup: 248 | def gen = type(Data, s: string, i: integer, d: date) // <1> 249 | when: 250 | Data result = gen.iterator().next() // <2> 251 | then: 252 | result.d 253 | result.i 254 | result.s 255 | } 256 | // end::typegenerator[] 257 | 258 | // tag::transform[] 259 | @Iterations(10) 260 | def 'transform the output of a generator'() { 261 | expect: 262 | result instanceof String 263 | result.isInteger() 264 | where: 265 | result << integer.map { val -> val.toString() } 266 | } 267 | // end::transform[] 268 | 269 | // tag::tupledata[] 270 | static class TupleData { 271 | String s 272 | Integer i 273 | Date d 274 | 275 | TupleData(String s, Integer i, Date d) { 276 | this.s = s 277 | this.i = i 278 | this.d = d 279 | } 280 | } 281 | // end::tupledata[] 282 | 283 | // tag::typegenerator2[] 284 | def 'generate type with tuple'() { 285 | expect: 286 | result instanceof TupleData 287 | result.d 288 | result.i == 42 289 | result.s 290 | 291 | where: 292 | result << type(TupleData, string, value(42), date).take(5) 293 | } 294 | // end::typegenerator2[] 295 | 296 | def 'generate with factory'() { 297 | expect: 298 | result == new Date(42) 299 | where: 300 | result << using { new Date(42) }.take(1) 301 | } 302 | 303 | // tag::datewith[] 304 | def 'call methods on generated value using with'() { 305 | setup: 306 | def gen = date.with { setTime(1400) } 307 | 308 | expect: 309 | gen.iterator().next().getTime() == 1400 310 | } 311 | // end::datewith[] 312 | 313 | // tag::these[] 314 | def 'generate from a specific set of values'() { 315 | expect: 'to get numbers from a varargs' 316 | these(1,2,3).take(3).collect() == [1,2,3] 317 | 318 | and: 'to get values from an iterable object such as a list' 319 | these([1,2,3]).take(2).collect() == [1,2] 320 | 321 | and: 'to get values from a given class' 322 | these(String).iterator().next() == String 323 | 324 | and: 'to stop producing numbers if the source is exhausted' 325 | these(1..3).take(10).collect() == [1,2,3] 326 | } 327 | // end::these[] 328 | 329 | // tag::then[] 330 | def 'generate from multiple iterators in sequence'() { 331 | setup: 332 | def gen = these(1, 2, 3).then([4, 5]) 333 | expect: 334 | gen.collect() == [1, 2, 3, 4, 5] 335 | } 336 | // end::then[] 337 | 338 | // tag::fromenum[] 339 | enum Days { 340 | SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY 341 | } 342 | 343 | def 'generate from an enum'() { 344 | setup: 345 | def gen = these Days 346 | expect: 347 | gen.collect() == Days.collect() 348 | } 349 | // end::fromenum[] 350 | 351 | // tag::once[] 352 | def 'generate a value once'() { 353 | setup: 354 | def gen = once value 355 | expect: 356 | gen.collect() == [value] 357 | where: 358 | value << [null, 1, 'b', [1,2]] 359 | } 360 | // end::once[] 361 | 362 | // tag::take[] 363 | def 'generate a value repeatedly'() { 364 | setup: 365 | def gen = value(null).take(100) 366 | when: 367 | def result = gen.collect() 368 | then: 369 | result.size() == 100 370 | result.every { it == null } 371 | } 372 | // end::take[] 373 | 374 | // tag::any[] 375 | def 'generate any value from a given source'() { 376 | given: 'a source' 377 | def source = [1,2,null,3] 378 | 379 | expect: 'only that the generated value is any of the elements' 380 | Gen.any(source).take(2).every { n -> n in source } 381 | } 382 | // end::any[] 383 | 384 | def 'generate a random value from specified values'() { 385 | setup: 386 | def range = 1..100 387 | def gen = any range 388 | when: 'generate a list for each value until it is generated' 389 | def results = range.collect { num -> 390 | gen.takeWhile { it != num }.collect() 391 | } 392 | then: 'at least one should have gotten multiple results before finding the value' 393 | results.any { it.size() > 1 } 394 | and: 'all results should be from the supplied values' 395 | results.flatten().every { it in range } 396 | } 397 | 398 | // tag::stringregex[] 399 | def 'generate a string using a regular expression'() { 400 | expect: 401 | generatedString ==~ '(https?|ftp|file)://[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|]\\d' 402 | where: 403 | generatedString << string(~'(https?|ftp|file)://[-A-Z0-9+&@#/%?=~_|!:,.;]*[-A-Z0-9+&@#/%=~_|]\\d').take(10) 404 | } 405 | // end::stringregex[] 406 | 407 | @Immutable 408 | static class Person { 409 | int id 410 | String name 411 | String title 412 | Date birthDate 413 | char gender 414 | } 415 | 416 | def 'complex pogo'() { 417 | expect: 418 | person instanceof Person 419 | person.gender in ['M', 'F', 'T', 'U'].collect { it as char } 420 | person.id > 199 421 | person.id < 10001 422 | person.birthDate >= Date.parse('MM/dd/yyyy', '01/01/1980') 423 | person.birthDate <= new Date() 424 | 425 | where: 426 | person << type(Person, 427 | id: integer(200..10000), 428 | name: string(~/[A-Z][a-z]+( [A-Z][a-z]+)?/), 429 | birthDate: date(Date.parse('MM/dd/yyyy', '01/01/1980'), new Date()), 430 | title: these('', null).then(Gen.any('Dr.', 'Mr.', 'Ms.', 'Mrs.')), 431 | gender: character('MFTU') 432 | ).take(3) 433 | } 434 | 435 | //tag::iterations[] 436 | @Iterations(2) 437 | def 'limiting iterations to 2 makes it so the first 2 iterations are all that run'() { 438 | expect: 439 | s instanceof String 440 | i instanceof Integer 441 | i < 3 442 | where: 443 | s << string(~/[A-Z][a-z]+( [A-Z][a-z]+)?/) 444 | i << these(1,2,3,4,5,6) 445 | } 446 | //end::iterations[] 447 | 448 | //tag::seed[] 449 | def 'setting seed returns the same values with 2 generators configured the same'() { 450 | given: 451 | def generatedA = string(10).seed(879).take(10).realized 452 | def generatedB = string(10).seed(879).take(10).realized 453 | expect: 454 | generatedA == generatedB 455 | } 456 | //end::seed[] 457 | 458 | //tag::differentseed[] 459 | def 'setting seed to different values produces different sequences'() { 460 | given: 461 | def generatedA = integer.seed(879).take(4).realized 462 | def generatedB = integer.seed(3).take(4).realized 463 | expect: 464 | generatedA == [-1295148427, 2105117961, -922763979, 1733784787] 465 | generatedB == [-1155099828, -1879439976, 304908421, -836442134] 466 | } 467 | //end::differentseed[] 468 | } 469 | -------------------------------------------------------------------------------- /src/main/groovy/spock/genesis/Gen.groovy: -------------------------------------------------------------------------------- 1 | package spock.genesis 2 | 3 | import groovy.transform.CompileStatic 4 | import spock.genesis.extension.ExtensionMethods 5 | import spock.genesis.generators.CyclicGenerator 6 | import spock.genesis.generators.FactoryGenerator 7 | import spock.genesis.generators.Generator 8 | import spock.genesis.generators.IterableGenerator 9 | import spock.genesis.generators.composites.DefinedMapGenerator 10 | import spock.genesis.generators.composites.ListGenerator 11 | import spock.genesis.generators.composites.PojoGenerator 12 | import spock.genesis.generators.composites.RandomMapGenerator 13 | import spock.genesis.generators.composites.TupleGenerator 14 | import spock.genesis.generators.values.ByteArrayGenerator 15 | import spock.genesis.generators.values.CharacterGenerator 16 | import spock.genesis.generators.values.DateGenerator 17 | import spock.genesis.generators.values.DoubleGenerator 18 | import spock.genesis.generators.values.IntegerGenerator 19 | import spock.genesis.generators.values.LongGenerator 20 | import spock.genesis.generators.values.RandomElementGenerator 21 | import spock.genesis.generators.values.StringGenerator 22 | import spock.genesis.generators.values.ValueGenerator 23 | 24 | import java.util.regex.Pattern 25 | 26 | /** 27 | * Static factory methods for Generators 28 | */ 29 | @SuppressWarnings(['MethodCount']) 30 | @CompileStatic 31 | class Gen { 32 | 33 | /** 34 | * Produces a {@link StringGenerator} capable of producing values 35 | * of type {@link String} 36 | * 37 | * @return an infinite lazy String Generator 38 | */ 39 | static StringGenerator getString() { 40 | new StringGenerator() 41 | } 42 | 43 | /** 44 | * Produces a {@link StringGenerator} capable of producing values 45 | * of type {@link String} with a maximum length passed as 46 | * parameter 47 | * 48 | * @param maxLength 49 | * @return an infinite lazy String Generator 50 | */ 51 | static StringGenerator string(int maxLength) { 52 | new StringGenerator(maxLength) 53 | } 54 | 55 | /** 56 | * Produces a {@link StringGenerator} capable of producing values 57 | * of type {@link String}. These strings will take into account 58 | * potential values passed as parameter. 59 | * 60 | * @param potentialCharacters A {@link String} with the potential 61 | * characters that we would like to see in the generated values 62 | * @return an infinite lazy String Generator {@link 63 | * StringGenerator} 64 | */ 65 | static StringGenerator string(String potentialCharacters) { 66 | new StringGenerator(potentialCharacters) 67 | } 68 | 69 | /** 70 | * Produces a {@link StringGenerator} capable of producing values 71 | * of type {@link String} with a maximum and minimum length passed 72 | * as parameters 73 | * 74 | * @param minLength minimum length of the generated values 75 | * @param maxLength maximum length of the generated values 76 | * @return an infinite lazy String Generator {@link 77 | * StringGenerator} 78 | */ 79 | static StringGenerator string(int minLength, int maxLength) { 80 | new StringGenerator(minLength, maxLength) 81 | } 82 | 83 | /** 84 | * Produces a {@link StringGenerator} capable of producing values 85 | * of type {@link String} from a given {@link Pattern} 86 | * 87 | * @param regex the pattern expression used as template for 88 | * generated values 89 | * @return an infinite lazy String Generator {@link 90 | * StringGenerator} 91 | */ 92 | static StringGenerator string(Pattern regex) { 93 | new StringGenerator(regex) 94 | } 95 | 96 | /** 97 | * Produces a {@link ByteArrayGenerator} capable of producing 98 | * random values of type {@link Byte} 99 | * 100 | * @return an infinite lazy {@link ByteArrayGenerator} 101 | */ 102 | static ByteArrayGenerator getBytes() { 103 | new ByteArrayGenerator() 104 | } 105 | 106 | /** 107 | * Produces a {@link IntegerGenerator} capable of producing 108 | * random values of type {@link Integer} 109 | * 110 | * @return an infinite lazy {@link IntegerGenerator} 111 | */ 112 | static IntegerGenerator getInteger() { 113 | new IntegerGenerator() 114 | } 115 | 116 | /** 117 | * Produces a {@link IntegerGenerator} capable of producing random 118 | * values of type {@link Integer} from a minimum number to a 119 | * maximum number. 120 | * 121 | * @param min minimum generated number allowed 122 | * @param max maximum generated number allowed 123 | * @return an infinite lazy {@link IntegerGenerator} 124 | */ 125 | static IntegerGenerator integer(int min, int max) { 126 | new IntegerGenerator(min, max) 127 | } 128 | 129 | /** 130 | * Produces a {@link IntegerGenerator} capable of producing random 131 | * values of type {@link Integer} from a minimum number to a 132 | * maximum number. 133 | * 134 | * @param range range representing the minimum and maximum 135 | * possible number allowed 136 | * @return an infinite lazy {@link IntegerGenerator} 137 | */ 138 | static IntegerGenerator integer(IntRange range) { 139 | new IntegerGenerator(range.from, range.to) 140 | } 141 | 142 | /** 143 | * Produces a {@link LongGenerator} capable of producing random 144 | * values of type {@link Long} 145 | * 146 | * @return an infinite lazy {@link LongGenerator} 147 | */ 148 | static LongGenerator getLong() { 149 | new LongGenerator() 150 | } 151 | 152 | /** 153 | * Produces a {@link CharacterGenerator} capable of producing 154 | * random values of type {@link Character} 155 | * 156 | * @return an infinite lazy {@link CharacterGenerator} 157 | */ 158 | static CharacterGenerator getCharacter() { 159 | new CharacterGenerator() 160 | } 161 | 162 | /** 163 | * Produces a {@link CharacterGenerator} capable of producing 164 | * random values of type {@link Character} from a given 165 | * set of potential characters 166 | * 167 | * @param potentialCharacters a {@link String} containing a set of 168 | * potential characters that can be used in new generated values 169 | * @return an infinite lazy {@link CharacterGenerator} 170 | */ 171 | static CharacterGenerator character(String potentialCharacters) { 172 | new CharacterGenerator(potentialCharacters) 173 | } 174 | 175 | /** 176 | * Produces a {@link DoubleGenerator} capable of producing random 177 | * values of type {@link Double} 178 | * 179 | * @return an infinite lazy {@link DoubleGenerator} 180 | */ 181 | static DoubleGenerator getDouble() { 182 | new DoubleGenerator() 183 | } 184 | 185 | /** 186 | * Produces a {@link ValueGenerator} capable of producing values 187 | * of the same kind passed as parameter 188 | * 189 | * @param value value you want to produce over an over again 190 | * @return an infinite lazy of type {@link ValueGenerator} 191 | */ 192 | static ValueGenerator value(T value) { 193 | new ValueGenerator(value) 194 | } 195 | 196 | /** 197 | * Produces a lazy infinite generator that returns a random 198 | * element from a source {@link Collection} 199 | * 200 | * @param source {@link Collection} of type {@link Object} to pick 201 | * from 202 | * @return a {@link RandomElementGenerator} 203 | */ 204 | static RandomElementGenerator any(Collection source) { 205 | new RandomElementGenerator(source) 206 | } 207 | 208 | /** 209 | * Produces a lazy infinite generator that returns a random 210 | * element from a source {@link Collection} 211 | * 212 | * @param source variable arguments of type {@link Object} to pick 213 | * from 214 | * @return a {@link RandomElementGenerator} 215 | */ 216 | static RandomElementGenerator any(T... source) { 217 | new RandomElementGenerator(source.toList()) 218 | } 219 | 220 | /** 221 | * Produces a lazy infinite {@link CyclicGenerator} that repeats 222 | * an {@link Iterable}. 223 | * 224 | * @param source {@link Iterable} to repeat over 225 | * @return an instance of {@link CyclicGenerator} 226 | */ 227 | static CyclicGenerator cycle(Iterable source) { 228 | new IterableGenerator(source).repeat() 229 | } 230 | 231 | /** 232 | * Produces a lazy infinite {@link PojoGenerator} that creates 233 | * instances of objects of a given type 234 | * 235 | * @param keysToValueGenerators generators per each field 236 | * @param target type of the object we would like to generate 237 | * @return an instance of {@link PojoGenerator} 238 | */ 239 | static PojoGenerator type(Map keysToValueGenerators, Class target) { 240 | new PojoGenerator(target, map(keysToValueGenerators)) 241 | } 242 | 243 | /** 244 | * Produces a lazy infinite {@link PojoGenerator} that creates 245 | * instances of objects of a given type 246 | * 247 | * @param target type of the object we would like to generate 248 | * @param argGenerators generators per each field 249 | * @return an instance of {@link PojoGenerator} 250 | */ 251 | static PojoGenerator type(Class target, Iterable... argGenerators) { 252 | new PojoGenerator(target, tuple(argGenerators)) 253 | } 254 | 255 | /** 256 | * Produces a lazy infinite {@link PojoGenerator} that creates 257 | * instances of {@link Map} 258 | * 259 | * @param keysToValueGenerators generators per each map field 260 | * @return an instance of {@link DefinedMapGenerator} 261 | */ 262 | static DefinedMapGenerator map(Map keysToValueGenerators) { 263 | new DefinedMapGenerator(keysToValueGenerators) 264 | } 265 | 266 | /** 267 | * Produces a lazy infinite {@link PojoGenerator} that creates 268 | * instances of {@link Map} 269 | * 270 | * @param keyGenerator generators of map keys 271 | * @param valueGenerator generators of map values 272 | * @return an instance of {@link RandomMapGenerator} 273 | */ 274 | static RandomMapGenerator map(Iterable keyGenerator, Iterable valueGenerator) { 275 | new RandomMapGenerator(keyGenerator, valueGenerator) 276 | } 277 | 278 | /** 279 | * Produces a lazy infinite {@link ListGenerator} that creates 280 | * instances of {@link List} based on a given value generator 281 | * 282 | * @param valueGenerator generates values of the produced list 283 | * @return an instance of {@link ListGenerator} 284 | */ 285 | static ListGenerator list(Generator valueGenerator) { 286 | new ListGenerator(valueGenerator) 287 | } 288 | 289 | /** 290 | * Produces a lazy infinite {@link ListGenerator} that creates 291 | * instances of {@link List} of a given length and it's based on a 292 | * given value generator 293 | * 294 | * @param valueGenerator generates values of the produced list 295 | * @param maxLength maximum length of generated lists 296 | * @return an instance of {@link ListGenerator} 297 | */ 298 | static ListGenerator list(Generator valueGenerator, int maxLength) { 299 | new ListGenerator(valueGenerator, maxLength) 300 | } 301 | 302 | /** 303 | * Produces a lazy infinite {@link ListGenerator} that creates 304 | * instances of {@link List} of a given max and min length and 305 | * it's based on a given value generator 306 | * 307 | * @param valueGenerator generates values of the produced list 308 | * @param minLength minimum length of generated lists 309 | * @param maxLength maximum length of generated lists 310 | * @return an instance of {@link ListGenerator} 311 | */ 312 | static ListGenerator list(Generator valueGenerator, int minLength, int maxLength) { 313 | new ListGenerator(valueGenerator, minLength, maxLength) 314 | } 315 | 316 | /** 317 | * Produces a lazy infinite {@link TupleGenerator} that creates 318 | * tuples with values based on the generators passed as parameter 319 | * 320 | * @param generators generators for each tuple element 321 | * @return an instance of {@link TupleGenerator} 322 | */ 323 | static TupleGenerator tuple(Iterable... generators) { 324 | new TupleGenerator(generators) 325 | } 326 | 327 | /** 328 | * Produces a lazy infinite {@link DateGenerator} that generates 329 | * random instances of {@link Date} 330 | * 331 | * @return an instance of {@link DateGenerator} 332 | */ 333 | static DateGenerator getDate() { 334 | new DateGenerator() 335 | } 336 | 337 | /** 338 | * Produces a lazy infinite {@link DateGenerator}. This generator 339 | * will create random instances of {@link Date} from a minimum 340 | * date to a maximum date. 341 | * 342 | * @param minDate minimum possible date 343 | * @param maxDate maximum possible date 344 | * @return an instance of {@link DateGenerator} 345 | */ 346 | static DateGenerator date(Date minDate, Date maxDate) { 347 | new DateGenerator(minDate, maxDate) 348 | } 349 | 350 | /** 351 | * Produces a lazy infinite {@link FactoryGenerator}. This 352 | * generator will produce random instances of the values returned 353 | * by a given {@link Closure} 354 | * 355 | * @param factory the closure that defines the generated value 356 | * @return an instance of {@link FactoryGenerator} 357 | */ 358 | static FactoryGenerator using(Closure factory) { 359 | new FactoryGenerator(factory) 360 | } 361 | 362 | /** 363 | * Produces a lazy infinite {@link Generator}. This 364 | * generator will produce the values taken from a given {@link 365 | * Iterable} in the order they were defined 366 | * 367 | * @param iterable {@link Iterable} declaring values to produce 368 | * @param finite sets the generator as finite or not 369 | * @return an instance of {@link Generator} 370 | */ 371 | static Generator these(Iterable iterable, boolean finite = false) { 372 | ExtensionMethods.toGenerator(iterable, finite) 373 | } 374 | 375 | /** 376 | * Produces a lazy infinite {@link Generator}. This 377 | * generator will produce classes of the type passed as parameter 378 | * 379 | * @param clazz the type of class you want to produce 380 | * @return an instance of {@link Generator} 381 | */ 382 | static Generator these(Class clazz) { 383 | ExtensionMethods.toGenerator(clazz) 384 | } 385 | 386 | /** 387 | * Produces a lazy infinite {@link Generator}. This 388 | * generator will produce the values taken from the values passed 389 | * as arguments in the order they were defined 390 | * 391 | * @param values variable arguments to get values from 392 | * @return an instance of {@link Generator} 393 | */ 394 | static Generator these(T... values) { 395 | ExtensionMethods.toGenerator(values) 396 | } 397 | 398 | /** 399 | * Produces a lazy infinite {@link Generator}. This 400 | * generator will produce the values taken from the values passed 401 | * as arguments in the order they were defined 402 | * 403 | * @param values collection to get values from 404 | * @return an instance of {@link Generator} 405 | */ 406 | static Generator these(Collection values) { 407 | ExtensionMethods.toGenerator(values) 408 | } 409 | 410 | /** 411 | * Produces a lazy infinite {@link Generator}. This 412 | * generator will produce copies of the value passed as parameter 413 | * 414 | * @param value sample value 415 | * @return an instance of {@link Generator} that produces copies 416 | * of the sample value 417 | */ 418 | static Generator once(T value) { 419 | these([value]) 420 | } 421 | } 422 | --------------------------------------------------------------------------------