testable
Class #
THIS FILE IS SYNCHRONIZED WITH MATHLIB4. Any changes to this file require a corresponding PR to mathlib4.
Testable propositions have a procedure that can generate counter-examples together with a proof that they invalidate the proposition.
This is a port of the Haskell QuickCheck library.
Creating Customized Instances #
The type classes testable
and sampleable
are the means by which
slim_check
creates samples and tests them. For instance, the proposition
∀ i j : ℕ, i ≤ j
has a testable
instance because ℕ
is sampleable
and i ≤ j
is decidable. Once slim_check
finds the testable
instance, it can start using the instance to repeatedly creating samples
and checking whether they satisfy the property. This allows the
user to create new instances and apply slim_check
to new situations.
Polymorphism #
The property testable.check (∀ (α : Type) (xs ys : list α), xs ++ ys = ys ++ xs)
shows us that type-polymorphic properties can be
tested. α
is instantiated with ℤ
first and then tested as normal
monomorphic properties.
The monomorphisation limits the applicability of slim_check
to
polymorphic properties that can be stated about integers. The
limitation may be lifted in the future but, for now, if
one wishes to use a different type than ℤ
, one has to refer to
the desired type.
What do I do if I'm testing a property about my newly defined type? #
Let us consider a type made for a new formalization:
structure my_type :=
(x y : ℕ)
(h : x ≤ y)
How do we test a property about my_type
? For instance, let us consider
testable.check $ ∀ a b : my_type, a.y ≤ b.x → a.x ≤ b.y
. Writing this
property as is will give us an error because we do not have an instance
of sampleable my_type
. We can define one as follows:
instance : sampleable my_type :=
{ sample := do
x ← sample ℕ,
xy_diff ← sample ℕ,
return { x := x, y := x + xy_diff, h := /- some proof -/ } }
We can see that the instance is very simple because our type is built
up from other type that have sampleable
instances. sampleable
also
has a shrink
method but it is optional. We may want to implement one
for ease of testing as:
/- ... -/
shrink := λ ⟨x,y,h⟩, (λ ⟨x,y⟩, { x := x, y := x + y, h := /- proof -/}) <$> shrink (x, y - x) }
Again, we take advantage of the fact that other types have useful
shrink
implementations, in this case prod
.
Optimizing the sampling #
Some properties are guarded by a proposition. For instance, recall this example:
#eval testable.check (∀ x : ℕ, 2 ∣ x → x < 100)
When testing the above example, we generate a natural number, we check
that it is even and test it if it is even or throw it away and start
over otherwise. Statistically, we can expect half of our samples to be
thrown away by such a filter. Sometimes, the filter is more
restrictive. For instance we might need x
to be a prime
number. This would cause most of our samples to be discarded.
We can help slim_check
find good samples by providing specialized
sampleable instances. Below, we show an instance for the subtype
of even natural numbers. This means that, when producing
a sample, it is forced to produce a proof that it is even.
instance {k : ℕ} [fact (0 < k)] : sampleable { x : ℕ // k ∣ x } :=
{ sample := do { n ← sample ℕ, pure ⟨k*n, dvd_mul_right _ _⟩ },
shrink := λ ⟨x,h⟩, (λ y, ⟨k*y, dvd_mul_right _ _⟩) <$> shrink x }
Such instance will be preferred when testing a proposition of the shape
∀ x : T, p x → q
We can observe the effect by enabling tracing:
/- no specialized sampling -/
#eval testable.check (∀ x : ℕ, 2 ∣ x → x < 100) { trace_discarded := tt }
-- discard
-- x := 1
-- discard
-- x := 41
-- discard
-- x := 3
-- discard
-- x := 5
-- discard
-- x := 5
-- discard
-- x := 197
-- discard
-- x := 469
-- discard
-- x := 9
-- discard
-- ===================
-- Found problems!
-- x := 552
-- -------------------
/- let us define a specialized sampling instance -/
instance {k : ℕ} : sampleable { x : ℕ // k ∣ x } :=
{ sample := do { n ← sample ℕ, pure ⟨k*n, dvd_mul_right _ _⟩ },
shrink := λ ⟨x,h⟩, (λ y, ⟨k*y, dvd_mul_right _ _⟩) <$> shrink x }
#eval testable.check (∀ x : ℕ, 2 ∣ x → x < 100) { enable_tracing := tt }
-- ===================
-- Found problems!
-- x := 358
-- -------------------
Similarly, it is common to write properties of the form: ∀ i j, i ≤ j → ...
as the following example show:
#eval check (∀ i j k : ℕ, j < k → i - k < i - j)
Without subtype instances, the above property discards many samples
because j < k
does not hold. Fortunately, we have appropriate
instance to choose k
intelligently.
Main definitions #
testable
classtestable.check
: a way to test a proposition using random examples
Tags #
random testing
References #
- success : Π {p : Prop}, psum unit p → slim_check.test_result p
- gave_up : Π {p : Prop}, ℕ → slim_check.test_result p
- failure : Π {p : Prop}, ¬p → list string → ℕ → slim_check.test_result p
Result of trying to disprove p
The constructors are:
success : (psum unit p) → test_result
succeed when we find another example satisfyingp
Insuccess h
,h
is an optional proof of the proposition. Without the proof, all we know is that we found one example wherep
holds. With a proof, the one test was sufficient to prove thatp
holds and we do not need to keep finding examples.gave_up {} : ℕ → test_result
give up when a well-formed example cannot be generated.gave_up n
tells us thatn
invalid examples were tried. Above 100, we give up on the proposition and report that we did not find a way to properly test it.failure : ¬ p → (list string) → ℕ → test_result
a counter-example top
; the strings specify values for the relevant variables.failure h vs n
also carries a proof thatp
does not hold. This way, we can guarantee that there will be no false positive. The last component,n
, is the number of times that the counter-example was shrunk.
Instances for slim_check.test_result
- slim_check.test_result.has_sizeof_inst
- slim_check.test_result.inhabited
- slim_check.test_result.has_to_string
format a test_result
as a string.
Equations
- (slim_check.test_result.failure a vs _x).to_string = "failed " ++ to_string vs ++ ""
- (slim_check.test_result.gave_up n).to_string = "gave up " ++ to_string n ++ " times"
- (slim_check.test_result.success (psum.inr h)).to_string = "success (with proof)"
- (slim_check.test_result.success (psum.inl unit.star())).to_string = "success (without proof)"
- num_inst : ℕ
- max_size : ℕ
- trace_discarded : bool
- trace_success : bool
- trace_shrink : bool
- trace_shrink_candidates : bool
- random_seed : option ℕ
- quiet : bool
configuration for testing a property
Instances for slim_check.slim_check_cfg
- slim_check.slim_check_cfg.has_sizeof_inst
- slim_check.slim_check_cfg.has_reflect
- slim_check.slim_check_cfg.inhabited
printable_prop p
allows one to print a proposition so that
slim_check
can indicate how values relate to each other.
Instances of this typeclass
- slim_check.default_printable_prop
- slim_check.eq.printable_prop
- slim_check.ne.printable_prop
- slim_check.le.printable_prop
- slim_check.lt.printable_prop
- slim_check.perm.printable_prop
- slim_check.and.printable_prop
- slim_check.or.printable_prop
- slim_check.iff.printable_prop
- slim_check.imp.printable_prop
- slim_check.not.printable_prop
- slim_check.true.printable_prop
- slim_check.false.printable_prop
- slim_check.bool.printable_prop
Instances of other typeclasses for slim_check.printable_prop
- slim_check.printable_prop.has_sizeof_inst
Equations
testable p
uses random examples to try to disprove p
.
Instances of this typeclass
- slim_check.decidable_testable
- slim_check.and_testable
- slim_check.or_testable
- slim_check.iff_testable
- slim_check.dec_guard_testable
- slim_check.var_testable
- slim_check.prop_var_testable
- slim_check.injective.testable
- slim_check.monotone.testable
- slim_check.antitone.testable
- slim_check.all_types_testable
- slim_check.exists_testable
- slim_check.subtype_var_testable
- slim_check.unused_var_testable
- slim_check.test_forall_in_list
Instances of other typeclasses for slim_check.testable
- slim_check.testable.has_sizeof_inst
applicative combinator proof carrying test results
Equations
- slim_check.combine (psum.inr f) (psum.inr x) = psum.inr _
- slim_check.combine (psum.inr val) (psum.inl val_1) = psum.inl unit.star()
- slim_check.combine (psum.inl val) _x = psum.inl unit.star()
Combine the test result for properties p
and q
to create a test for their conjunction.
Equations
- slim_check.and_counter_example (slim_check.test_result.failure Hce xs n) (slim_check.test_result.failure ᾰ ᾰ_1 ᾰ_2) = slim_check.test_result.failure _ xs n
- slim_check.and_counter_example (slim_check.test_result.failure Hce xs n) (slim_check.test_result.gave_up ᾰ) = slim_check.test_result.failure _ xs n
- slim_check.and_counter_example (slim_check.test_result.failure Hce xs n) (slim_check.test_result.success ᾰ) = slim_check.test_result.failure _ xs n
- slim_check.and_counter_example (slim_check.test_result.gave_up ᾰ) (slim_check.test_result.failure Hce xs n) = slim_check.test_result.failure _ xs n
- slim_check.and_counter_example (slim_check.test_result.gave_up n) (slim_check.test_result.gave_up m) = slim_check.test_result.gave_up (n + m)
- slim_check.and_counter_example (slim_check.test_result.gave_up n) (slim_check.test_result.success ᾰ) = slim_check.test_result.gave_up n
- slim_check.and_counter_example (slim_check.test_result.success ᾰ) (slim_check.test_result.failure Hce xs n) = slim_check.test_result.failure _ xs n
- slim_check.and_counter_example (slim_check.test_result.success ᾰ) (slim_check.test_result.gave_up n) = slim_check.test_result.gave_up n
- slim_check.and_counter_example (slim_check.test_result.success xs) (slim_check.test_result.success ys) = slim_check.test_result.success (slim_check.combine (slim_check.combine (psum.inr and.intro) xs) ys)
Combine the test result for properties p
and q
to create a test for their disjunction
Equations
- slim_check.or_counter_example (slim_check.test_result.failure Hce xs n) (slim_check.test_result.failure Hce' ys n') = slim_check.test_result.failure _ (xs ++ ys) (n + n')
- slim_check.or_counter_example (slim_check.test_result.failure ᾰ ᾰ_1 ᾰ_2) (slim_check.test_result.gave_up n) = slim_check.test_result.gave_up n
- slim_check.or_counter_example (slim_check.test_result.failure ᾰ ᾰ_1 ᾰ_2) (slim_check.test_result.success ys) = slim_check.test_result.success (slim_check.combine (psum.inr or.inr) ys)
- slim_check.or_counter_example (slim_check.test_result.gave_up n) (slim_check.test_result.failure ᾰ ᾰ_1 ᾰ_2) = slim_check.test_result.gave_up n
- slim_check.or_counter_example (slim_check.test_result.gave_up n) (slim_check.test_result.gave_up m) = slim_check.test_result.gave_up (n + m)
- slim_check.or_counter_example (slim_check.test_result.gave_up ᾰ) (slim_check.test_result.success ys) = slim_check.test_result.success (slim_check.combine (psum.inr or.inr) ys)
- slim_check.or_counter_example (slim_check.test_result.success xs) (slim_check.test_result.failure ᾰ ᾰ_1 ᾰ_2) = slim_check.test_result.success (slim_check.combine (psum.inr or.inl) xs)
- slim_check.or_counter_example (slim_check.test_result.success xs) (slim_check.test_result.gave_up ᾰ) = slim_check.test_result.success (slim_check.combine (psum.inr or.inl) xs)
- slim_check.or_counter_example (slim_check.test_result.success xs) (slim_check.test_result.success ᾰ) = slim_check.test_result.success (slim_check.combine (psum.inr or.inl) xs)
If q → p
, then ¬ p → ¬ q
which means that testing p
can allow us
to find counter-examples to q
.
Equations
- slim_check.convert_counter_example h (slim_check.test_result.failure Hce xs n) _x = slim_check.test_result.failure _ xs n
- slim_check.convert_counter_example h (slim_check.test_result.gave_up n) _x = slim_check.test_result.gave_up n
- slim_check.convert_counter_example h (slim_check.test_result.success Hp) Hpq = slim_check.test_result.success (slim_check.combine Hpq Hp)
Test q
by testing p
and proving the equivalence between the two.
Equations
When we assign a value to a universally quantified variable, we record that value using this function so that our counter-examples can be informative.
Equations
- slim_check.add_to_counter_example x h (slim_check.test_result.failure Hce xs n) _x = slim_check.test_result.failure _ (x :: xs) n
- slim_check.add_to_counter_example x h (slim_check.test_result.gave_up ᾰ) hpq = slim_check.convert_counter_example h (slim_check.test_result.gave_up ᾰ) hpq
- slim_check.add_to_counter_example x h (slim_check.test_result.success ᾰ) hpq = slim_check.convert_counter_example h (slim_check.test_result.success ᾰ) hpq
Add some formatting to the information recorded by add_to_counter_example
.
Equations
- slim_check.add_var_to_counter_example var x h = slim_check.add_to_counter_example (var ++ " := " ++ repr x) h
Gadget used to introspect the name of bound variables.
It is used with the testable
typeclass so that
testable (named_binder "x" (∀ x, p x))
can use the variable name
of x
in error messages displayed to the user. If we find that instantiating
the above quantifier with 3 falsifies it, we can print:
==============
Problem found!
==============
x := 3
Equations
- slim_check.named_binder n p = p
Is the given test result a failure?
Equations
- slim_check.and_testable p q = {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), slim_check.testable.run p cfg min >>= λ (xp : slim_check.test_result p), slim_check.testable.run q cfg min >>= λ (xq : slim_check.test_result q), has_pure.pure (slim_check.and_counter_example xp xq)}
Equations
- slim_check.or_testable p q = {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), slim_check.testable.run p cfg min >>= λ (xp : slim_check.test_result p), slim_check.or_testable._match_1 p q cfg min xp xp}
- slim_check.or_testable._match_1 p q cfg min xp (slim_check.test_result.failure ᾰ ᾰ_1 ᾰ_2) = slim_check.testable.run q cfg min >>= λ (xq : slim_check.test_result q), has_pure.pure (slim_check.or_counter_example xp xq)
- slim_check.or_testable._match_1 p q cfg min xp (slim_check.test_result.gave_up ᾰ) = slim_check.testable.run q cfg min >>= λ (xq : slim_check.test_result q), has_pure.pure (slim_check.or_counter_example xp xq)
- slim_check.or_testable._match_1 p q cfg min xp (slim_check.test_result.success (psum.inr h)) = has_pure.pure (slim_check.test_result.success (psum.inr _))
- slim_check.or_testable._match_1 p q cfg min xp (slim_check.test_result.success (psum.inl h)) = has_pure.pure (slim_check.test_result.success (psum.inl h))
Equations
- slim_check.iff_testable p q = {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), slim_check.testable.run (p ∧ q ∨ ¬p ∧ ¬q) cfg min >>= λ (xp : slim_check.test_result (p ∧ q ∨ ¬p ∧ ¬q)), return (slim_check.convert_counter_example' _ xp)}
Equations
- slim_check.dec_guard_testable var p β = {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), dite p (λ (h : p), slim_check.dec_guard_testable._match_1 var p β cfg min h (slim_check.printable_prop.print_prop p)) (λ (h : ¬p), ite (↥(cfg.trace_discarded) ∨ ↥(cfg.trace_success)) (slim_check.dec_guard_testable._match_2 var p β (slim_check.printable_prop.print_prop p)) (return (slim_check.test_result.gave_up 1)))}
- slim_check.dec_guard_testable._match_1 var p β cfg min h (option.some str) = (λ (r : slim_check.test_result (β h)), slim_check.add_to_counter_example ("guard: " ++ to_string str ++ "") _ r (psum.inr _)) <$> slim_check.testable.run (β h) cfg min
- slim_check.dec_guard_testable._match_1 var p β cfg min h option.none = (λ (r : slim_check.test_result (β h)), slim_check.convert_counter_example _ r (psum.inr _)) <$> slim_check.testable.run (β h) cfg min
- slim_check.dec_guard_testable._match_2 var p β (option.some str) = trace ("discard: " ++ to_string str ++ " does not hold") (λ («_» : unit), return (slim_check.test_result.gave_up 1))
- slim_check.dec_guard_testable._match_2 var p β option.none = trace "discard" (λ («_» : unit), return (slim_check.test_result.gave_up 1))
Type tag that replaces a type's has_repr
instance with its has_to_string
instance.
Equations
Instances for slim_check.use_has_to_string
Equations
Add the type tag use_has_to_string
to an expression's type.
Equations
Equations
- slim_check.use_has_to_string.has_repr α = {repr := to_string _inst_1}
Equations
- slim_check.all_types_testable var f = {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), slim_check.testable.run (f ℤ) cfg min >>= λ (r : slim_check.test_result (f ℤ)), return (slim_check.add_var_to_counter_example var (slim_check.use_has_to_string.mk "ℤ") _ r)}
Trace the value of sampled variables if the sample is discarded.
Equations
- slim_check.trace_if_giveup tracing_enabled var val (slim_check.test_result.failure ᾰ ᾰ_1 ᾰ_2) = λ (_x : thunk β), _x unit.star()
- slim_check.trace_if_giveup tracing_enabled var val (slim_check.test_result.gave_up _x) = ite ↥tracing_enabled (trace (" " ++ to_string var ++ (" := " ++ to_string (repr val) ++ ""))) (λ (_x : thunk β), _x unit.star())
- slim_check.trace_if_giveup tracing_enabled var val (slim_check.test_result.success ᾰ) = λ (_x : thunk β), _x unit.star()
testable instance for a property iterating over the element of a list
Equations
- slim_check.test_forall_in_list var var' α β (x :: xs) = {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), slim_check.testable.run (β x) cfg min >>= λ (r : slim_check.test_result (β x)), slim_check.trace_if_giveup cfg.trace_discarded var x r (λ («_» : unit), slim_check.test_forall_in_list._match_1 var var' α β x xs cfg min r (slim_check.test_forall_in_list var var' α β xs) (slim_check.test_forall_in_list var var' α β xs) r)}
- slim_check.test_forall_in_list var var' α β list.nil = {run := λ (tracing : slim_check.slim_check_cfg) (min : bool), return (slim_check.test_result.success (psum.inr _))}
- slim_check.test_forall_in_list._match_1 var var' α β x xs cfg min r _f_1 _f_2 (slim_check.test_result.failure _x _x_1 _x_2) = return (slim_check.add_var_to_counter_example var x _ r)
- slim_check.test_forall_in_list._match_1 var var' α β x xs cfg min r _f_1 _f_2 (slim_check.test_result.gave_up n) = slim_check.testable.run (slim_check.named_binder var (∀ (x : α), slim_check.named_binder var' (x ∈ xs → β x))) cfg min >>= λ (rs : slim_check.test_result (slim_check.named_binder var (∀ (x : α), slim_check.named_binder var' (x ∈ xs → β x)))), slim_check.test_forall_in_list._match_2 var var' α β x xs n rs
- slim_check.test_forall_in_list._match_1 var var' α β x xs cfg min r _f_1 _f_2 (slim_check.test_result.success hp) = slim_check.testable.run (slim_check.named_binder var (∀ (x : α), slim_check.named_binder var' (x ∈ xs → β x))) cfg min >>= λ (rs : slim_check.test_result (slim_check.named_binder var (∀ (x : α), slim_check.named_binder var' (x ∈ xs → β x)))), return (slim_check.convert_counter_example _ rs (slim_check.combine (psum.inr _) hp))
- slim_check.test_forall_in_list._match_2 var var' α β x xs n (slim_check.test_result.failure Hce xs_1 n_1) = return (slim_check.test_result.failure _ xs_1 n_1)
- slim_check.test_forall_in_list._match_2 var var' α β x xs n (slim_check.test_result.gave_up n') = return (slim_check.test_result.gave_up (n + n'))
- slim_check.test_forall_in_list._match_2 var var' α β x xs n (slim_check.test_result.success _x) = return (slim_check.test_result.gave_up n)
Test proposition p
by randomly selecting one of the provided
testable instances.
Equations
- slim_check.combine_testable p t h = {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), have this : 0 < (list.map (λ (t : slim_check.testable p), slim_check.testable.run p cfg min) t).length, from _, slim_check.gen.one_of (list.map (λ (t : slim_check.testable p), slim_check.testable.run p cfg min) t) this}
Format the counter-examples found in a test failure.
Format the counter-examples found in a test failure.
Equations
Increase the number of shrinking steps in a test result.
Equations
- slim_check.add_shrinks n (slim_check.test_result.failure h vs n') = slim_check.test_result.failure h vs (n + n')
- slim_check.add_shrinks n (slim_check.test_result.gave_up a) = slim_check.test_result.gave_up a
- slim_check.add_shrinks n (slim_check.test_result.success a) = slim_check.test_result.success a
Shrink a counter-example x
by using shrink x
, picking the first
candidate that falsifies a property and recursively shrinking that one.
The process is guaranteed to terminate because shrink x
produces
a proof that all the values it produces are smaller (according to sizeof
)
than x
.
Equations
- slim_check.minimize_aux α β cfg var = _.fix (λ (x : slim_check.sampleable_ext.proxy_repr α) (f_rec : Π (y : slim_check.sampleable_ext.proxy_repr α), has_well_founded.r y x → ℕ → option_t slim_check.gen (Σ (x : slim_check.sampleable_ext.proxy_repr α), slim_check.test_result (β (slim_check.sampleable_ext.interp α x)))) (n : ℕ), ite ↥(cfg.trace_shrink_candidates) (return (trace ("candidates for " ++ to_string var ++ (" :=\n" ++ to_string (repr (slim_check.sampleable_ext.shrink x).to_list) ++ "\n")) (λ («_» : unit), unit.star()))) (has_pure.pure unit.star()) >>= λ (_x : unit), lazy_list.mfirst (λ (_x : {y // slim_check.sizeof_lt y x}), slim_check.minimize_aux._match_2 α β cfg x _x) (slim_check.sampleable_ext.shrink x) >>= λ (_p : Σ (a : slim_check.sampleable_ext.proxy_repr α), slim_check.test_result (β (slim_check.sampleable_ext.interp α a)) × plift (slim_check.sizeof_lt a x)), slim_check.minimize_aux._match_3 α β cfg var x f_rec n _p)
- slim_check.minimize_aux._match_2 α β cfg x ⟨a, h⟩ = has_monad_lift_t.monad_lift (uliftable.up (slim_check.testable.run (β (slim_check.sampleable_ext.interp α a)) cfg bool.tt)) >>= λ (_p : ulift (slim_check.test_result (β (slim_check.sampleable_ext.interp α a)))), slim_check.minimize_aux._match_1 α β x a h _p
- slim_check.minimize_aux._match_3 α β cfg var x f_rec n ⟨y, (r, {down := h₁})⟩ = ite ↥(cfg.trace_shrink) (return (trace ("" ++ to_string var ++ (" := " ++ to_string (repr y) ++ "") ++ slim_check.format_failure' "Shrink counter-example:" r) (λ («_» : unit), unit.star()))) (has_pure.pure unit.star()) >>= λ (_x : unit), f_rec y h₁ (n + 1) <|> has_pure.pure ⟨y, slim_check.add_shrinks (n + 1) r⟩
- slim_check.minimize_aux._match_1 α β x a h {down := r} = ite ↥(slim_check.is_failure r) (has_pure.pure ⟨a, (r, {down := h})⟩) failure
Once a property fails to hold on an example, look for smaller counter-examples to show the user.
Equations
- slim_check.minimize α β cfg var x r = ite ↥(cfg.trace_shrink) (return (trace ("" ++ to_string var ++ (" := " ++ to_string (repr x) ++ "") ++ slim_check.format_failure' "Shrink counter-example:" r) (λ («_» : unit), unit.star()))) (has_pure.pure unit.star()) >>= λ (_x : unit), (slim_check.minimize_aux α β cfg var x 0).run >>= λ (x' : option (Σ (x : slim_check.sampleable_ext.proxy_repr α), slim_check.test_result (β (slim_check.sampleable_ext.interp α x)))), has_pure.pure (x'.get_or_else ⟨x, r⟩)
Equations
- slim_check.exists_testable var var' α β p = {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), slim_check.testable.run (slim_check.named_binder var (∀ (x : α), slim_check.named_binder var' (β x → p))) cfg min >>= λ (x : slim_check.test_result (slim_check.named_binder var (∀ (x : α), slim_check.named_binder var' (β x → p)))), has_pure.pure (slim_check.convert_counter_example' _ x)}
Test a universal property by creating a sample of the right type and instantiating the bound variable with it
Equations
- slim_check.var_testable var α β = {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), uliftable.adapt_down (slim_check.sampleable_ext.sample α) (λ (x : slim_check.sampleable_ext.proxy_repr α), slim_check.testable.run (β (slim_check.sampleable_ext.interp α x)) cfg bool.ff >>= λ (r : slim_check.test_result (β (slim_check.sampleable_ext.interp α x))), uliftable.adapt_down (ite (↥(slim_check.is_failure r) ∧ ↥min) (slim_check.minimize α β cfg var x r) (ite ↥(cfg.trace_success) (trace (" " ++ to_string var ++ (" := " ++ to_string (repr x) ++ "")) (λ («_» : unit), has_pure.pure ⟨x, r⟩)) (has_pure.pure ⟨x, r⟩))) (λ (_x : Σ (x : slim_check.sampleable_ext.proxy_repr α), slim_check.test_result (β (slim_check.sampleable_ext.interp α x))), slim_check.var_testable._match_1 var α β cfg _x))}
- slim_check.var_testable._match_1 var α β cfg ⟨x, r⟩ = return (slim_check.trace_if_giveup cfg.trace_discarded var x r (λ («_» : unit), (slim_check.add_var_to_counter_example var x _ r)))
Test a universal property about propositions
Equations
- slim_check.prop_var_testable var β = {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), (λ (ᾰ : slim_check.test_result (∀ (b : bool), β ↥b)), (slim_check.convert_counter_example _ ᾰ)) <$> slim_check.testable.run (slim_check.named_binder var (∀ (b : bool), β ↥b)) cfg min}
Equations
- slim_check.unused_var_testable var α β = {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), slim_check.testable.run β cfg min >>= λ (r : slim_check.test_result β), has_pure.pure (slim_check.convert_counter_example _ r (psum.inr _))}
Equations
- slim_check.subtype_var_testable var var' α β = {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), let test : Π (x : subtype p), slim_check.testable (β ↑x) := λ (x : subtype p), {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), slim_check.testable.run (β x.val) cfg min >>= λ (r : slim_check.test_result (β x.val)), slim_check.subtype_var_testable._match_1 α β x r (slim_check.printable_prop.print_prop (p ↑x))} in slim_check.testable.run (∀ (x : subtype p), β x.val) cfg min >>= λ (r : slim_check.test_result (∀ (x : subtype p), β x.val)), has_pure.pure (slim_check.convert_counter_example' _ r)}
- slim_check.subtype_var_testable._match_1 α β x r (option.some str) = has_pure.pure (slim_check.add_to_counter_example ("guard: " ++ to_string str ++ " (by construction)") _ r (psum.inr _))
- slim_check.subtype_var_testable._match_1 α β x r option.none = has_pure.pure r
Equations
- slim_check.decidable_testable p = {run := λ (cfg : slim_check.slim_check_cfg) (min : bool), return (dite p (λ (h : p), slim_check.test_result.success (psum.inr h)) (λ (h : ¬p), slim_check.decidable_testable._match_1 p h (slim_check.printable_prop.print_prop p)))}
- slim_check.decidable_testable._match_1 p h (option.some str) = slim_check.test_result.failure h ["issue: " ++ to_string str ++ " does not hold"] 0
- slim_check.decidable_testable._match_1 p h option.none = slim_check.test_result.failure h list.nil 0
Equations
- slim_check.eq.printable_prop x y = {print_prop := option.some ("" ++ to_string (repr x) ++ (" = " ++ to_string (repr y) ++ ""))}
Equations
- slim_check.ne.printable_prop x y = {print_prop := option.some ("" ++ to_string (repr x) ++ (" ≠ " ++ to_string (repr y) ++ ""))}
Equations
- slim_check.le.printable_prop x y = {print_prop := option.some ("" ++ to_string (repr x) ++ (" ≤ " ++ to_string (repr y) ++ ""))}
Equations
- slim_check.lt.printable_prop x y = {print_prop := option.some ("" ++ to_string (repr x) ++ (" < " ++ to_string (repr y) ++ ""))}
Equations
- slim_check.perm.printable_prop xs ys = {print_prop := option.some ("" ++ to_string (repr xs) ++ (" ~ " ++ to_string (repr ys) ++ ""))}
Equations
- slim_check.and.printable_prop x y = {print_prop := slim_check.printable_prop.print_prop x >>= λ (x' : string), slim_check.printable_prop.print_prop y >>= λ (y' : string), option.some ("(" ++ to_string x' ++ (" ∧ " ++ to_string y' ++ ")"))}
Equations
- slim_check.or.printable_prop x y = {print_prop := slim_check.printable_prop.print_prop x >>= λ (x' : string), slim_check.printable_prop.print_prop y >>= λ (y' : string), option.some ("(" ++ to_string x' ++ (" ∨ " ++ to_string y' ++ ")"))}
Equations
- slim_check.iff.printable_prop x y = {print_prop := slim_check.printable_prop.print_prop x >>= λ (x' : string), slim_check.printable_prop.print_prop y >>= λ (y' : string), option.some ("(" ++ to_string x' ++ (" ↔ " ++ to_string y' ++ ")"))}
Equations
- slim_check.imp.printable_prop x y = {print_prop := slim_check.printable_prop.print_prop x >>= λ (x' : string), slim_check.printable_prop.print_prop y >>= λ (y' : string), option.some ("(" ++ to_string x' ++ (" → " ++ to_string y' ++ ")"))}
Equations
- slim_check.not.printable_prop x = {print_prop := slim_check.printable_prop.print_prop x >>= λ (x' : string), option.some ("¬ " ++ to_string x' ++ "")}
Equations
- slim_check.true.printable_prop = {print_prop := option.some "true"}
Equations
- slim_check.false.printable_prop = {print_prop := option.some "false"}
Equations
- slim_check.bool.printable_prop b = {print_prop := option.some (ite ↥b "true" "false")}
Execute cmd
and repeat every time the result is gave_up
(at most
n
times).
Equations
- slim_check.retry cmd n.succ = cmd >>= λ (r : slim_check.test_result p), slim_check.retry._match_1 (slim_check.retry cmd n) r
- slim_check.retry cmd 0 = return (slim_check.test_result.gave_up 1)
- slim_check.retry._match_1 _f_1 (slim_check.test_result.failure Hce xs n) = return (slim_check.test_result.failure Hce xs n)
- slim_check.retry._match_1 _f_1 (slim_check.test_result.gave_up _x) = _f_1
- slim_check.retry._match_1 _f_1 (slim_check.test_result.success hp) = return (slim_check.test_result.success hp)
Count the number of times the test procedure gave up.
Equations
- slim_check.give_up x (slim_check.test_result.failure Hce xs n) = slim_check.test_result.failure Hce xs n
- slim_check.give_up x (slim_check.test_result.gave_up n) = slim_check.test_result.gave_up (n + x)
- slim_check.give_up x (slim_check.test_result.success (psum.inr p_1)) = slim_check.test_result.success (psum.inr p_1)
- slim_check.give_up x (slim_check.test_result.success (psum.inl unit.star())) = slim_check.test_result.gave_up x
Try n
times to find a counter-example for p
.
Equations
- slim_check.testable.run_suite_aux p cfg r n.succ = let size : ℕ := (cfg.num_inst - n - 1) * cfg.max_size / cfg.num_inst in when ↥(cfg.trace_success) (return (trace "[slim_check: sample]" (λ («_» : unit), unit.star()))) >>= λ (_x : unit), slim_check.retry ((slim_check.testable.run p cfg bool.tt).run {down := size}) 10 >>= λ (x : slim_check.test_result p), slim_check.testable.run_suite_aux._match_1 p (slim_check.testable.run_suite_aux p cfg r n) (λ (g : ℕ), slim_check.testable.run_suite_aux p cfg (slim_check.give_up g r) n) x
- slim_check.testable.run_suite_aux p cfg r 0 = return r
- slim_check.testable.run_suite_aux._match_1 p _f_1 _f_2 (slim_check.test_result.failure Hce xs n) = return (slim_check.test_result.failure Hce xs n)
- slim_check.testable.run_suite_aux._match_1 p _f_1 _f_2 (slim_check.test_result.gave_up g) = _f_2 g
- slim_check.testable.run_suite_aux._match_1 p _f_1 _f_2 (slim_check.test_result.success (psum.inr Hp)) = return (slim_check.test_result.success (psum.inr Hp))
- slim_check.testable.run_suite_aux._match_1 p _f_1 _f_2 (slim_check.test_result.success (psum.inl unit.star())) = _f_1
Try to find a counter-example of p
.
Equations
Run a test suite for p
in io
.
Equations
- slim_check.testable.check' p cfg = slim_check.testable.check'._match_1 p cfg cfg.random_seed
- slim_check.testable.check'._match_1 p cfg (option.some seed) = io.run_rand_with seed (slim_check.testable.run_suite p cfg)
- slim_check.testable.check'._match_1 p cfg option.none = io.run_rand (slim_check.testable.run_suite p cfg)
Decorations #
Instances of testable
use named_binder
as a decoration on
propositions in order to access the name of bound variables, as in
named_binder "x" (forall x, x < y)
. This helps the
testable
instances create useful error messages where variables
are matched with values that falsify a given proposition.
The following functions help support the gadget so that the user does not have to put them in themselves.
decorations_of p
is used as a hint to mk_decorations
to specify
that the goal should be satisfied with a proposition equivalent to p
with added annotations.
Equations
Run a test suite for p
and return true or false: should we believe that p
holds?
Equations
- slim_check.testable.check p cfg p' = slim_check.testable.check._match_1 p cfg p' cfg.random_seed >>= λ (x : slim_check.test_result p'), slim_check.testable.check._match_2 p cfg p' x
- slim_check.testable.check._match_1 p cfg p' (option.some seed) = io.run_rand_with seed (slim_check.testable.run_suite p' cfg)
- slim_check.testable.check._match_1 p cfg p' option.none = io.run_rand (slim_check.testable.run_suite p' cfg)
- slim_check.testable.check._match_2 p cfg p' (slim_check.test_result.failure _x xs n) = io.fail (slim_check.format_failure "Found problems!" xs n)
- slim_check.testable.check._match_2 p cfg p' (slim_check.test_result.gave_up n) = io.fail ("Gave up " ++ to_string (repr n) ++ " times")
- slim_check.testable.check._match_2 p cfg p' (slim_check.test_result.success _x) = when (¬↥(cfg.quiet)) (io.put_str_ln "Success")