All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.jqwik.api.Arbitrary Maven / Gradle / Ivy

There is a newer version: 1.9.2
Show newest version
package net.jqwik.api;

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

import org.apiguardian.api.*;

import net.jqwik.api.arbitraries.*;

import static org.apiguardian.api.API.Status.*;

/**
 * The main interface for representing objects that can be generated and shrunk.
 *
 * @param  The type of generated objects. Primitive objects (e.g. int, boolean etc.) are represented by their boxed
 *            type (e.g. Integer, Boolean).
 */
@API(status = STABLE, since = "1.0")
public interface Arbitrary {

	@API(status = INTERNAL)
	abstract class ArbitraryFacade {
		private static final ArbitraryFacade implementation;

		static {
			implementation = FacadeLoader.load(ArbitraryFacade.class);
		}

		public abstract  Optional> flatMapExhaustiveGenerator(
			ExhaustiveGenerator self,
			Function> mapper,
			long maxNumberOfSamples
		);

		public abstract  ListArbitrary list(Arbitrary elementArbitrary);

		public abstract  SetArbitrary set(Arbitrary elementArbitrary);

		public abstract  StreamArbitrary stream(Arbitrary elementArbitrary);

		public abstract  IteratorArbitrary iterator(Arbitrary elementArbitrary);

		public abstract  StreamableArbitrary array(Arbitrary elementArbitrary, Class arrayClass);

		public abstract  Stream sampleStream(Arbitrary arbitrary);

		public abstract  Arbitrary injectNull(Arbitrary self, double nullProbability);
	}

	/**
	 * Create the random generator for an arbitrary
	 *
	 * @param genSize a very unspecific configuration parameter that can be used
	 *                to influence the configuration and behaviour of a random generator
	 *                if and only if the generator wants to be influenced.
	 *                Many generators are independent of genSize.
	 *                

* The default value of {@code genSize} is the number of tries configured * for a property. Use {@linkplain Arbitrary#fixGenSize(int)} to fix * the parameter for a given arbitrary. * @return a new random generator instance */ RandomGenerator generator(int genSize); /** * Sometimes simplifies test writing * @return The same instance but with type Arbitrary<Object> */ @SuppressWarnings("unchecked") @API(status = INTERNAL) default Arbitrary asGeneric() { return (Arbitrary) this; } /** * All arbitraries whose base generator is supposed to produce no duplicates * should return true. * * @return true if base genator is supposed to produce no duplicates */ @API(status = INTERNAL) default boolean isUnique() { return false; } /** * Create the exhaustive generator for an arbitrary using the maximum allowed * number of generated samples. Just trying to find out if such a generator * exists might take a long time. This method should never be overridden. * * @return a new exhaustive generator or Optional.empty() if it cannot be created. */ @API(status = INTERNAL) default Optional> exhaustive() { return exhaustive(ExhaustiveGenerator.MAXIMUM_SAMPLES_TO_GENERATE); } /** * Create the exhaustive generator for an arbitrary. Depending on * {@code maxNumberOfSamples} this can take a long time. * This method must be overridden in all arbitraries that support exhaustive * generation. * * @param maxNumberOfSamples The maximum number of samples considered. * If during generation it becomes clear that this * number will be exceeded generation stops. * @return a new exhaustive generator or Optional.empty() if it cannot be created. */ @API(status = INTERNAL) default Optional> exhaustive(long maxNumberOfSamples) { return Optional.empty(); } @API(status = EXPERIMENTAL, since = "1.3.0") EdgeCases edgeCases(); /** * Create optional stream of all possible values this arbitrary could generate. * This is only possible if the arbitrary is available for exhaustive generation. * * @return optional stream of all possible values */ default Optional> allValues() { return exhaustive().map(generator -> StreamSupport.stream(generator.spliterator(), false)); } /** * Iterate through each value this arbitrary can generate if - and only if - * exhaustive generation is possible. This method can be used for example * to make assertions about a set of values described by an arbitrary. * * @param action the consumer function to be invoked for each value * @throws AssertionError if exhaustive generation is not possible */ @API(status = MAINTAINED, since = "1.1.2") default void forEachValue(Consumer action) { if (!allValues().isPresent()) throw new AssertionError("Cannot generate all values of " + this.toString()); allValues().ifPresent( stream -> stream.forEach(action::accept)); } /** * Create a new arbitrary of the same type {@code T} that creates and shrinks the original arbitrary but only allows * values that are accepted by the {@code filterPredicate}. * * @return a new arbitrary instance * @throws JqwikException if filtering will fail to come up with a value after 10000 tries */ default Arbitrary filter(Predicate filterPredicate) { return new Arbitrary() { @Override public RandomGenerator generator(int genSize) { return Arbitrary.this.generator(genSize).filter(filterPredicate); } @Override public boolean isUnique() { return Arbitrary.this.isUnique(); } @Override public Optional> exhaustive(long maxNumberOfSamples) { return Arbitrary.this.exhaustive(maxNumberOfSamples) .map(generator -> generator.filter(filterPredicate)); } @Override public EdgeCases edgeCases() { return Arbitrary.this.edgeCases().filter(filterPredicate); } }; } /** * Create a new arbitrary of type {@code U} that maps the values of the original arbitrary using the {@code mapper} * function. * * @return a new arbitrary instance */ default Arbitrary map(Function mapper) { return new Arbitrary() { @Override public RandomGenerator generator(int genSize) { return Arbitrary.this.generator(genSize).map(mapper); } @Override public boolean isUnique() { return Arbitrary.this.isUnique(); } @Override public Optional> exhaustive(long maxNumberOfSamples) { return Arbitrary.this.exhaustive(maxNumberOfSamples) .map(generator -> generator.map(mapper)); } @Override public EdgeCases edgeCases() { return Arbitrary.this.edgeCases().map(mapper); } }; } /** * Create a new arbitrary of type {@code U} that uses the values of the existing arbitrary to create a new arbitrary * using the {@code mapper} function. * * @return a new arbitrary instance */ default Arbitrary flatMap(Function> mapper) { return new Arbitrary() { @Override public RandomGenerator generator(int genSize) { return Arbitrary.this.generator(genSize).flatMap(mapper, genSize); } @Override public Optional> exhaustive(long maxNumberOfSamples) { return Arbitrary.this.exhaustive(maxNumberOfSamples) .flatMap(generator -> ArbitraryFacade.implementation .flatMapExhaustiveGenerator(generator, mapper, maxNumberOfSamples)); } @Override public EdgeCases edgeCases() { return Arbitrary.this.edgeCases().flatMapArbitrary(mapper); } }; } /** * Create a new arbitrary of the same type but inject null values with a probability of {@code nullProbability}. * * @return a new arbitrary instance */ default Arbitrary injectNull(double nullProbability) { return ArbitraryFacade.implementation.injectNull(Arbitrary.this, nullProbability); } /** * Create a new arbitrary of the same type {@code T} that creates and shrinks the original arbitrary but will * never generate the same value twice. * *

* Uniqueness is only held up for a single use of this arbitrary. * If the same arbitrary instance is used in several places, * e.g. for creating several lists, the different lists may share values * between them. *

* * @return a new arbitrary instance * @throws JqwikException if filtering will fail to come up with a value after 10000 tries */ default Arbitrary unique() { return new Arbitrary() { @Override public RandomGenerator generator(int genSize) { return Arbitrary.this.generator(genSize).unique(); } @Override public boolean isUnique() { return true; } @Override public Optional> exhaustive(long maxNumberOfSamples) { return Arbitrary.this.exhaustive(maxNumberOfSamples).map(ExhaustiveGenerator::unique); } @Override public EdgeCases edgeCases() { return Arbitrary.this.edgeCases(); } }; } /** * Fix the genSize of an arbitrary so that it can no longer be influenced from outside * * @return a new arbitrary instance */ @API(status = MAINTAINED, since = "1.2.0") default Arbitrary fixGenSize(int genSize) { return new Arbitrary() { @Override public RandomGenerator generator(int ignoredGenSize) { return Arbitrary.this.generator(genSize); } @Override public Optional> exhaustive(long maxNumberOfSamples) { return Arbitrary.this.exhaustive(maxNumberOfSamples); } @Override public EdgeCases edgeCases() { return Arbitrary.this.edgeCases(); } }; } /** * Create a new arbitrary of type {@code List} using the existing arbitrary for generating the elements of the list. */ default ListArbitrary list() { return ArbitraryFacade.implementation.list(this); } /** * Create a new arbitrary of type {@code Set} using the existing arbitrary for generating the elements of the set. * * @return a new arbitrary instance */ default SetArbitrary set() { return ArbitraryFacade.implementation.set(this); } /** * Create a new arbitrary of type {@code Stream} using the existing arbitrary * for generating the elements of the stream. * * @return a new arbitrary instance */ default StreamArbitrary stream() { return ArbitraryFacade.implementation.stream(this); } /** * Create a new arbitrary of type {@code Iterable} using the existing arbitrary for generating the elements of the * stream. * * @return a new arbitrary instance */ default IteratorArbitrary iterator() { return ArbitraryFacade.implementation.iterator(this); } /** * Create a new arbitrary of type {@code T[]} using the existing arbitrary for generating the elements of the array. * * @param arrayClass The arrays class to create, e.g. {@code String[].class}. This is required due to limitations in Java's * reflection capabilities. * @return a new arbitrary instance */ default StreamableArbitrary array(Class arrayClass) { return ArbitraryFacade.implementation.array(this, arrayClass); } /** * Create a new arbitrary of type {@code Optional} using the existing arbitrary for generating the elements of the * stream. * *

* The new arbitrary also generates {@code Optional.empty()} values with a probability of {@code 0.05} (i.e. 1 in 20). *

* * @return a new arbitrary instance */ default Arbitrary> optional() { return this.injectNull(0.05).map(Optional::ofNullable); } /** * Create a new arbitrary of type {@code List} by adding elements of type T until condition {@code until} is fulfilled. * * @return a new arbitrary instance */ @API(status = MAINTAINED, since = "1.3.0") default Arbitrary> collect(Predicate> until) { return new Arbitrary>() { @Override public RandomGenerator> generator(final int genSize) { return Arbitrary.this.generator(genSize).collect(until); } @Override public EdgeCases> edgeCases() { return EdgeCases.none(); } }; } /** * Generate a stream of sample values using this arbitrary. * This can be useful for *
    *
  • Testing arbitraries
  • *
  • Playing around with arbitraries in jshell
  • *
  • Using arbitraries independently from jqwik, e.g. to feed test data builders
  • *
* *

* The underlying generator is created with size 1000. * Outside a property a new instance of {@linkplain Random} will be created * to feed the generator. *

* *

* Using this method within a property does not break reproducibility of results, * i.e. rerunning it with same seed will also generate the same values. *

* * @return a stream of newly generated values */ @API(status = MAINTAINED, since = "1.3.0") default Stream sampleStream() { return ArbitraryFacade.implementation.sampleStream(this); } /** * Generate a single sample value using this arbitrary. * This can be useful for *
    *
  • Testing arbitraries
  • *
  • Playing around with arbitraries in jshell
  • *
  • Using arbitraries independently from jqwik, e.g. to feed test data builders
  • *
*

* Some additional things to be aware of: *

    *
  • * If you feel the need to use this method for real generation, e.g. in a provider method * you are most probably doing it wrong. You might want to use {@linkplain Arbitrary#flatMap(Function)}. *
  • *
  • * The underlying generator is created with size 1000. * Outside a property a new instance of {@linkplain Random} will be created * to feed the generator.
  • *
  • * Using this method within a property does not break reproducibility of results, * i.e. rerunning it with same seed will also generate the same value. *
  • *
* * @return a newly generated value */ @API(status = MAINTAINED, since = "1.3.0") default T sample() { return this.sampleStream() .findFirst() .orElseThrow(() -> new JqwikException("Cannot generate a value")); } /** * Create a new arbitrary of type {@code Iterable} that will * inject duplicates of previously generated values with a probability of {@code duplicateProbability}. * *

* Shrinking behavior for duplicate values * -- if duplication is required for falsification -- is poor, * i.e. those duplicate values cannot be shrunk to "smaller" duplicate values. *

* * @param duplicateProbability The probability with which a previous value will be generated * @return a new arbitrary instance */ @API(status = MAINTAINED, since = "1.3.0") default Arbitrary injectDuplicates(double duplicateProbability) { return new Arbitrary() { @Override public RandomGenerator generator(int genSize) { return Arbitrary.this.generator(genSize).injectDuplicates(duplicateProbability); } @Override public Optional> exhaustive(long maxNumberOfSamples) { return Arbitrary.this.exhaustive(maxNumberOfSamples); } @Override public EdgeCases edgeCases() { return Arbitrary.this.edgeCases(); } }; } /** * Create a new arbitrary of type {@code Tuple.Tuple1} that will use the underlying * arbitrary to create the tuple value; * * @return a new arbitrary instance */ @API(status = MAINTAINED, since = "1.3.0") default Arbitrary> tuple1() { return Arbitrary.this.map(Tuple::of); } /** * Create a new arbitrary of type {@code Tuple.Tuple2} that will use the underlying * arbitrary to create the tuple values; * * @return a new arbitrary instance */ @API(status = MAINTAINED, since = "1.3.0") default Arbitrary> tuple2() { return Arbitrary.this.list().ofSize(2).map(l -> Tuple.of(l.get(0), l.get(1))); } /** * Create a new arbitrary of type {@code Tuple.Tuple3} that will use the underlying * arbitrary to create the tuple values; * * @return a new arbitrary instance */ @API(status = MAINTAINED, since = "1.3.0") default Arbitrary> tuple3() { return Arbitrary.this.list().ofSize(3).map(l -> Tuple.of(l.get(0), l.get(1), l.get(2))); } /** * Create a new arbitrary of type {@code Tuple.Tuple4} that will use the underlying * arbitrary to create the tuple values; * * @return a new arbitrary instance */ @API(status = MAINTAINED, since = "1.3.0") default Arbitrary> tuple4() { return Arbitrary.this.list().ofSize(4).map(l -> Tuple.of(l.get(0), l.get(1), l.get(2), l.get(3))); } /** * Create a new arbitrary of type {@code Tuple.Tuple5} that will use the underlying * arbitrary to create the tuple values; * * @return a new arbitrary instance */ @API(status = MAINTAINED, since = "1.3.3") default Arbitrary> tuple5() { return Arbitrary.this.list().ofSize(5).map(l -> Tuple.of(l.get(0), l.get(1), l.get(2), l.get(3), l.get(4))); } /** * Create a new arbitrary of type {@code T} that will use the underlying * arbitrary to create the tuple values but will ignore any raised exception of * type {@code exceptionType} during generation. * * @param exceptionType The exception type to ignore * @return a new arbitrary instance */ @API(status = EXPERIMENTAL, since = "1.3.1") default Arbitrary ignoreException(Class exceptionType) { return new Arbitrary() { @Override public RandomGenerator generator(int genSize) { return Arbitrary.this.generator(genSize).ignoreException(exceptionType); } @Override public boolean isUnique() { return Arbitrary.this.isUnique(); } @Override public Optional> exhaustive(long maxNumberOfSamples) { return Arbitrary.this.exhaustive(maxNumberOfSamples) .map(generator -> generator.ignoreException(exceptionType)); } @Override public EdgeCases edgeCases() { return Arbitrary.this.edgeCases().ignoreException(exceptionType); } }; } /** * Create a new arbitrary of type {@code T} that will use the underlying * arbitrary to create the tuple values but will return unshrinkable values. * This might be necessary if values are being mutated during a property run * and the mutated state would make a shrunk value invalid. * *

* This is a hack to get around a weakness in jqwik's shrinking mechanism *

* * @return a new arbitrary instance */ @API(status = EXPERIMENTAL, since = "1.3.2") default Arbitrary dontShrink() { return new Arbitrary() { @Override public RandomGenerator generator(int genSize) { return Arbitrary.this.generator(genSize).dontShrink(); } @Override public boolean isUnique() { return Arbitrary.this.isUnique(); } @Override public Optional> exhaustive(long maxNumberOfSamples) { return Arbitrary.this.exhaustive(maxNumberOfSamples); } @Override public EdgeCases edgeCases() { return Arbitrary.this.edgeCases().dontShrink(); } }; } }