arbitrary
stdlib/core/arbitrary.hk: the Arbitrary trait and its built-in instances.
Arbitrary is the bridge between a type and the property-testing generator core (§ extra gen): a type is Arbitrary when it can describe how to draw a value of itself as a Gen<Self>. The runner samples each typed test parameter through its instance, and @derive(Arbitrary) composes a struct's / sum's instance from its fields'.
Dispatch is static and type-directed, as Decode's is (core decode): i32.arbitrary() selects the i32 instance; a bounded T: Arbitrary calls T.arbitrary(). Generation is pure: the Gen threads a seeded source.
Covered here: bool, the fixed-width integers, the arbitrary-precision int (i64-range magnitudes for now), bytes, string (full Unicode), decimal, rational, and the Option / List / Map / Entry composites: every builtin type is Arbitrary.
@derive(Arbitrary) composes a type's instance from its parts'. It supports structs (including self-recursive ones, through Option/List) and sums, generic and self-recursive ones included, as well as mutual/indirect recursion across distinct types: both derive leaf-biased sized generation (Gen.sized / Gen.one_of / Gen.recur), and generation therefore terminates even where a type refers back to itself directly or through another type. A recursive field shrinks the size budget each level (recur). For mutual recursion the deriver follows the module's type-reference edges to shrink every field that reaches the type, and the budget therefore halves around the cycle. The size-aware Option and List instances below bottom out, at None and the empty list, at the size floor, and the recursion has a base case. A generic type's params are each bounded by Arbitrary, and a bound-param part draws through T.arbitrary().
Arbitrary
trait Arbitrary
arbitrary
def arbitrary() -> Gen<Self>
A generator that draws an arbitrary value of Self. @no-doctest: produces a Gen, a generator and no concrete value; the module's test pack exercises it
impl Arbitrary<bool>
arbitrary
def arbitrary() -> Gen<bool>
Draws true or false with even odds, one uint64 per sample.
@no-doctest: draws with even odds, and odds are not a thing one sample can show
impl Arbitrary<u64>
arbitrary
def arbitrary() -> Gen<u64>
Draws the full unsigned 64-bit range, uniformly.
@no-doctest: draws the full u64 range; the type is the whole statement, and a sample would only pin the PRNG
impl Arbitrary<u32>
arbitrary
def arbitrary() -> Gen<u32>
Draws the full u32 range by truncating one uint64.
@no-doctest: draws the full u32 range; see Arbitrary<u64>
impl Arbitrary<u16>
arbitrary
def arbitrary() -> Gen<u16>
Draws the full u16 range by truncating one uint64.
@no-doctest: draws the full u16 range; see Arbitrary<u64>
impl Arbitrary<u8>
arbitrary
def arbitrary() -> Gen<u8>
Draws the full u8 range by truncating one uint64.
@no-doctest: draws the full u8 range; see Arbitrary<u64>
impl Arbitrary<i64>
arbitrary
def arbitrary() -> Gen<i64>
Draws the full signed 64-bit range, uniformly.
@no-doctest: draws the full i64 range; see Arbitrary<u64>
impl Arbitrary<i32>
arbitrary
def arbitrary() -> Gen<i32>
Draws the full i32 range by truncating one uint64.
@no-doctest: draws the full i32 range; see Arbitrary<u64>
impl Arbitrary<i16>
arbitrary
def arbitrary() -> Gen<i16>
Draws the full i16 range by truncating one uint64.
@no-doctest: draws the full i16 range; see Arbitrary<u64>
impl Arbitrary<i8>
arbitrary
def arbitrary() -> Gen<i8>
Draws the full i8 range by truncating one uint64.
@no-doctest: draws the full i8 range; see Arbitrary<u64>
impl Arbitrary<int>
arbitrary
def arbitrary() -> Gen<int>
Draws an arbitrary-precision int across the signed 64-bit range.
Magnitudes wider than i64 are still a follow-up, and a draw fits there:
v = int.arbitrary().run(seeded_source(1u64)).value.unwrap_or(0)
v.try_to_i64() == None => false
impl Arbitrary<f64>
arbitrary
def arbitrary() -> Gen<f64>
A finite f64: whole numbers and readable fractions most of the time, the IEEE boundary values often enough to hit them, and full bit-pattern coverage for the rest.
NaN and the infinities are not drawn; see any_f64, which adds them. A property comparing a drawn float to itself is the first thing anybody writes, and NaN breaks reflexivity, and a default that produced it would teach that generated-value testing is fiddly in place of that NaN is special. Ask for them and you get them; the asking is what marks the intent.
Never NaN, and a first property's reflexivity is therefore safe:
v = f64.arbitrary().run(seeded_source(1u64)).value.unwrap_or(0.0f64)
v == v => true
finite_f64
def finite_f64() -> Gen<f64>
Every finite f64, in the mixture Arbitrary<f64> draws. Named because a property that wants floats but not specials should not have to spell the mixture out.
The weights are a tuning choice and no law: they follow the practical art (Hypothesis, proptest) in spending most draws on values a human can read and the rest on coverage. Change them if a real property wants a different balance. @no-doctest: draws from a choice source; the value depends on the seed
any_f64
def any_f64() -> Gen<f64>
finite_f64 plus NaN, +inf and -inf, about one draw in ten.
The specials are what break float code: a comparison that assumes trichotomy, or a sum that swallows an infinity, and a property about IEEE behaviour should draw from here. Anything comparing a value to itself should not. @no-doctest: draws from a choice source; the value depends on the seed
wholef64
def _whole_f64() -> Gen<f64>
Whole numbers, magnitude first and sign second so the smallest choice is 0.0f64 and not the most negative value an int_range would start at.
fractionf64
def _fraction_f64() -> Gen<f64>
Readable fractions: a whole number over a small power of two or ten, and a shrunk counterexample therefore reads 0.25 or 1.5 and not 0.30000000000000004.
_divisor
def _divisor(d: i32) -> f64
notablef64
def _notable_f64() -> Gen<f64>
The values a float implementation gets wrong at the edges: both zeros, both units, the smallest subnormal and the largest normal. Built from bit patterns, f64 having no constants for them.
finitebits_f64
def _finite_bits_f64() -> Gen<f64>
Full bit-pattern coverage, forced finite. An all-ones exponent is what makes a pattern NaN or infinite, and the one case is therefore rewritten in place of rejected: filtering would burn the draw and, at a 1-in-2048 rate, do it often enough to matter.
forcefinite
def _force_finite(u: u64) -> u64
specialf64
def _special_f64() -> Gen<f64>
NaN and the two infinities, by bit pattern: exponent all ones, mantissa zero for the infinities and non-zero for NaN.
impl Arbitrary<f32>
arbitrary
def arbitrary() -> Gen<f32>
A finite f32, drawn the way Arbitrary<f64> draws: a mixture rather than a bit reinterpretation.
The reason is the same one, at 32 bits. For an integer the bit pattern is the magnitude, and a smaller choice shrinks toward zero; for a float the patterns just above zero are the subnormals, around 1e-45 here, and a bit draw would shrink every counterexample toward a denormal nobody can read.
NaN and the infinities are not drawn - any_f32 adds them, for the reason any_f64 exists.
Never NaN, as for f64:
v = f32.arbitrary().run(seeded_source(1u64)).value.unwrap_or(0.0f32)
v == v => true
finite_f32
def finite_f32() -> Gen<f32>
Every finite f32, in the mixture Arbitrary<f32> draws. The f64 twin is finite_f64, and the weights match it. @no-doctest: draws from a choice source; the value depends on the seed
any_f32
def any_f32() -> Gen<f32>
finite_f32 plus NaN, +inf and -inf, about one draw in ten. @no-doctest: draws from a choice source; the value depends on the seed
wholef32
def _whole_f32() -> Gen<f32>
Whole numbers, magnitude before sign so the smallest choice is 0.0f32.
f32of
def _f32_of(n: i32) -> f32
An i32 as an f32, by building the IEEE encoding directly.
No name in core converts into f32, and this is therefore no cast: the value is encoded. For a non-negative whole number below 2^24 that encoding is exact and short: the highest set bit is the exponent, and the bits under it are the mantissa, shifted up into the mantissa field. Going through a decimal string and f32.parse would also work, and is what this is not: a string round-trip per draw, in a generator.
wholebits
def _whole_bits(n: u32) -> u32
highestbit
def _highest_bit(n: u32) -> u32
The index of the highest set bit, for a non-zero n.
fractionf32
def _fraction_f32() -> Gen<f32>
Readable fractions: a whole number over a small power of two or ten.
divisorf32
def _divisor_f32(d: i32) -> f32
notablef32
def _notable_f32() -> Gen<f32>
The values a float implementation gets wrong at the edges, at 32 bits: both zeros, both units, the smallest subnormal and the largest normal.
finitebits_f32
def _finite_bits_f32() -> Gen<f32>
Full bit-pattern coverage, forced finite. f32's exponent is 8 bits at offset 23, the all-ones exponent is therefore 255, and clearing bit 23 takes it to 254, the largest finite one, leaving every other bit alone.
forcefinite_f32
def _force_finite_f32(u: u32) -> u32
specialf32
def _special_f32() -> Gen<f32>
NaN and the two infinities, by bit pattern.
impl<T: Arbitrary> Arbitrary<Option<T>>
arbitrary
def arbitrary() -> Gen<Option<T>>
Draws None about one time in four, else Some of a drawn T.
At the size floor forces the base case, which is what makes a type that recurses through it terminate:
g: Gen<Option<i32>> = Option.arbitrary()
g.run(seeded_source(1u64).with_size(0i32)).value.unwrap_or(Some(9i32)) => None
impl<T: Arbitrary> Arbitrary<List<T>>
arbitrary
def arbitrary() -> Gen<List<T>>
Draws a list of up to 16 elements, shrinking toward empty.
Empty at the size floor, and capped at 16 above it:
g: Gen<List<i32>> = List.arbitrary()
g.run(seeded_source(1u64).with_size(0i32)).value.unwrap_or(List.empty()).length => 0
g.run(seeded_source(1u64)).value.unwrap_or(List.empty()).length <= 16 => true
impl Arbitrary<bytes>
arbitrary
def arbitrary() -> Gen<bytes>
Draws a buffer of up to 16 arbitrary bytes.
Empty at the size floor, and capped at 16 above it:
bytes.arbitrary().run(seeded_source(1u64).with_size(0i32)).value.unwrap_or("x".to_bytes()).length => 0
bytes.arbitrary().run(seeded_source(1u64)).value.unwrap_or("x".to_bytes()).length <= 16 => true
impl Arbitrary<string>
arbitrary
def arbitrary() -> Gen<string>
Draws up to 16 arbitrary Unicode characters as valid UTF-8.
Empty at the size floor:
string.arbitrary().run(seeded_source(1u64).with_size(0i32)).value.unwrap_or("x") => ""
impl Arbitrary<rational>
arbitrary
def arbitrary() -> Gen<rational>
Draws an exact ratio of two int draws, with a non-zero denominator.
The denominator is built as abs(d) + 1, and it is therefore never zero and the draw is a real ratio and no sentinel:
r = rational.arbitrary().run(seeded_source(1u64)).value.unwrap_or(1.to_rational())
r.denominator > 0 => true
impl Arbitrary<decimal>
arbitrary
def arbitrary() -> Gen<decimal>
Draws a decimal from an int mantissa and a small non-negative scale.
The scale is a small non-negative draw, and fractional decimals therefore appear at all - decimal has no division to build them with:
d = decimal.arbitrary().run(seeded_source(1u64)).value.unwrap_or(0.to_decimal())
d.scale >= 0 and d.scale <= 12 => true
impl<K: Arbitrary, V: Arbitrary> Arbitrary<Entry<K, V>>
arbitrary
def arbitrary() -> Gen<Entry<K, V>>
Draws a key and a value independently and pairs them.
@no-doctest: pairs an arbitrary key with an arbitrary value, which is what the type says; the parts' own instances carry the examples
impl<K: Hash + Arbitrary, V: Arbitrary> Arbitrary<Map<K, V>>
arbitrary
def arbitrary() -> Gen<Map<K, V>>
Draws up to 16 entries; a repeated key takes the last-drawn value.
Bounded by the List draw underneath it, and at the size floor it is empty:
g: Gen<Map<string, i32>> = Map.arbitrary()
g.run(seeded_source(1u64).with_size(0i32)).value.unwrap_or(Map.empty()).length => 0