Skip to content

Commit

Permalink
Merge pull request #22 from Bijnagte/seed-random
Browse files Browse the repository at this point in the history
resolves #16 control seed for random generators
  • Loading branch information
Bijnagte authored Jul 19, 2016
2 parents 16e3cce + f90a1f6 commit f2edbc3
Show file tree
Hide file tree
Showing 41 changed files with 560 additions and 139 deletions.
3 changes: 3 additions & 0 deletions src/docs/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,7 @@ include::combine.adoc[]

include::cardinality.adoc[]

include::output.adoc[]

include::development.adoc[]

41 changes: 41 additions & 0 deletions src/docs/output.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
== Output
Some times you need to control the output of a generator.

=== Seeding random generation

For random generators it can be useful to control the seed for random generation.
This will cause a consistent sequence of values to be generated by an equivalent generator.

[source,groovy]
----
include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=seed,indent=0]
----

Setting the seed to different values will vary the output but will always produce the same sequence.
[source,groovy]
----
include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=differentseed,indent=0]
----

=== Using `with`

You can use `with` if you would like to set some property of the
generated value.

IMPORTANT: `with` is different from the default groovy implementation! It always returns a new generator.

[source,groovy]
----
include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=datewith,indent=0]
----

=== Transforming with `map`

Sometimes output needs to be converted to another type. the `map` method works
like groovy's `collect` but will return a new generator that lazily performs the
transformation. An example would be calling the `toString()` method.

[source,groovy]
----
include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=transform,indent=0]
----
10 changes: 0 additions & 10 deletions src/docs/values.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -112,13 +112,3 @@ include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=fromenum,indent=0]
NOTE: `these` in general can generate values taken from a given
source, in this case the source is an enum. To know more about `these`
check the section `combine`.

=== Using `with`

You can use `with` if you would like to set some property of the
generated value.

[source,groovy]
----
include::{testDir}/spock/genesis/SamplesSpec.groovy[tags=datewith,indent=0]
----
63 changes: 27 additions & 36 deletions src/main/groovy/spock/genesis/Gen.groovy
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package spock.genesis

import groovy.transform.CompileStatic
import spock.genesis.extension.ExtensionMethods
import spock.genesis.generators.CyclicGenerator
import spock.genesis.generators.FactoryGenerator
import spock.genesis.generators.Generator
Expand All @@ -25,6 +27,7 @@ import java.util.regex.Pattern
* Static factory methods for Generators
*/
@SuppressWarnings(['MethodCount'])
@CompileStatic
class Gen {

/**
Expand Down Expand Up @@ -146,18 +149,6 @@ class Gen {
new LongGenerator()
}

/**
* Produces a {@link CharacterGenerator} capable of producing
* random values of type {@link Character}
*
* @return an infinite lazy {@link CharacterGenerator}
* @deprecated use {@link Gen#getCharacter} instead
*/
@Deprecated
static CharacterGenerator getChar() {
new CharacterGenerator()
}

/**
* Produces a {@link CharacterGenerator} capable of producing
* random values of type {@link Character}
Expand Down Expand Up @@ -198,7 +189,7 @@ class Gen {
* @param value value you want to produce over an over again
* @return an infinite lazy of type {@link ValueGenerator}
*/
static ValueGenerator value(value) {
static <T> ValueGenerator<T> value(T value) {
new ValueGenerator(value)
}

Expand All @@ -210,7 +201,7 @@ class Gen {
* from
* @return a {@link RandomElementGenerator}
*/
static RandomElementGenerator any(Collection source) {
static <T> RandomElementGenerator<T> any(Collection<T> source) {
new RandomElementGenerator(source)
}

Expand All @@ -222,7 +213,7 @@ class Gen {
* from
* @return a {@link RandomElementGenerator}
*/
static RandomElementGenerator any(Object... source) {
static <T> RandomElementGenerator<T> any(T... source) {
new RandomElementGenerator(source.toList())
}

Expand All @@ -245,7 +236,7 @@ class Gen {
* @param target type of the object we would like to generate
* @return an instance of {@link PojoGenerator}
*/
static PojoGenerator type(Map<String, Object> keysToValueGenerators, Class target) {
static <T> PojoGenerator<T,Map> type(Map<String, Object> keysToValueGenerators, T target) {
new PojoGenerator(target, map(keysToValueGenerators))
}

Expand All @@ -257,7 +248,7 @@ class Gen {
* @param argGenerators generators per each field
* @return an instance of {@link PojoGenerator}
*/
static <T> PojoGenerator<T> type(T target, Iterable... argGenerators) {
static <T> PojoGenerator<T, List> type(T target, Iterable... argGenerators) {
new PojoGenerator(target, tuple(argGenerators))
}

Expand All @@ -280,8 +271,8 @@ class Gen {
* @param valueGenerator generators of map values
* @return an instance of {@link RandomMapGenerator}
*/
static RandomMapGenerator map(Iterable keyGenerator, Iterable valueGenerator) {
new RandomMapGenerator(keyGenerator, valueGenerator)
static <K,V> RandomMapGenerator map(Iterable<K> keyGenerator, Iterable<V> valueGenerator) {
new RandomMapGenerator<K,V>(keyGenerator, valueGenerator)
}

/**
Expand All @@ -291,7 +282,7 @@ class Gen {
* @param valueGenerator generates values of the produced list
* @return an instance of {@link ListGenerator}
*/
static ListGenerator list(Generator valueGenerator) {
static <T> ListGenerator<T> list(Generator<T> valueGenerator) {
new ListGenerator(valueGenerator)
}

Expand All @@ -304,7 +295,7 @@ class Gen {
* @param maxLength maximum length of generated lists
* @return an instance of {@link ListGenerator}
*/
static ListGenerator list(Generator valueGenerator, int maxLength) {
static <T> ListGenerator<T> list(Generator<T> valueGenerator, int maxLength) {
new ListGenerator(valueGenerator, maxLength)
}

Expand All @@ -329,7 +320,7 @@ class Gen {
* @param generators generators for each tuple element
* @return an instance of {@link TupleGenerator}
*/
static TupleGenerator tuple(Iterable... generators) {
static <T> TupleGenerator<T> tuple(Iterable<T>... generators) {
new TupleGenerator(generators)
}

Expand Down Expand Up @@ -364,67 +355,67 @@ class Gen {
* @param factory the closure that defines the generated value
* @return an instance of {@link FactoryGenerator}
*/
static FactoryGenerator using(Closure factory) {
static <T> FactoryGenerator<T> using(Closure<T> factory) {
new FactoryGenerator(factory)
}

/**
* Produces a lazy infinite {@link LimitedGenerator}. This
* Produces a lazy infinite {@link Generator}. This
* generator will produce the values taken from a given {@link
* Iterable} in the order they were defined
*
* @param iterable {@link Iterable} declaring values to produce
* @param finite sets the generator as finite or not
* @return an instance of {@link Generator}
*/
static Generator these(Iterable iterable, boolean finite = false) {
iterable.toGenerator(finite)
static <T> Generator<T> these(Iterable<T> iterable, boolean finite = false) {
ExtensionMethods.toGenerator(iterable, finite)
}

/**
* Produces a lazy infinite {@link LimitedGenerator}. This
* Produces a lazy infinite {@link Generator}. This
* generator will produce classes of the type passed as parameter
*
* @param clazz the type of class you want to produce
* @return an instance of {@link Generator}
*/
static Generator these(Class clazz) {
clazz.toGenerator()
ExtensionMethods.toGenerator(clazz)
}

/**
* Produces a lazy infinite {@link LimitedGenerator}. This
* Produces a lazy infinite {@link Generator}. This
* generator will produce the values taken from the values passed
* as arguments in the order they were defined
*
* @param values variable arguments to get values from
* @return an instance of {@link Generator}
*/
static Generator these(Object... values) {
values.toGenerator()
static <T> Generator<T> these(T... values) {
ExtensionMethods.toGenerator(values)
}

/**
* Produces a lazy infinite {@link LimitedGenerator}. This
* Produces a lazy infinite {@link Generator}. This
* generator will produce the values taken from the values passed
* as arguments in the order they were defined
*
* @param values collection to get values from
* @return an instance of {@link Generator}
*/
static Generator these(Collection values) {
values.toGenerator()
static <T> Generator<T> these(Collection<T> values) {
ExtensionMethods.toGenerator(values)
}

/**
* Produces a lazy infinite {@link LimitedGenerator}. This
* Produces a lazy infinite {@link Generator}. This
* generator will produce copies of the value passed as parameter
*
* @param value sample value
* @return an instance of {@link Generator} that produces copies
* of the sample value
*/
static Generator once(Object value) {
static <T> Generator<T> once(T value) {
these([value])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ class ExtensionMethods {
}

static <T> Generator<T> toGenerator(Iterable<T> self, boolean finite = false) {
new IterableGenerator<T>(self, finite)
if (Generator.isInstance(self)) {
(Generator) self
} else {
new IterableGenerator<T>(self, finite)
}
}

static Generator<String> toGenerator(String self) {
new ObjectIteratorGenerator<String>(self)
}

static Generator<Object> toGenerator(Object self) {
static Generator toGenerator(Object self) {
new ObjectIteratorGenerator(self)
}

Expand Down
21 changes: 17 additions & 4 deletions src/main/groovy/spock/genesis/generators/Generator.groovy
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package spock.genesis.generators

import groovy.transform.CompileStatic
import spock.genesis.extension.ExtensionMethods
import spock.genesis.generators.values.NullGenerator

/**
Expand All @@ -9,6 +10,7 @@ import spock.genesis.generators.values.NullGenerator
*/
@CompileStatic
abstract class Generator<E> implements Iterable<E>, Closeable {
protected final Random random = new Random()

/**
* Wraps this generator in a generator that returns values that matches the supplied predicate
Expand All @@ -19,8 +21,8 @@ abstract class Generator<E> implements Iterable<E>, Closeable {
new FilteredGenerator<E>(this, predicate)
}

TransformingGenerator<E, ?> map(Closure<?> transform) {
new TransformingGenerator<E, ?>(this, transform)
public <T> TransformingGenerator<E, T> map(Closure<T> transform) {
new TransformingGenerator<E, T>(this, transform)
}

TransformingGenerator<E, E> with(Closure<?> transform) {
Expand All @@ -36,10 +38,10 @@ abstract class Generator<E> implements Iterable<E>, Closeable {
}

SequentialMultisourceGenerator<E> then(Iterable<E>... iterables) {
Iterable<E>[] all = new Iterable<E>[iterables.length + 1]
Generator<E>[] all = new Generator<E>[iterables.length + 1]
all[0] = this
for (int i = 0; i < iterables.length; i++) {
all[i + 1] = iterables[i]
all[i + 1] = ExtensionMethods.toGenerator(iterables[i])

}
new SequentialMultisourceGenerator<E>(all)
Expand Down Expand Up @@ -95,4 +97,15 @@ abstract class Generator<E> implements Iterable<E>, Closeable {

@SuppressWarnings('EmptyMethodInAbstractClass')
void close() { }

/**
* Set the {@link Random} seed for this generator and all contained generators.
* This method mutates the generator!
* @param seed
* @return this generator
*/
Generator<E> seed(Long seed) {
random.setSeed(seed)
this
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,10 @@ class GeneratorDecorator<E> extends Generator<E> implements Closeable {
void close() {
generator.close()
}

GeneratorDecorator<E> seed(Long seed) {
generator.seed(seed)
super.seed(seed)
this
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic

/**
* A generator that wraps an iterator to provide {@link Generator} methods.
* A generator that wraps an {@link Iterable} to provide {@link Generator} methods.
* @param < E > the generated type
*/
@CompileStatic
Expand Down Expand Up @@ -56,4 +56,13 @@ class IterableGenerator<E> extends Generator<E> implements Closeable {
iterable.close()
}
}

@CompileDynamic
@Override
IterableGenerator<E> seed(Long seed) {
if (iterable.respondsTo('seed')) {
iterable.seed(seed)
}
this
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import spock.genesis.extension.ExtensionMethods
class MultiSourceGenerator<E> extends Generator<E> implements Closeable {

private final List<Generator<E>> generators
final Random random = new Random()

MultiSourceGenerator(Collection<Iterable<E>> iterables) {
this.generators = iterables.collect { ExtensionMethods.toGenerator(it) }
Expand Down Expand Up @@ -77,4 +76,11 @@ class MultiSourceGenerator<E> extends Generator<E> implements Closeable {
boolean isFinite() {
generators.every { it.finite }
}

@Override
MultiSourceGenerator<E> seed(Long seed) {
generators.each { it.seed(seed) }
super.seed(seed)
this
}
}
Loading

0 comments on commit f2edbc3

Please sign in to comment.