-
Notifications
You must be signed in to change notification settings - Fork 30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to generate DU cases? #133
Comments
You can find an example here for Instead, what you can do is write a reflection-based generator, and that should handle this, as well as #123. Such a reflection-based generator may be fairly easy to write with TypeShape, as I wrote in #93 (comment). Perhaps, if we come up with a fancy, battle-tested, reflection-based generator, we can consider releasing it as a separate NuGet Package so that other users can benefit from it. |
I would love absolutely that. Unfortunately my F# skills aren't up to that task, so I can only hope. |
(This is the example I'm talking about in #93 (comment), in case it's TL;DR.) |
👍 I'll see if I can come up with a draft where I can paste it here so you may use it sooner. |
Here's something that may actually work! You may add more cases below the //
// TypeShape-driven Hedgehog generator
//
// Credits: http://www.fssnip.net/7RA/title/Generic-POCO-generators-for-FsCheck
//
open Hedgehog
open TypeShape
module Gen =
let rec mkGenerator<'T> () : Gen<'T> =
let wrap (t : Gen<'a>) =
unbox<Gen<'T>> t
let mkRandomMember (shape : IShapeWriteMember<'DeclaringType>) =
shape.Accept {
new IWriteMemberVisitor<'DeclaringType, Gen<'DeclaringType -> 'DeclaringType>> with
member this.Visit(shape : ShapeWriteMember<'DeclaringType, 'Field>) =
let rf = mkGenerator<'Field>()
gen { let! f = rf
return fun dt -> shape.Inject dt f } }
match TypeShape.Create<'T>() with
| Shape.Byte -> wrap (Gen.byte <| Range.linearBounded ())
| Shape.Int16 -> wrap (Gen.int16 <| Range.linear -1000s 1000s)
| Shape.Int32 -> wrap (Gen.int <| Range.linear -1000 1000 )
| Shape.Int64 -> wrap (Gen.int64 <| Range.linear -1000L 1000L)
| Shape.Double -> wrap (Gen.double <| Range.linear -1000.0 1000.0)
| Shape.Decimal -> wrap (Gen.double <| (Range.linear -1000.0 1000.0) |> Gen.map decimal)
| Shape.Bool -> wrap Gen.bool
| Shape.Guid -> wrap Gen.guid
| Shape.Char -> wrap Gen.unicodeAll
| Shape.DateTime -> wrap Gen.dateTime
| Shape.Unit -> wrap (Gen.constant ())
| Shape.String -> wrap (Gen.string (Range.constant -1000 1000) Gen.alphaNum)
| Shape.DateTimeOffset -> wrap (Gen.map System.DateTimeOffset Gen.dateTime)
| Shape.FSharpOption s ->
s.Accept {
new IFSharpOptionVisitor<Gen<'T>> with
member this.Visit<'t>() =
let tGen = mkGenerator<'t> ()
Gen.frequency [ (10, tGen |> Gen.map Some)
(1, gen { return None }) ]
|> wrap }
| Shape.Array s when s.Rank = 1 ->
s.Accept {
new IArrayVisitor<Gen<'T>> with
member this.Visit<'t> _ =
let tG = mkGenerator<'t> ()
gen {
let! length = Gen.sized (fun n -> Gen.integral <| Range.constant -1 n)
match length with
| -1 -> return null
| _ ->
let array = Array.zeroCreate<'t> length
for i = 0 to array.Length - 1 do
let! t = tG
array.[i] <- t
return array
}
|> wrap }
| Shape.FSharpList s ->
s.Accept {
new IFSharpListVisitor<Gen<'T>> with
member this.Visit<'t> () =
let tG = mkGenerator<'t> ()
gen {
let! length =
Gen.sized (fun n -> Gen.integral <| Range.constant -1 n)
let rec aux acc n =
gen {
if n = 0 then return acc
else let! t = tG
return! aux (t :: acc) (n - 1)
}
return! aux [] length
}
|> wrap }
| Shape.FSharpSet s ->
s.Accept {
new IFSharpSetVisitor<Gen<'T>> with
member this.Visit<'t when 't : comparison> () =
let tG = mkGenerator<'t list> ()
wrap (tG |> Gen.map Set.ofList) }
| Shape.FSharpMap s ->
s.Accept {
new IFSharpMapVisitor<Gen<'T>> with
member this.Visit<'k, 'v when 'k : comparison> () =
let kvG = mkGenerator<('k * 'v) list> ()
wrap (kvG |> Gen.map Map.ofList) }
| Shape.Tuple (:? ShapeTuple<'T> as shape) ->
let eGens =
shape.Elements
|> Array.map mkRandomMember
gen {
let mutable target = shape.CreateUninitialized ()
for eg in eGens do
let! u = eg
target <- u target
return target
}
| Shape.FSharpRecord (:? ShapeFSharpRecord<'T> as shape) ->
let fieldGen =
shape.Fields
|> Array.map mkRandomMember
gen {
let mutable target = shape.CreateUninitialized ()
for eg in fieldGen do
let! u = eg
target <- u target
return target
}
| Shape.FSharpUnion (:? ShapeFSharpUnion<'T> as shape) ->
let caseFieldGen =
shape.UnionCases
|> Array.map (fun uc -> uc.Fields |> Array.map mkRandomMember)
gen {
let! tag = Gen.integral <| Range.constant 0 (caseFieldGen.Length - 1)
let mutable u = shape.UnionCases.[tag].CreateUninitialized ()
for f in caseFieldGen.[tag] do
let! uf = f
u <- uf u
return u
}
| Shape.CliMutable (:? ShapeCliMutable<'T> as shape) ->
let propGen = shape.Properties |> Array.map mkRandomMember
gen {
let mutable target = shape.CreateUninitialized ()
for ep in propGen do
let! up = ep
target <- up target
return target
}
| Shape.Poco (:? ShapePoco<'T> as shape) ->
let bestCtor =
shape.Constructors
|> Seq.filter (fun c -> c.IsPublic)
|> Seq.sortBy (fun c -> c.Arity)
|> Seq.tryFind (fun _ -> true)
match bestCtor with
| None -> failwithf "Class %O lacking an appropriate ctor" typeof<'T>
| Some ctor ->
ctor.Accept {
new IConstructorVisitor<'T, Gen<'T>> with
member this.Visit<'CtorParams> (ctor : ShapeConstructor<'T, 'CtorParams>) =
let paramGen = mkGenerator<'CtorParams> ()
gen { let! args = paramGen
return ctor.Invoke args } }
| _ -> raise (System.NotSupportedException ()) |
Here's an example (notice that you do get also shrinking for free): type Shape =
| Rectangle of height : float * width : float
| Circle of radius : float
Gen.printSample <| Gen.mkGenerator<Shape> ();;
=== Outcome ===
Rectangle (-911.0102007,-830.9254441)
=== Shrinks ===
Rectangle (-1000.0,-830.9254441)
Rectangle (-955.5051004,-830.9254441)
Rectangle (-933.2576505,-830.9254441)
Rectangle (-922.1339256,-830.9254441)
Rectangle (-916.5720632,-830.9254441)
Rectangle (-913.791132,-830.9254441)
Rectangle (-912.4006663,-830.9254441)
Rectangle (-911.7054335,-830.9254441)
Rectangle (-911.3578171,-830.9254441)
Rectangle (-911.1840089,-830.9254441)
Rectangle (-911.0971048,-830.9254441)
Rectangle (-911.0536528,-830.9254441)
Rectangle (-911.0319267,-830.9254441)
Rectangle (-911.0210637,-830.9254441)
Rectangle (-911.0156322,-830.9254441)
Rectangle (-911.0129165,-830.9254441)
Rectangle (-911.0115586,-830.9254441)
Rectangle (-911.0108797,-830.9254441)
Rectangle (-911.0105402,-830.9254441)
Rectangle (-911.0103705,-830.9254441)
Rectangle (-911.0102856,-830.9254441)
Rectangle (-911.0102432,-830.9254441)
Rectangle (-911.0102219,-830.9254441)
Rectangle (-911.0102113,-830.9254441)
Rectangle (-911.010206,-830.9254441)
Rectangle (-911.0102034,-830.9254441)
Rectangle (-911.010202,-830.9254441)
Rectangle (-911.0102014,-830.9254441)
Rectangle (-911.0102011,-830.9254441)
Rectangle (-911.0102009,-830.9254441)
Rectangle (-911.0102008,-830.9254441)
Rectangle (-911.0102008,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-1000.0)
Rectangle (-911.0102007,-915.4627221)
Rectangle (-911.0102007,-873.1940831)
Rectangle (-911.0102007,-852.0597636)
Rectangle (-911.0102007,-841.4926039)
Rectangle (-911.0102007,-836.209024)
Rectangle (-911.0102007,-833.5672341)
Rectangle (-911.0102007,-832.2463391)
Rectangle (-911.0102007,-831.5858916)
Rectangle (-911.0102007,-831.2556679)
Rectangle (-911.0102007,-831.090556)
Rectangle (-911.0102007,-831.0080001)
Rectangle (-911.0102007,-830.9667221)
Rectangle (-911.0102007,-830.9460831)
Rectangle (-911.0102007,-830.9357636)
Rectangle (-911.0102007,-830.9306039)
Rectangle (-911.0102007,-830.928024)
Rectangle (-911.0102007,-830.9267341)
Rectangle (-911.0102007,-830.9260891)
Rectangle (-911.0102007,-830.9257666)
Rectangle (-911.0102007,-830.9256054)
Rectangle (-911.0102007,-830.9255247)
Rectangle (-911.0102007,-830.9254844)
Rectangle (-911.0102007,-830.9254643)
Rectangle (-911.0102007,-830.9254542)
Rectangle (-911.0102007,-830.9254492)
Rectangle (-911.0102007,-830.9254466)
Rectangle (-911.0102007,-830.9254454)
Rectangle (-911.0102007,-830.9254447)
Rectangle (-911.0102007,-830.9254444)
Rectangle (-911.0102007,-830.9254443)
Rectangle (-911.0102007,-830.9254442)
Rectangle (-911.0102007,-830.9254442)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
Rectangle (-911.0102007,-830.9254441)
.
=== Outcome ===
Circle -907.2173144
=== Shrinks ===
Rectangle (-907.2173144,-804.4497323)
Circle -1000.0
Circle -953.6086572
Circle -930.4129858
Circle -918.8151501
Circle -913.0162322
Circle -910.1167733
Circle -908.6670438
Circle -907.9421791
Circle -907.5797467
Circle -907.3985305
Circle -907.3079224
Circle -907.2626184
Circle -907.2399664
Circle -907.2286404
Circle -907.2229774
Circle -907.2201459
Circle -907.2187301
Circle -907.2180222
Circle -907.2176683
Circle -907.2174913
Circle -907.2174028
Circle -907.2173586
Circle -907.2173365
Circle -907.2173254
Circle -907.2173199
Circle -907.2173171
Circle -907.2173157
Circle -907.217315
Circle -907.2173147
Circle -907.2173145
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
Circle -907.2173144
.
=== Outcome ===
Circle -982.6691089
=== Shrinks ===
Rectangle (-982.6691089,-910.8182287)
Circle -1000.0
Circle -991.3345544
Circle -987.0018316
Circle -984.8354703
Circle -983.7522896
Circle -983.2106992
Circle -982.939904
Circle -982.8045064
Circle -982.7368077
Circle -982.7029583
Circle -982.6860336
Circle -982.6775712
Circle -982.67334
Circle -982.6712245
Circle -982.6701667
Circle -982.6696378
Circle -982.6693733
Circle -982.6692411
Circle -982.669175
Circle -982.6691419
Circle -982.6691254
Circle -982.6691171
Circle -982.669113
Circle -982.6691109
Circle -982.6691099
Circle -982.6691094
Circle -982.6691091
Circle -982.669109
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
Circle -982.6691089
.
=== Outcome ===
Rectangle (-847.9604826,-845.8164013)
=== Shrinks ===
Rectangle (-1000.0,-845.8164013)
Rectangle (-923.9802413,-845.8164013)
Rectangle (-885.970362,-845.8164013)
Rectangle (-866.9654223,-845.8164013)
Rectangle (-857.4629525,-845.8164013)
Rectangle (-852.7117175,-845.8164013)
Rectangle (-850.3361001,-845.8164013)
Rectangle (-849.1482914,-845.8164013)
Rectangle (-848.554387,-845.8164013)
Rectangle (-848.2574348,-845.8164013)
Rectangle (-848.1089587,-845.8164013)
Rectangle (-848.0347207,-845.8164013)
Rectangle (-847.9976017,-845.8164013)
Rectangle (-847.9790421,-845.8164013)
Rectangle (-847.9697624,-845.8164013)
Rectangle (-847.9651225,-845.8164013)
Rectangle (-847.9628026,-845.8164013)
Rectangle (-847.9616426,-845.8164013)
Rectangle (-847.9610626,-845.8164013)
Rectangle (-847.9607726,-845.8164013)
Rectangle (-847.9606276,-845.8164013)
Rectangle (-847.9605551,-845.8164013)
Rectangle (-847.9605189,-845.8164013)
Rectangle (-847.9605008,-845.8164013)
Rectangle (-847.9604917,-845.8164013)
Rectangle (-847.9604872,-845.8164013)
Rectangle (-847.9604849,-845.8164013)
Rectangle (-847.9604838,-845.8164013)
Rectangle (-847.9604832,-845.8164013)
Rectangle (-847.9604829,-845.8164013)
Rectangle (-847.9604828,-845.8164013)
Rectangle (-847.9604827,-845.8164013)
Rectangle (-847.9604827,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-1000.0)
Rectangle (-847.9604826,-922.9082006)
Rectangle (-847.9604826,-884.362301)
Rectangle (-847.9604826,-865.0893511)
Rectangle (-847.9604826,-855.4528762)
Rectangle (-847.9604826,-850.6346387)
Rectangle (-847.9604826,-848.22552)
Rectangle (-847.9604826,-847.0209607)
Rectangle (-847.9604826,-846.418681)
Rectangle (-847.9604826,-846.1175411)
Rectangle (-847.9604826,-845.9669712)
Rectangle (-847.9604826,-845.8916862)
Rectangle (-847.9604826,-845.8540438)
Rectangle (-847.9604826,-845.8352225)
Rectangle (-847.9604826,-845.8258119)
Rectangle (-847.9604826,-845.8211066)
Rectangle (-847.9604826,-845.8187539)
Rectangle (-847.9604826,-845.8175776)
Rectangle (-847.9604826,-845.8169895)
Rectangle (-847.9604826,-845.8166954)
Rectangle (-847.9604826,-845.8165483)
Rectangle (-847.9604826,-845.8164748)
Rectangle (-847.9604826,-845.816438)
Rectangle (-847.9604826,-845.8164197)
Rectangle (-847.9604826,-845.8164105)
Rectangle (-847.9604826,-845.8164059)
Rectangle (-847.9604826,-845.8164036)
Rectangle (-847.9604826,-845.8164024)
Rectangle (-847.9604826,-845.8164019)
Rectangle (-847.9604826,-845.8164016)
Rectangle (-847.9604826,-845.8164014)
Rectangle (-847.9604826,-845.8164014)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
Rectangle (-847.9604826,-845.8164013)
.
=== Outcome ===
Rectangle (-842.0160974,-850.9816042)
=== Shrinks ===
Rectangle (-1000.0,-850.9816042)
Rectangle (-921.0080487,-850.9816042)
Rectangle (-881.512073,-850.9816042)
Rectangle (-861.7640852,-850.9816042)
Rectangle (-851.8900913,-850.9816042)
Rectangle (-846.9530943,-850.9816042)
Rectangle (-844.4845959,-850.9816042)
Rectangle (-843.2503466,-850.9816042)
Rectangle (-842.633222,-850.9816042)
Rectangle (-842.3246597,-850.9816042)
Rectangle (-842.1703785,-850.9816042)
Rectangle (-842.093238,-850.9816042)
Rectangle (-842.0546677,-850.9816042)
Rectangle (-842.0353825,-850.9816042)
Rectangle (-842.02574,-850.9816042)
Rectangle (-842.0209187,-850.9816042)
Rectangle (-842.018508,-850.9816042)
Rectangle (-842.0173027,-850.9816042)
Rectangle (-842.0167001,-850.9816042)
Rectangle (-842.0163987,-850.9816042)
Rectangle (-842.0162481,-850.9816042)
Rectangle (-842.0161727,-850.9816042)
Rectangle (-842.0161351,-850.9816042)
Rectangle (-842.0161162,-850.9816042)
Rectangle (-842.0161068,-850.9816042)
Rectangle (-842.0161021,-850.9816042)
Rectangle (-842.0160997,-850.9816042)
Rectangle (-842.0160986,-850.9816042)
Rectangle (-842.016098,-850.9816042)
Rectangle (-842.0160977,-850.9816042)
Rectangle (-842.0160975,-850.9816042)
Rectangle (-842.0160975,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-1000.0)
Rectangle (-842.0160974,-925.4908021)
Rectangle (-842.0160974,-888.2362031)
Rectangle (-842.0160974,-869.6089037)
Rectangle (-842.0160974,-860.2952539)
Rectangle (-842.0160974,-855.6384291)
Rectangle (-842.0160974,-853.3100166)
Rectangle (-842.0160974,-852.1458104)
Rectangle (-842.0160974,-851.5637073)
Rectangle (-842.0160974,-851.2726557)
Rectangle (-842.0160974,-851.12713)
Rectangle (-842.0160974,-851.0543671)
Rectangle (-842.0160974,-851.0179856)
Rectangle (-842.0160974,-850.9997949)
Rectangle (-842.0160974,-850.9906995)
Rectangle (-842.0160974,-850.9861519)
Rectangle (-842.0160974,-850.983878)
Rectangle (-842.0160974,-850.9827411)
Rectangle (-842.0160974,-850.9821726)
Rectangle (-842.0160974,-850.9818884)
Rectangle (-842.0160974,-850.9817463)
Rectangle (-842.0160974,-850.9816752)
Rectangle (-842.0160974,-850.9816397)
Rectangle (-842.0160974,-850.9816219)
Rectangle (-842.0160974,-850.9816131)
Rectangle (-842.0160974,-850.9816086)
Rectangle (-842.0160974,-850.9816064)
Rectangle (-842.0160974,-850.9816053)
Rectangle (-842.0160974,-850.9816047)
Rectangle (-842.0160974,-850.9816045)
Rectangle (-842.0160974,-850.9816043)
Rectangle (-842.0160974,-850.9816043)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
Rectangle (-842.0160974,-850.9816042)
. |
Classes (types that represent objects that can have properties, methods, and events) work too: type Person (name : string, age : int) =
member this.Name = name
member this.Age = age
override this.ToString() =
sprintf "{ Name = \"%s\"; Age = %d }" this.Name this.Age
Gen.printSample <| Gen.mkGenerator<Person> ();;
[..] |
That's absolutely lovely! A couple of questions:
|
@cmeeren, you are free to modify this based on your scenarios. Nothing stops you from changing the ranges I've used in that example. |
@cmeeren, I'll place this on a GitHub Gist, so we can take advantage of the versioning feature it has. Then we can tweak |
@cmeeren, I'll have to look at this closer, in order to make sure it's not some weird side-effect of |
Sounds great. If auto-generation like this can work, and the speed issues from #135 are fixed, then I'll be a very happy Hedgehog user 😄 |
There seems to be a bug in the list generator definition. The length can be I'm also of two minds whether the generator should by default generate nulls for e.g. arrays. Mostly, when programming in F#, nulls aren't a problem. In most cases, I'd guess the user would not want nulls to be generated, because they're testing code that never gets a null. On the other hand, when they actually do test code that needs to handle |
Feel free to tweak If you need to be able to 'override' the existing generators (say, the one for strings), perhaps you can modify Then, inside the If you come up with a battle-tested version of Closing this, but feel free to add as many comments as you want. Your feedback is invaluable 🚀 |
Thanks. I'm still going a bit back and forth between Hedgehog and FsCheck. If I fall down on Hedgehog I will likely have a closer look at this - currently the speed issue from #135 is blocking any attempts at a full adoption of Hedgehog on my part, and if FsCheck also implements integrated shrinking (fscheck/FsCheck#403), then I might still end up with FsCheck in the long run. We'll see. :) |
If your test suite runs in 10 seconds or less, you should be fine until we're done with #135. Otherwise perhaps you should probably take the type-based shrinking route via FsCheck or similar tool(s).
I can imagine this being implemented à la smartcheck, and then I'll be curious to see how it performs. |
It did with FsCheck, but after converting a few of the more generation-heavy tests to Hedgehog, it decidedly does not. Some individual tests take almost a full second to run. |
I can't seem to figure out how to create a map with types as keys. For example, I tried the following: Map.empty.Add(typeof<bool>, Gen.bool) But I get an error saying that System.Type do not support the |
Here's a quick draft of what I had in mind in #133 (comment): open System
open System.Collections.Generic
open Hedgehog
let localGens = Dictionary<_, _> ()
localGens.Add (typeof<bool>, box Gen.bool)
localGens.Add (typeof<char>, box Gen.unicodeAll)
let get<'a> (d : Dictionary<_, _>) : Gen<'a> =
unbox<Gen<'a>> d.[typeof<'a>] Examples: let gnBool = localGens |> get<bool>
let gnChar = localGens |> get<char> |
I've had a look at the whole shebang now and here's my suggestion (not extensively tested, but seems to work). In addition to the overridable generators, I've made use of the already existing I think that in general, auto-generation and generator overriding should be used for fairly simple purposes. For more complex scenarios, custom generators should be defined as per now. IMHO the biggest benefit to auto-generation is in simple DU types. From my (very limited) experience, more complex types would likely need a more controlled generator anyway (both for correctness and performance). Usage examples: gen {
// Using defaults
let! myTypeValue = Gen.auto<MyType>()
// Overriding
let! myTypeValue = Gen.auto'<MyType>({Gen.defaults with Char = Gen.alphaNum})
} The whole implementation: open Hedgehog
open TypeShape
module Gen =
type AutoGenConfig =
{Byte: Gen<byte>
Int16: Gen<int16>
Int: Gen<int>
Int64: Gen<int64>
Double: Gen<double>
Decimal: Gen<decimal>
Bool: Gen<bool>
Guid: Gen<System.Guid>
Char: Gen<System.Char>
DateTime: Gen<System.DateTime>
String: Gen<System.String>
DateTimeOffset: Gen<System.DateTimeOffset>
SeqRange: Range<int> // range for lists, arrays, etc.
}
let defaults =
{Byte = Gen.byte <| Range.exponentialBounded()
Int16 = Gen.int16 <| Range.exponentialBounded()
Int = Gen.int <| Range.exponentialBounded()
Int64 = Gen.int64 <| Range.exponentialBounded()
Double = Gen.double <| Range.exponentialBounded()
Decimal = Gen.double <| Range.exponentialBounded() |> Gen.map decimal
Bool = Gen.bool
Guid = Gen.guid
Char = Gen.latin1
DateTime = Gen.dateTime
String = Gen.string (Range.linear 0 50) Gen.latin1
DateTimeOffset = Gen.dateTime |> Gen.map System.DateTimeOffset
SeqRange = Range.exponential 0 50}
let rec auto'<'T> (config:AutoGenConfig) : Gen<'T> =
let wrap (t : Gen<'a>) =
unbox<Gen<'T>> t
let mkRandomMember (shape : IShapeWriteMember<'DeclaringType>) =
shape.Accept {
new IWriteMemberVisitor<'DeclaringType, Gen<'DeclaringType -> 'DeclaringType>> with
member __.Visit(shape : ShapeWriteMember<'DeclaringType, 'Field>) =
let rf = auto'<'Field>(config)
gen { let! f = rf
return fun dt -> shape.Inject dt f } }
match TypeShape.Create<'T>() with
| Shape.Byte -> wrap config.Byte
| Shape.Int16 -> wrap config.Int16
| Shape.Int32 -> wrap config.Int
| Shape.Int64 -> wrap config.Int64
| Shape.Double -> wrap config.Double
| Shape.Decimal -> wrap config.Decimal
| Shape.Bool -> wrap config.Bool
| Shape.Guid -> wrap config.Guid
| Shape.Char -> wrap config.Char
| Shape.DateTime -> wrap config.DateTime
| Shape.Unit -> wrap (Gen.constant ())
| Shape.String -> wrap config.String
| Shape.DateTimeOffset -> wrap config.DateTimeOffset
| Shape.FSharpOption s ->
s.Accept {
new IFSharpOptionVisitor<Gen<'T>> with
member __.Visit<'t>() =
auto'<'t> config |> Gen.option |> wrap }
| Shape.Array s when s.Rank = 1 ->
s.Accept {
new IArrayVisitor<Gen<'T>> with
member __.Visit<'t> _ =
auto'<'t> config |> Gen.array config.SeqRange |> wrap }
| Shape.Array _ ->
raise (System.NotSupportedException("Can only generate arrays of rank 1"))
| Shape.FSharpList s ->
s.Accept {
new IFSharpListVisitor<Gen<'T>> with
member __.Visit<'t> () =
auto'<'t> config |> Gen.list config.SeqRange |> wrap }
| Shape.FSharpSet s ->
s.Accept {
new IFSharpSetVisitor<Gen<'T>> with
member __.Visit<'t when 't : comparison> () =
auto'<'t list> config
|> Gen.map Set.ofList
|> wrap }
| Shape.FSharpMap s ->
s.Accept {
new IFSharpMapVisitor<Gen<'T>> with
member __.Visit<'k, 'v when 'k : comparison> () =
auto'<('k * 'v) list> config
|> Gen.map Map.ofList
|> wrap }
| Shape.Tuple (:? ShapeTuple<'T> as shape) ->
let eGens =
shape.Elements
|> Array.map mkRandomMember
gen {
let mutable target = shape.CreateUninitialized ()
for eg in eGens do
let! u = eg
target <- u target
return target
}
| Shape.FSharpRecord (:? ShapeFSharpRecord<'T> as shape) ->
let fieldGen =
shape.Fields
|> Array.map mkRandomMember
gen {
let mutable target = shape.CreateUninitialized ()
for eg in fieldGen do
let! u = eg
target <- u target
return target
}
| Shape.FSharpUnion (:? ShapeFSharpUnion<'T> as shape) ->
let caseFieldGen =
shape.UnionCases
|> Array.map (fun uc -> uc.Fields |> Array.map mkRandomMember)
gen {
let! tag = Gen.integral <| Range.constant 0 (caseFieldGen.Length - 1)
let mutable u = shape.UnionCases.[tag].CreateUninitialized ()
for f in caseFieldGen.[tag] do
let! uf = f
u <- uf u
return u
}
| Shape.CliMutable (:? ShapeCliMutable<'T> as shape) ->
let propGen = shape.Properties |> Array.map mkRandomMember
gen {
let mutable target = shape.CreateUninitialized ()
for ep in propGen do
let! up = ep
target <- up target
return target
}
| Shape.Poco (:? ShapePoco<'T> as shape) ->
let bestCtor =
shape.Constructors
|> Seq.filter (fun c -> c.IsPublic)
|> Seq.sortBy (fun c -> c.Arity)
|> Seq.tryHead
match bestCtor with
| None -> failwithf "Class %O lacking an appropriate ctor" typeof<'T>
| Some ctor ->
ctor.Accept {
new IConstructorVisitor<'T, Gen<'T>> with
member __.Visit<'CtorParams> (ctor : ShapeConstructor<'T, 'CtorParams>) =
let paramGen = auto'<'CtorParams> (config)
gen { let! args = paramGen
return ctor.Invoke args } }
| _ -> raise (System.NotSupportedException ())
let auto<'T>() = auto'<'T>(defaults) |
This looks nice 🚀 |
Actually, the
So the usage is simply gen {
// Using defaults
let! x = Gen.auto()
// Overriding
let! x = Gen.auto'({Gen.defaults with Char = Gen.alphaNum})
} and the compiler will infer what type it should generate based on the usage of |
We're going to need it for the stuff in hedgehogqa/fsharp-hedgehog#133
We should take this on hedgehogqa/fsharp-hedgehog-experimental#5. |
How can I generate discriminated union cases? FsCheck handles this automatically, and I can't see any methods in Hedgehog that could easily allow me to do that. The only thing I can come up with is to create generators for each case of every DU, and then use
Gen.item
or similar to pick one of the generators. (In the case of a very simple DU with little or no data, only case labels,Gen.item
could be used directly.)This has the drawback that if I expand the DU, I have to manually add the new cases to the generator.
The text was updated successfully, but these errors were encountered: