How to easily filter out discriminatory merge cases in FsCheck?

Consider the Discrimination Union:

type DU = | Foo of string | Bar of int | Baz of decimal * float | Qux of bool 

I would like to create a list of DU values ​​using FsCheck, but I want none of the values ​​to be in the case of Qux .

This predicate already exists:

 let isQux = function Qux _ -> true | _ -> false 

First try

My first attempt to create a list of DU values ​​without a Qux case was something like this:

 type DoesNotWork = static member DU () = Arb.from<DU> |> Arb.filter (not << isQux) [<Property(MaxTest = 10 , Arbitrary = [| typeof<DoesNotWork> |])>] let repro (dus : DU list) = printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus 

Doing this seems to lead to a stack overflow, so I assume that what happens behind the scenes is that Arb.from<DU> calls DoesNotWork.DU .

Second attempt

Then I tried this:

 type DoesNotWorkEither = static member DU () = Arb.generate<DU> |> Gen.suchThat (not << isQux) |> Arb.fromGen [<Property(MaxTest = 10 , Arbitrary = [| typeof<DoesNotWorkEither> |])>] let repro (dus : DU list) = printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus 

The same problems as above.

Detailed solution

This is the best solution I've been able to come up with:

 type WithoutQux = static member DU () = [ Arb.generate<string> |> Gen.map Foo Arb.generate<int> |> Gen.map Bar Arb.generate<decimal * float> |> Gen.map Baz ] |> Gen.oneof |> Arb.fromGen [<Property(MaxTest = 10 , Arbitrary = [| typeof<WithoutQux> |])>] let repro (dus : DU list) = printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus 

This works, but has the following disadvantages:

  • It seems like a lot of work.
  • It does not use the already available isQux function, so it seems to subtly violate DRY
  • It really does not filter, but rather creates only the necessary cases (therefore, it filters only inaction).
  • This is not particularly convenient because if I ever add a fifth case to DU , I would also have to add Gen for this case.

Is there a more elegant way to tell FsCheck to filter Qux values?

+7
f # fscheck
source share
2 answers

Instead of Arb.generate , which is trying to use a registered instance for the type that is the instance you are trying to determine, which causes an infinite loop, use Arb.Default.Derive() , which will go directly to the generator with a reflective base.

https://github.com/fscheck/FsCheck/blob/master/src/FsCheck/Arbitrary.fs#L788-788

This is such a common mistake that we can solve out of the box in FsCheck: https://github.com/fscheck/FsCheck/issues/109


A particular problem in the OP can be solved as follows:

 type WithoutQux = static member DU () = Arb.Default.Derive () |> Arb.filter (not << isQux) [<Property(MaxTest = 10 , Arbitrary = [| typeof<WithoutQux> |])>] let repro (dus : DU list) = printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus 
+5
source share

The following should work:

 type DU = | Foo of string | Bar of int | Baz of decimal * float | Qux of bool let isQux = function Qux _ -> true | _ -> false let g = Arb.generate<DU> |> Gen.suchThat (not << isQux) |> Gen.listOf type DoesWork = static member DU () = Arb.fromGen g [<Property(MaxTest = 10 , Arbitrary = [| typeof<DoesWork> |])>] let repro (dus : DU list) = printfn "%-5b : %O" (dus |> List.exists isQux |> not) dus 

Note. I used Gen.listOf at the end - it seems that FsCheck cannot create a list with this generator

+4
source share

All Articles