Skip to content

Latest commit

 

History

History
544 lines (378 loc) · 17.7 KB

generators_ref.md

File metadata and controls

544 lines (378 loc) · 17.7 KB

Generators reference

The following is a reference of all the included generators of RapidCheck. These generators are accessed by factory functions in the rc::gen namespace. The signatures are not the actual signatures in the source code and should be seen as pseudo C++ for pedagogical purposes. The real signatures are, of course, uglier.

Basic

Gen<T> arbitrary<T>()

Generates an arbitrary value of type T. Support for new types can be added by specializing struct rc::Arbitrary<T> and providing a static method arbitrary() which returns an appropriate generator. For more information see the documentation on generators. The semantics of the returned generator depends entirely on the implementation of the Arbitrary specialization.

This generator is also used by RapidCheck whenever it implicitly needs a generator for some type, for example when generating arguments to properties.

// Example:
const auto str_vector = *gen::arbitrary<std::vector<std::string>>();

Gen<T> just(T value)

Constantly generates value.

// Example:
const auto alwaysLeet = gen::just(1337);

Gen<std::tuple<Ts...>> tuple(Gen<Ts>... gens)

Generates an std::tuple using the given generators to generate elements.

// Example:
const auto tuple = *gen::tuple(gen::arbitrary<std::string>(),
                               gen::arbitrary<int>(),
                               gen::arbitrary<float>());

Gen<std::pair<T1, T2>> pair(Gen<T1> gen1, Gen<T2> gen2)

Similar to tuple but generates std::pair instead.

// Example:
const auto pair = *gen::pair(gen::arbitrary<std::string>(),
                             gen::arbitrary<int>());

Filtering

Gen<T> suchThat(Gen<T> gen, Predicate predicate)

Generates a value that satisfies the given predicate. Fails generation if a value that satisfies the predicate cannot be generated after an unspecified number of tries.

// Example:
const auto smallEven = *gen::suchThat(gen::inRange(0, 100), [](int x) {
  return (x % 2) == 0;
});

Gen<T> suchThat(Predicate predicate)

Like suchThat(Gen<T>, Predicate) but uses gen::arbitrary<T>(). T cannot be deduced and must be explicitly specified.

// Example:
const auto even = *gen::suchThat<int>([](int x) {
  return (x % 2) == 0;
});

Gen<T> distinctFrom(Gen<T> gen, T value)

Generates a value using gen that is not equal to value.

// Example:
const auto a = *gen::arbitrary<int>();
const auto b = *gen::distinctFrom(gen::inRange(0, 100), a);

Gen<T> distinctFrom(T value)

Like distinctFrom(Gen<T> gen, T value) but uses gen::arbitrary<T>() as the generator.

Gen<T> nonEmpty(Gen<T> gen)

Generates a value x using the given generator for x.empty() is false. Useful with strings, STL containers and other similar types.

// Example:
const auto nonEmptyString = *gen::nonEmpty(gen::string<std::string>());

Gen<T> nonEmpty()

Same as nonEmpty(Gen<T>) but uses gen::arbitrary<T>().

// Example:
const auto nonEmptyInts = *gen::nonEmpty<std::vector<int>>();

Transforming

// Example:
const auto str = *gen::string<std::string>();

Gen<U> map(Gen<T> gen, Mapper mapper)

Returns a generator equivalent to gen but with its values mapped by mapper. The value type of the returned generator will be the type returned by mapper. This is a functor map, in functional terms.

The value is passed as an rvalue to mapper and can thus be moved from.

// Example:
const auto year = *gen::map(gen::inRange(1900, 2015), [](int y) {
  return std::to_string(y);
});

Gen<U> map(Mapper mapper)

Like map(Gen<T>, Mapper) but uses gen::arbitrary<T>() for the input generator. T cannot be deduced and must be explicitly specified.

// Example:
const auto strNumber = *gen::map<int>([](int x) { return std::to_string(x); });

Gen<U> mapcat(Gen<T> gen, Mapper mapper)

Monadic bind. Takes a Gen<T> and a function from a T to a Gen<U> and returns a Gen<U>. This allows you to chain two generators together.

When shrinking, the value generated by the first generator will be shrunk first, then the second.

// Example:
const auto name = *gen::mapcat(gen::arbitrary<bool>(), [](bool isMale) {
    return isMale ? gen::element<std::string>("John", "Joe")
                  : gen::element<std::string>("Jane", "Kate");
});

Gen<T> join(Gen<Gen<T>> gen)

Flattens a generator of generators of T into a generator of T. This is the monadic join operation.

// Example:
std::vector<Gen<int>> gens;
gens.push_back(gen::arbitrary<int>());
gens.push_back(gen::inRange(0, 100));
gens.push_back(gen::element(2, 3, 5, 7));

const auto someNumber = *gen::join(gen::elementOf(gens));

Gen<U> apply(Callable callable, Gen<Ts>... gens)

Calls the given callable with values generated by the given generators. Has tuple semantics when shrinking. Can be seen as a more C++ friendly version of applicative functor application.

// Example:
const auto id = *gen::apply([](char c, int x) {
  return std::string(1, c) + "-" + std::to_string(x);
}, gen::elementOf(std::string("ABCDEF")), gen::positive<int>());

Gen<U> cast(Gen<T> gen)

Returns a generator that casts the generated values to U using static_cast<U>(...).

// Example:
const auto integer = *gen::cast<float>(gen::arbitrary<int>());

Text

Gen<T> character()

Generates text characters. Commonly occurring characters have a higher probability of being generated.

// Example:
const auto c = *gen::character<char>();

Gen<String> string()

Generates strings. Essentially equivalent to gen::container<String>(gen::character<typename String::value_type>()) but a lot faster. If you need to use a custom character generator, use gen::container.

Numeric

Gen<T> inRange(T min, T max)

Generates a value between min (inclusive) and max (exclusive). The part of the range that is used grows with size and when size is 0, only min is generated. When shrinking, the value will shrink towards min.

// Example:
const auto age = *gen::inRange<int>(0, 100);

Gen<T> nonZero()

Generates a value that is not equal to 0.

// Example:
const auto x = *gen::nonZero<int>();

Gen<T> positive()

Generates a value which is greater than 0.

// Example:
const auto x = *gen::positive<int>();

Gen<T> negative()

Generates a value which is less than 0.

// Example:
const auto x = *gen::positive<int>();

Gen<T> nonNegative()

Generates a value which is not less than 0.

// Example:
const auto x = *gen::nonNegative<int>();

Containers

Gen<Container> container(Gen<Ts>... gens)

Generates an STL container containing elements generated by the given generator(s). For most containers you should only specify one generator but for containers that have both keys and values (i.e. maps), you need to supply two separate generators. The Container type parameter must be specified explicitly.

// Example:
const auto smallInts = *gen::container<std::vector<int>>(gen::inRange(0, 100));

Gen<Container> container(std::size_t count, Gen<Ts>... gens)

Like container(Gen<Ts>... gens) but generates containers of a fixed size count.

Gen<Container> unique(Gen<T> gen)

Generates a container of unique T. The Container type parameter must be specified explicitly.

// Example:
const auto uniqueInts = *gen::unique<std::vector<int>>(gen::arbitrary<int>());

Gen<Container> unique(std::size_t count, Gen<T> gen)

Like unique(Gen<T> gen) but generates containers of a fixed size count.

Gen<Container> uniqueBy(Gen<T> gen, F f)

Generates a container of T such that for every element e in the container, f(e) is unique. The Container type parameter must be specified explicitly.

// Example:
const auto uniquePeople = *gen::uniqueBy<std::vector<Person>>(
    gen::arbitrary<Person>(),
    [](const Person &p) {
      return std::make_pair(p.firstName, p.lastName);
    });

Gen<Container> uniqueBy(std::size_t count, Gen<T> gen, F f)

Like uniqueBy(Gen<T> gen, F f) but generates containers of a fixed size count.

Picking

Gen<Container::value_type> elementOf(Container container)

Randomly generates one of the elements of container. If container is empty, generation will fail.

// Example:
std::vector<std::string> names{"John", "Jane", "Bob", "Kate"};
const auto name = *gen::elementOf(names);

Gen<T> element(T... elements)

Randomly generates on of the given elements.

const auto prime = *gen::element(2, 3, 5, 7, 11, 13);

Gen<T> weightedElement(std::initializer_list<std::pair<std::size_t, T>> pairs)

Takes a sequence of weights paired with elements and generates one of the elements with probabilities according to the weights. For example, if one element has the weight 1 and another one the weight 2, the second one will be generated twice as often as the first.

// Example:
const auto animal = *gen::weightedElement({
    {4, std::string("horse")},
    {2, std::string("donkey")},
    {1, std::string("zebra")}});

Gen<Container::value_type> sizedElementOf(Container container)

Randomly chooses an element from an initial segment of the given container which is proportional to size. Effectively, elements are interpreted as increasing in size from start to end which reflects both with regards to the size but also when shrinking.

// Example:
const auto digit = *gen::sizedElementOf(std::string("0123456789"));

Gen<T> sizedElement(T... elements)

Like sizedElementOf(Container) but takes elements directly instead of a container.

// Example:
const auto digit = *gen::sizedElement(
    std::string("one"),
    std::string("two"),
    std::string("three"));

Gen<T> oneOf(Gen<Ts>... gens)

Randomly picks one of the given generators and generates a value using that.

// Example:
const auto name = *gen::oneOf(
    gen::element<std::string>("Jane", "Kate", "Ashley"),
    gen::element<std::string>("John", "Bob", "Nick"));

Gen<T> weightedOneOf(std::initializer_list<std::pair<std::size_t, Gen<T>>> pairs)

Like weightedElement(std::initializer_list<std::pair<std::size_t, T>>) but specifying generators instead of immediate values.

// Example:
const auto username = *gen::weightedOneOf({
    {1, gen::arbitrary<std::string>()},
    (4, gen::element<std::string>("foo", "bar", "baz"))});

Gen<T> sizedOneOf(Gen<T>... gens)

Like sizedElement(T...) but specifying generators instead of immediate values.

// Example:
const auto username = *gen::sizedOneOf(
    gen::element<std::string>("ant", "cockroach", "fly"),
    gen::element<std::string>("cat", "dog", "wolf"),
    gen::element<std::string>("elephant", "giraffe", "horse"));

Constructing

Gen<T> construct<T>(Gen<Args>... gens)

Generates objects of type T constructed using arguments from the given generators.

// Example:
const auto person = *gen::construct<Person>(
    gen::arbitrary<std::string>(), // Name
    gen::arbitrary<std::string>(), // Hometown
    gen::inRange(0, 100));         // Age

Gen<std::unique_ptr<T>> makeUnique<T>(Gen<Args>... gens)

Like construct but generates std::unique_ptrs to T instead.

// Example:
const auto person = *gen::makeUnique<Person>(
    gen::arbitrary<std::string>(), // Name
    gen::arbitrary<std::string>(), // Hometown
    gen::inRange(0, 100));         // Age

Gen<std::shared_ptr<T>> makeShared<T>(Gen<Args>... gens)

Like construct but generates std::shared_ptrs to T instead.

// Example:
const auto person = *gen::makeShared<Person>(
    gen::arbitrary<std::string>(), // Name
    gen::arbitrary<std::string>(), // Hometown
    gen::inRange(0, 100));         // Age

Gen<T> build(Gen<T> gen, Bindings... bindings)

Generates objects of type T by setting members of the object according to the specified bindings. A binding is created using gen::set(Member member, Gen<T> gen). Member should be a pointer to a member of that object and gen should be an appropriate generator for the type of the member. The member may be one of:

  • A member variable in which case gen should be a generator for the type of that variable.
  • A member function that takes a single argument. gen should be a generator for the type of that argument.
  • A member function that takes multiple arguments. gen should be a generator of tuples matching those arguments.

The generator may be omitted in which case gen::arbitrary<T>() will be used where T is the appropriate type of the generator.

The initial value is generated by the specified generator after which the bindings are applied.

// Example:
const auto person = *gen::build(gen::construct<Person>(genName()),
                                gen::set(&Person::age,
                                         gen::inRange(0, 100)),
                                gen::set(&Person::phoneNumber),
                                gen::set(&Person::setGender,
                                         gen::element(std::string("Male"),
                                                      std::string("Female"))),
                                gen::set(&Person::setAddress,
                                         gen::tuple(
                                             genCity(),
                                             genStreet(),
                                             genZipCode())));

Gen<T> build(Bindings... bindings)

Like build(Gen<T> gen, Bindings... bindings) but T is default constructed. Since no generator is specified, T cannot be deduced and must be explicitly specified.

Resizing

Gen<T> resize(int size, Gen<T> gen)

Returns a version of the given generator that always uses the specified size.

// Example:
const auto fullRangeInt = *gen::resize(kNominalSize, gen::arbitrary<int>());

Gen<T> scale(double scale, Gen<T> gen)

Returns a version of the given generator that scales the size by the given factor before passing it to the underlying generator.

// Example:
const auto smallerInt = *gen::scale(0.5, gen::arbitrary<int>());

Gen<T> withSize(Callable callable)

Creates a generator by taking a callable which gets passed the current size and is expected to return a generator. The type of the returned generator will be the same as the type returned by callable.

// Example:
const auto sizedInt = *gen::withSize([](int size) {
  return gen::inRange(0, size);
});

Miscellaneous

Gen<T> exec(Callable callable)

This allows you to use the same semantics that is used for properties when creating a generator. However, instead of callable yielding a success or a failure, it should return the generated value.

Inside the specified callable, you can use operator* of Gen<T> to pick values. In this way, you can have the values of one generator depend on the other. Just like with properties, if callable has any arguments, those will be generate with tuple-like semantics using gen::arbitrary<T>() for the appropriate types.

Note: If there are other combinators you can use instead of gen::exec, you are encouraged to do so. Because of its implementation, gen::exec is likely to have both worse compile time performance and runtime performance than any options. In addition, if the picked values do not actually depend on each other, RapidCheck will be unnecessarily restricted in the way that it can shrink them on failure.

// Example:
const auto name = *gen::exec([](const Address &address) {
  const auto gender = *gen::element(kMale, kFemale);
  const auto name = *genName(gender);
  return Person(name, gender, address);
});

Gen<T> lazy(Callable callable)

Returns a generator which delegates generation to the generator lazily returned by callable. This is useful when creating generators for recursive data types such as trees. The type of the returned generator is the same as the type of the generator returned by callable.

// Example:
Gen<LinkedList> genLinkedList() {
  // NOTE: gen::lazy is required, otherwise, we'll go into infinite recursion
  //       that never terminates
  return gen::oneOf(gen::just(LinkedList()),
                    gen::construct<LinkedList>(gen::arbitrary<int>(),
                                               gen::lazy(&genLinkedList)));
}

Gen<Maybe<T>> maybe(Gen<T> gen)

Generates a Maybe of the type of the given generator. At small sizes, the frequency of Nothing is greater than at larger sizes.

// Example:
const auto maybeSmallInt = *gen::maybe(gen::inRange(0, 100));

Gen<T> noShrink(Gen<T> gen)

Returns a generator which generates the same values as gen but RapidCheck will not try to shrink it when a failing case is found.

// Example:
const auto fixedInt = *gen::noShrink(gen::arbitrary<int>());

Gen<T> shrink(Gen<T> gen, Shrink shrink)

Returns a version of the given generator that will try shrinks returned by the given callable after the regular shrinks have been tried. Use this to add further shrinking to an existing generator.

// Examples:
const auto treeNode =
    *gen::shrink(gen::arbitrary<TreeNode>(),
                 [](TreeNode &&node) {
                   // Shrink by flattening single-child nodes
                   if (node.children.size() == 1) {
                     return seq::just(std::move(node.children[0]));
                   }

                   return Seq<TreeNode>();
                 });