Arbitrary
Arbitraries - The Default Generators¶
What makes defaults so special¶
An Arbitrary<T> or its alias Arbi<T> is a generator type (that also coerces to GenFunction<T>).
Arbitraries are specially treated in cppproptest. An arbitrary serves as globally defined default generator for the type. If a default generator for a type is available, cppproptest can use the default generator to generate a value of that type, if no generator has been specified.
forAll([](T1 t1, T2 t2, ..., TN tn) {
// property function body
}, /* custom generators for T1, ..., TN */);
For each of the parameter types of a property function, forAll() requires either a custom generator is provided as an argument, or a conforming Arbitrary<T> class has been defined in proptest namespace. A custom generator can be supplied in the forAll() function arguments next to the property function, as in the same order of parameters of the property function. If it hasn't been supplied, forAll() looks up the default generator - the arbitrary - and uses it instead. If there were no proptest::Arbitrary<T> defined, the compilation would fail.
// if there is no default generator available, you must provide a generator for the type SomeNewType.
forAll([](SomeNewType x) {
}, someNewTypeGen);
// explicit generators should be supplied in same order as parameter types of property function
forAll([](SomeNewType x, SomeOtherType y) {
}, someNewTypeGen, SomeOtherTypeGen);
// if there is a default generator (Arbitrary<SomeType>) available, you can use that generator by omitting the argument
forAll([](SomeType x) {
});
// Partially specifying generators is also allowed. Other types will be generated with arbitraries
forAll([](SomeNewType x, SomeOtherType y) {
}, someNewTypeGen); // y will be generated with Arbitary<SomeOtherType>
Built-in Arbitraries¶
cppproptest provides a set of built-in generators for generation of types that are often used in practice. These built-in generators are in the form of Arbitraries. You can access an arbitrary for T with proptest::Arbitrary<T>. Some of them are defined as template classes with type parameters for universality. For example, protest::Arbitrary<vector<T>> defines a generator for a vector of any given type T, assuming you have an arbitrary for T already defined, or you have provided a custom generator for T as an argument for the vector arbitrary's constructor.
auto intVectorGenA = proptest::Arbitrary<std::vector<int>>(); // defaults to proptest::Arbitrary<int>()
auto intVectorGenB = proptest::Arbitrary<std::vector<int>>(gen::inRange(1,10)); // custom element generator
auto fooVectorGen = proptest::Arbitrary<std::vector<Foo>>(fooGen); // custom generator for T
auto fooVectorGen = proptest::Arbitrary<std::vector<Foo>>(); // BUILD fails: proptest::Arbitrary<Foo> not available
Arbitraries of Commonly used standard containers are defined with type parameters so that you can generate such containers for the elemental types you desire.
Here's quick reference for built-in arbitraries:
| Arbitrary | Alias | Examples |
|---|---|---|
Arbi<bool>() |
gen::boolean() |
true or false |
Arbi<char>() |
gen::character() |
'c' or '%' |
Arbi<int>(), Arbi<uint64_t>(), ... |
gen::int32(), gen::uint64()... |
12 or -1133 |
Arbi<float>(), Arbi<double>() |
gen::float32(), gen::float64() |
3.4 or -1.4e3 |
- Boolean type:
bool - Character type:
char - Integral types:
int8_t,uint8_t,int16_t,uint16_t,int32_t,uint32_t,int64_t,uint64_t - Floating point types:
float,double— Named parameters:Arbi<float>({.nanProb = 0.1}). See Floating Point Generators.
| Arbitrary | Alias | Examples |
|---|---|---|
Arbi<std::string>(), Arbi<UTF8String>() |
gen::string(), gen::utf8string() |
"world" or "あ叶葉말" |
Arbi<std::pair<T1,T2>>() |
gen::pair<T1,T2>() |
{1, "xv"} or {true, 3.4} |
Arbi<std::tuple<Ts...>>() |
gen::tuple<Ts...>() |
{1, "xv", true} or {true, 3.4} |
Arbi<std::list<T>>() |
gen::list<T>() |
{10, -4, 0} or {"k", "&"} |
Arbi<std::vector<T>>() |
gen::vector<T>() |
{10, -4, 0} or {"k", "&"} |
Arbi<std::set<T>>() |
gen::set<T>() |
set {1, 3, 4} but not {1, 1, 3} |
Arbi<std::map<K,V>>() |
gen::map<K,V>() |
map of "Bob" -> 25, "Alice" -> 30 |
- String types:
std::string(defaults to generate ASCII character strings in [0x01, 0x7F] range)UTF8String(a class which extendsstd::stringand can be used to generate valid UTF-8 strings by usingArbi<UTF8String>)CESU8String(similar to UTF-8, but can be used to generate valid CESU-8 strings)UTF16BEStringandUTF16LEStringfor UTF-16 big and little endian strings. CESU-8 and Unicode types produce full unicode code point range of [0x1, 0x10FFFF], excluding forbidden surrogate code points ([0xD800, 0xDFFF])
- Shared pointers:
std::shared_ptr<T>where anArbi<T>or a custom generator forTis available. It's also useful for generating polymorphic types.struct Action { virtual int get() = 0; }; struct Insert : Action { virtual void get() { return 1; } }; struct Delete : Action { virtual void get() { return 2; } }; Generator<std::shared_ptr<Action>>(...); // can hold both Insert and Delete -
Standard containers:
std::string,std::vector,std::list,std::set,std::pair,std::tuple,std::map-
Arbitraries for containers can optionally take a custom generator for their elemental types. If no custom generator for elemental type
Tis provided,Arbitrary<T>will be used instead.// You can supply a specific generator for integers auto vecInt0to100 = Arbi<std::vector<int>>(gen::interval<int>(0,100)); // otherwise, Arbi<int> is used auto vecInt = Arbi<std::vector<int>>(); // Named parameters (util::ContainerGenConfig<T>) auto vecGen = Arbi<std::vector<int>>({.minSize = 5, .maxSize = 20}); auto vecGen2 = Arbi<std::vector<int>>({.elemGen = gen::interval<int>(1, 100), .minSize = 5, .maxSize = 20}); // String arbitraries: ContainerGenConfig<char> for std::string, ContainerGenConfig<uint32_t> for UTF-8/16/CESU-8 auto strGen = Arbi<std::string>({.minSize = 5, .maxSize = 20}); auto utf8Gen = Arbi<UTF8String>({.elemGen = gen::interval<uint32_t>('0', '9'), .minSize = 1, .maxSize = 10}); // string aarbitraries also take optional element generator auto uppercaseGen = Arbi<std::string>(gen::interval('A', 'Z')); auto alphabetGen = Arbi<std::string>(gen::unionOf(gen::interval('A', 'Z'), gen::interval('a','z'))); -
Arbi<std::map<K,V>>provides a pair generator and size constraints. UsesetPairGenor the config constructor.auto mapGen = Arbi<std::map<int,int>>(); mapGen.setPairGen(gen::pair(gen::interval<int>(0,100), gen::interval<int>(-100, 100))); // Named parameters (util::MapGenConfig<K,V>) auto mapGen2 = Arbi<std::map<int,int>>({.minSize = 5, .maxSize = 20}); auto mapGen3 = Arbi<std::map<int,int>>({.keyGen = gen::int32(), .valueGen = gen::int32(), .minSize = 5, .maxSize = 20}); -
Containers provide methods for configuring the desired sizes
setMinSize(size),setMaxSize(size)for restricting the container to specific range of sizessetSize(size)for restricting the container to a specific size
auto vecInt = Arbi<std::vector<int>>(); vecInt.setSize(10); // 1) generated vector will always have size 10 vecInt.setMinSize(1); // 2) generated vector will have size >= 1 vecInt.setMaxSize(10); // generated vector will have size <= 10 vecInt.setSize(1, 10); // 3) generated vector will have size >= 1 and size <= 10
-
As long as a generator for type T is available (either by Arbitary<T> defined or a custom generator provided), you can generate a container of that type, however complex the type T is, even including another container type. This means you can readily generate a random vector<vector<int>>, as Arbitrary<vector<T>> and Arbitrary<int> are both available.
Arbi<std::vector<std::vector<int>>>(); // generates a vector of vector of ints.
Arbi<std::map<std::string, std::vector<std::set<int>>>>();
This design makes arbitraries of cppproptest composable, meaning that they can be easily reusable as building blocks for a new generator.
Defining an Arbitrary¶
With template specialization, new proptest::Arbi<T> (or its alias proptest::Arbitrary<T>) for type T can be defined, if it hasn't been already defined yet. By defining an Arbitrary, you are effectively adding a default generator for a type.
Following shows an example of defining an Arbitrary. Note that it should be defined under proptest namespace in order to be recognized and accessible by the library.
namespace proptest { // you should define your Arbi<T> inside this namespace
// define a template specialization of Arbi for Car type
// by extending ArbiBase, you are decorating your arbitrary with standard methods (map, flatMap, filter, etc.)
template <>
struct Arbi<Car> : ArbiBase<Car> {
Shrinkable<Car> operator()(Random& rand) const {
bool isAutomatic = rand.getRandomBool();
return make_shrinkable<Car>(isAutomatic); // make_shrinkable creates a Car object by calling Car's constructor with 1 boolean parameter
}
};
}
Although you can define an arbitrary as shown in this example, it's only required to do so if you desire to have a default generator for the type.
Utility methods of Arbitrary¶
Arbitrary<T> provides useful helpers for creating new generators from existing ones[^generatorT]. filter is such a helper. It restrictively generates values that satisfy a criteria function. Following shows an even number generator from the integer Arbitrary.
// generates any integers
auto anyIntGen = Arbi<int>();
// generates even integers
auto evenGen = anyIntGen.filter([](int num) {
return num % 2 == 0;
});
You can find the full list of such helpers in Utility methods in standard generators.