Skip to content
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

Generalized formatting of generated values #353

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@

"extensions": [
"editorconfig.editorconfig",
"[email protected]"
"[email protected]",
"[email protected]"
],

"settings": {
Expand Down
43 changes: 37 additions & 6 deletions src/Hedgehog/Gen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,41 @@ open System

/// A generator for values and shrink trees of type 'a.
[<Struct>]
type Gen<'a> =
| Gen of Random<Tree<'a>>
type Gen<'a> = private {
Config : GenConfig<'a>
Random : Random<Tree<'a>>
}

module Gen =
module Config =
This conversation was marked as resolved.
Show resolved Hide resolved
let get (gen : Gen<'a>) : GenConfig<'a> =
gen.Config

let ofRandom (r : Random<Tree<'a>>) : Gen<'a> =
Gen r
let map (f : GenConfig<'a> -> GenConfig<'a>) (gen : Gen<'a>) : Gen<'a> =
{ gen with Config = f (get gen) }

let toRandom (Gen r : Gen<'a>) : Random<Tree<'a>> =
r
let set (config : GenConfig<'a>) (gen : Gen<'a>) : Gen<'a> =
map (always config) gen

let format (a : 'a) (gen : Gen<'a>) : string =
let formatter = gen |> Config.get |> GenConfig.getFormatter
formatter a

let withFormatter (formatter : 'a -> string) (gen : Gen<'a>) : Gen<'a> =
gen
|> Config.map (GenConfig.setFormatter formatter)

let withListFormatter (gen : Gen<_>) : Gen<_> =
gen
|> withFormatter (Seq.toList >> sprintf "%A")

let ofRandom (random : Random<Tree<'a>>) : Gen<'a> = {
Config = GenConfig.defaultConfig
Random = random
}

let toRandom (gen : Gen<'a>) : Random<Tree<'a>> =
gen.Random

let delay (f : unit -> Gen<'a>) : Gen<'a> =
Random.delay (toRandom << f) |> ofRandom
Expand Down Expand Up @@ -343,6 +368,12 @@ module Gen =
Random.sized sizedList
|> ofRandom

/// Generates a `System.Collections.Generic.List<T>` using a 'Range' to determine the length.
let resizeArray (range : Range<int>) (g : Gen<'a>) : Gen<ResizeArray<'a>> =
list range g
|> map ResizeArray
|> withListFormatter

/// Generates an array using a 'Range' to determine the length.
let array (range : Range<int>) (g : Gen<'a>) : Gen<array<'a>> =
list range g |> map Array.ofList
Expand Down
17 changes: 17 additions & 0 deletions src/Hedgehog/GenConfig.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Hedgehog

type GenConfig<'a> = private {
Formatter : 'a -> string
}

module GenConfig =

let defaultConfig<'a> =
let formatter: 'a -> string = sprintf "%A"
{ Formatter = formatter }

let getFormatter (config : GenConfig<'a>) : ('a -> string) =
config.Formatter

let setFormatter (formatter : 'a -> string) (config : GenConfig<'a>) : GenConfig<'a> =
{ config with Formatter = formatter }
1 change: 1 addition & 0 deletions src/Hedgehog/Hedgehog.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ https://github.com/hedgehogqa/fsharp-hedgehog/blob/master/doc/index.md
<Compile Include="Range.fs" />
<Compile Include="Random.fs" />
<Compile Include="Shrink.fs" />
<Compile Include="GenConfig.fs" />
<Compile Include="Gen.fs" />
<Compile Include="ListGen.fs" />
<Compile Include="Journal.fs" />
Expand Down
11 changes: 9 additions & 2 deletions src/Hedgehog/Linq/Gen.fs
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,7 @@ type GenExtensions private () =

[<Extension>]
static member List (gen : Gen<'T>, range : Range<int>) : Gen<ResizeArray<'T>> =
Gen.list range gen
|> Gen.map ResizeArray
Gen.resizeArray range gen

[<Extension>]
static member NoShrink (gen : Gen<'T>) : Gen<'T> =
Expand Down Expand Up @@ -292,6 +291,14 @@ type GenExtensions private () =
static member Where (gen : Gen<'T>, predicate : Func<'T, bool>) : Gen<'T> =
Gen.filter predicate.Invoke gen

[<Extension>]
static member WithFormatter (gen : Gen<'T>, formatter : Func<'T, string>) =
Gen.withFormatter formatter.Invoke gen

[<Extension>]
static member WithListFormatter (gen : Gen<ResizeArray<'T>>) =
Gen.withListFormatter gen

[<Extension>]
static member Zip (genA : Gen<'T>, genB : Gen<'U>) : Gen<'T * 'U> =
Gen.zip genA genB
Expand Down
19 changes: 1 addition & 18 deletions src/Hedgehog/Property.fs
Original file line number Diff line number Diff line change
Expand Up @@ -98,26 +98,9 @@ module Property =
|> bindGen kTry
|> ofGen

let private printValue (value) : string =
// sprintf "%A" is not prepared for printing ResizeArray<_> (C# List<T>) so we prepare the value instead
let prepareForPrinting (value: obj) : obj =
#if FABLE_COMPILER
value
#else
let t = value.GetType()
// have to use TypeInfo due to targeting netstandard 1.6
let t = System.Reflection.IntrospectionExtensions.GetTypeInfo(t)
let isList = t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<ResizeArray<_>>
if isList
then value :?> System.Collections.IEnumerable |> Seq.cast<obj> |> List.ofSeq :> obj
else value
#endif

value |> prepareForPrinting |> sprintf "%A"
This conversation was marked as resolved.
Show resolved Hide resolved

let forAll (k : 'a -> Property<'b>) (gen : Gen<'a>) : Property<'b> =
let prepend (x : 'a) =
counterexample (fun () -> printValue x)
counterexample (fun () -> gen |> Gen.format x)
|> set x
|> bind k
|> toGen
Expand Down
13 changes: 12 additions & 1 deletion tests/Hedgehog.Tests/PropertyTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,18 @@ let propertyTests = testList "Property tests" [
fableIgnore "generated C# list of five elements is not abbreviated in the failure report" <| fun _ ->
let report =
property {
let! xs = Range.singleton 0 |> Gen.int32 |> Gen.list (Range.singleton 5) |> Gen.map ResizeArray
let gen = Gen.int32 (Range.singleton 0)
let! xs = Gen.resizeArray (Range.singleton 5) gen
return false
}
|> Property.renderWith (PropertyConfig.withShrinks 0<shrinks> PropertyConfig.defaultConfig)
Expect.isNotMatch report "\.\.\." "Abbreviation (...) found"

fableIgnore "generated seq of five elements converted to C# list is not abbreviated in the failure report" <| fun _ ->
let report =
property {
let gen = Gen.int32 (Range.singleton 0)
let! xs = Gen.seq (Range.singleton 5) gen |> Gen.map ResizeArray |> Gen.withListFormatter
return false
}
|> Property.renderWith (PropertyConfig.withShrinks 0<shrinks> PropertyConfig.defaultConfig)
Expand Down