-
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
Simplifying property configuration #346
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,91 +71,46 @@ type PropertyExtensions private () = | |
let (Property property) = property | ||
Property.report property | ||
|
||
[<Extension>] | ||
static member Report (property : Property, config : Hedgehog.PropertyConfig) : Report = | ||
let (Property property) = property | ||
Property.reportWith config property | ||
|
||
[<Extension>] | ||
static member Report (property : Property<bool>) : Report = | ||
Property.reportBool property | ||
|
||
[<Extension>] | ||
static member Report (property : Property<bool>, config : Hedgehog.PropertyConfig) : Report = | ||
Property.reportBoolWith config property | ||
|
||
[<Extension>] | ||
static member Check (property : Property) : unit = | ||
let (Property property) = property | ||
Property.check property | ||
|
||
[<Extension>] | ||
static member Check (property : Property, config : Hedgehog.PropertyConfig) : unit = | ||
let (Property property) = property | ||
Property.checkWith config property | ||
|
||
[<Extension>] | ||
static member Check (property : Property<bool>) : unit = | ||
Property.checkBool property | ||
|
||
[<Extension>] | ||
static member Check (property : Property<bool>, config : Hedgehog.PropertyConfig) : unit = | ||
Property.checkBoolWith config property | ||
|
||
[<Extension>] | ||
static member Recheck (property : Property, size : Size, seed : Seed) : unit = | ||
let (Property property) = property | ||
Property.recheck size seed property | ||
|
||
[<Extension>] | ||
static member Recheck (property : Property, size : Size, seed : Seed, config : Hedgehog.PropertyConfig) : unit = | ||
let (Property property) = property | ||
Property.recheckWith size seed config property | ||
|
||
[<Extension>] | ||
static member Recheck (property : Property<bool>, size : Size, seed : Seed) : unit = | ||
Property.recheckBool size seed property | ||
|
||
[<Extension>] | ||
static member Recheck (property : Property<bool>, size : Size, seed : Seed, config : Hedgehog.PropertyConfig) : unit = | ||
Property.recheckBoolWith size seed config property | ||
|
||
[<Extension>] | ||
static member ReportRecheck (property : Property, size : Size, seed : Seed) : Report = | ||
let (Property property) = property | ||
Property.reportRecheck size seed property | ||
|
||
[<Extension>] | ||
static member ReportRecheck (property : Property, size : Size, seed : Seed, config : Hedgehog.PropertyConfig) : Report = | ||
let (Property property) = property | ||
Property.reportRecheckWith size seed config property | ||
|
||
[<Extension>] | ||
static member ReportRecheck (property : Property<bool>, size : Size, seed : Seed) : Report = | ||
Property.reportRecheckBool size seed property | ||
|
||
[<Extension>] | ||
static member ReportRecheck (property : Property<bool>, size : Size, seed : Seed, config : Hedgehog.PropertyConfig) : Report = | ||
Property.reportRecheckBoolWith size seed config property | ||
|
||
[<Extension>] | ||
static member Render (property : Property) : string = | ||
let (Property property) = property | ||
Property.render property | ||
|
||
[<Extension>] | ||
static member Render (property : Property, config : Hedgehog.PropertyConfig) : string = | ||
let (Property property) = property | ||
Property.renderWith config property | ||
|
||
[<Extension>] | ||
static member Render (property : Property<bool>) : string = | ||
Property.renderBool property | ||
|
||
[<Extension>] | ||
static member Render (property : Property<bool>, config : Hedgehog.PropertyConfig) : string = | ||
Property.renderBoolWith config property | ||
|
||
[<Extension>] | ||
static member Where (property : Property<'T>, filter : Func<'T, bool>) : Property<'T> = | ||
Property.filter filter.Invoke property | ||
|
@@ -184,4 +139,40 @@ type PropertyExtensions private () = | |
Property.ofThrowing projection.Invoke (a, b))) | ||
Property result | ||
|
||
[<Extension>] | ||
static member WithShrinks (property : Property, shrinkLimit : int<shrinks>) : Property = | ||
let (Property property) = property | ||
Property (Property.withShrinks shrinkLimit property) | ||
|
||
[<Extension>] | ||
static member WithoutShrinks (property : Property) : Property = | ||
let (Property property) = property | ||
Property (Property.withoutShrinks property) | ||
|
||
[<Extension>] | ||
static member WithTests (property : Property, testLimit : int<tests>) : Property = | ||
let (Property property) = property | ||
Property (Property.withTests testLimit property) | ||
|
||
[<Extension>] | ||
static member WithConfig (property : Property, config : PropertyConfig) : Property = | ||
let (Property property) = property | ||
Property (Property.withConfig config property) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we can get rid of this, and only expose configuration with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the same as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What would the value be in overriding a user's preferences? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be the user calling this function, it would be their preference. For example, @dharmaturtle talks about how he runs PB tests with a time limit during development and with a test limit during integration. I think the minimal amount of duplicated code to achieve this would be constructing the correct There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could also make an extension method of public static Property ConfigureSpecifically(this Property property) =>
property.WithTests(1000).WithoutShrinkLimit().Whatever();
// in a test
var someProperty = // ...
someProperty.ConfigureSpecifically().OverrideLocally().Check(); There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. I think I would still keep this myself, but I won't argue further in favor of keeping it. |
||
|
||
[<Extension>] | ||
static member WithShrinks (property : Property<bool>, shrinkLimit : int<shrinks>) : Property<bool> = | ||
Property.withShrinks shrinkLimit property | ||
|
||
[<Extension>] | ||
static member WithoutShrinks (property : Property<bool>) : Property<bool> = | ||
Property.withoutShrinks property | ||
|
||
[<Extension>] | ||
static member WithTests (property : Property<bool>, testLimit : int<tests>) : Property<bool> = | ||
Property.withTests testLimit property | ||
|
||
[<Extension>] | ||
static member WithConfig (property : Property<bool>, config : PropertyConfig) : Property<bool> = | ||
Property.withConfig config property | ||
|
||
#endif |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,15 +4,15 @@ open System | |
|
||
[<Struct>] | ||
type Property<'a> = | ||
| Property of Gen<Journal * Outcome<'a>> | ||
| Property of PropertyConfig * Gen<Journal * Outcome<'a>> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about making this into a record? How about this? { Config: PropertyConfig
Generator: Gen<Journal * Outcome<'a>> } There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm okay with this, I think we should also mark the internals as // unsure of the validity of this syntax, just typing into the comment editor right now
type Property<'a> =
private
{
config: PropertyConfig
generator: Gen<Journal * Outcome<'a>>
} In any case, we should make 2 accessor functions, and only use them. They can be marked as let inline config (property : Property<'a>) : PropertyConfig =
property.config
// This already exists as 'toGen'
let inline generator (property : Property<'a>) : PropertyConfig =
property.generator Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am ok with removing the public access scope. However, I think the access scope should be I think we should follow the F# naming guidelines and use Pascal case for record field names. The most common operation is mapping, not (just) getting. As such, I think we should create a submodule inside Property like this (@dharmaturtle knows what I mean): module Config =
let get p = p.Config
let set p c = { p with Config = c }
let map f p = { p with Config = f p.Config } |
||
|
||
module Property = | ||
|
||
let ofGen (x : Gen<Journal * Outcome<'a>>) : Property<'a> = | ||
Property x | ||
let ofGen (gen : Gen<Journal * Outcome<'a>>) : Property<'a> = | ||
Property (PropertyConfig.defaultConfig, gen) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I gave the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We'll need to audit the use of this function, if it's being used as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed. All such calls should be replaced with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about, it a PR before this one, we change |
||
|
||
let toGen (Property x : Property<'a>) : Gen<Journal * Outcome<'a>> = | ||
x | ||
let toGen (Property (_, gen) : Property<'a>) : Gen<Journal * Outcome<'a>> = | ||
gen | ||
|
||
let tryFinally (after : unit -> unit) (m : Property<'a>) : Property<'a> = | ||
Gen.tryFinally after (toGen m) |> ofGen | ||
|
@@ -136,8 +136,8 @@ module Property = | |
| _, Some tree -> loop (nshrinks + 1<shrinks>) tree | ||
loop 0<shrinks> | ||
|
||
let private reportWith' (args : PropertyArgs) (config : PropertyConfig) (p : Property<unit>) : Report = | ||
let random = toGen p |> Gen.toRandom | ||
let private report' (args : PropertyArgs) (Property (config, gen) : Property<unit>) : Report = | ||
let random = gen |> Gen.toRandom | ||
|
||
let nextSize size = | ||
if size >= 100 then | ||
|
@@ -175,32 +175,19 @@ module Property = | |
|
||
loop args 0<tests> 0<discards> | ||
|
||
let reportWith (config : PropertyConfig) (p : Property<unit>) : Report = | ||
let args = PropertyArgs.init | ||
p |> reportWith' args config | ||
|
||
let report (p : Property<unit>) : Report = | ||
p |> reportWith PropertyConfig.defaultConfig | ||
|
||
let reportBoolWith (config : PropertyConfig) (p : Property<bool>) : Report = | ||
p |> bind ofBool |> reportWith config | ||
let args = PropertyArgs.init | ||
p |> report' args | ||
|
||
let reportBool (p : Property<bool>) : Report = | ||
p |> bind ofBool |> report | ||
|
||
let checkWith (config : PropertyConfig) (p : Property<unit>) : unit = | ||
reportWith config p | ||
|> Report.tryRaise | ||
|
||
let check (p : Property<unit>) : unit = | ||
report p | ||
|> Report.tryRaise | ||
|
||
let checkBool (g : Property<bool>) : unit = | ||
g |> bind ofBool |> check | ||
|
||
let checkBoolWith (config : PropertyConfig) (g : Property<bool>) : unit = | ||
g |> bind ofBool |> checkWith config | ||
let checkBool (p : Property<bool>) : unit = | ||
p |> bind ofBool |> check | ||
|
||
/// Converts a possibly-throwing function to | ||
/// a property by treating an exception as a failure. | ||
|
@@ -210,41 +197,24 @@ module Property = | |
with e -> | ||
handle e | ||
|
||
let reportRecheckWith (size : Size) (seed : Seed) (config : PropertyConfig) (p : Property<unit>) : Report = | ||
let reportRecheck (size : Size) (seed : Seed) (p : Property<unit>) : Report = | ||
let args = { | ||
PropertyArgs.init with | ||
RecheckType = RecheckType.None | ||
Seed = seed | ||
Size = size | ||
} | ||
reportWith' args config p | ||
|
||
let reportRecheck (size : Size) (seed : Seed) (p : Property<unit>) : Report = | ||
reportRecheckWith size seed PropertyConfig.defaultConfig p | ||
|
||
let reportRecheckBoolWith (size : Size) (seed : Seed) (config : PropertyConfig) (p : Property<bool>) : Report = | ||
p |> bind ofBool |> reportRecheckWith size seed config | ||
report' args p | ||
|
||
let reportRecheckBool (size : Size) (seed : Seed) (p : Property<bool>) : Report = | ||
p |> bind ofBool |> reportRecheck size seed | ||
|
||
let recheckWith (size : Size) (seed : Seed) (config : PropertyConfig) (p : Property<unit>) : unit = | ||
reportRecheckWith size seed config p | ||
|> Report.tryRaise | ||
|
||
let recheck (size : Size) (seed : Seed) (p : Property<unit>) : unit = | ||
reportRecheck size seed p | ||
|> Report.tryRaise | ||
|
||
let recheckBoolWith (size : Size) (seed : Seed) (config : PropertyConfig) (g : Property<bool>) : unit = | ||
g |> bind ofBool |> recheckWith size seed config | ||
|
||
let recheckBool (size : Size) (seed : Seed) (g : Property<bool>) : unit = | ||
g |> bind ofBool |> recheck size seed | ||
|
||
let renderWith (n : PropertyConfig) (p : Property<unit>) : string = | ||
reportWith n p | ||
|> Report.render | ||
let recheckBool (size : Size) (seed : Seed) (p : Property<bool>) : unit = | ||
p |> bind ofBool |> recheck size seed | ||
|
||
let render (p : Property<unit>) : string = | ||
report p | ||
|
@@ -254,9 +224,25 @@ module Property = | |
reportBool property | ||
|> Report.render | ||
|
||
let renderBoolWith (config : PropertyConfig) (p : Property<bool>) : string = | ||
reportBoolWith config p | ||
|> Report.render | ||
/// Set the number of times a property is allowed to shrink before the test | ||
/// runner gives up and displays the counterexample. | ||
let withShrinks (shrinkLimit : int<shrinks>) (Property (config, gen) : Property<'a>) : Property<'a> = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be nice to just take an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you expand on this? |
||
let config = { config with ShrinkLimit = Some shrinkLimit } | ||
Property (config, gen) | ||
|
||
/// Restores the default shrinking behavior. | ||
let withoutShrinks (Property (config, gen) : Property<'a>) : Property<'a> = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this should be called There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm okay with this, the current name There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a better name that |
||
let config = { config with ShrinkLimit = None } | ||
Property (config, gen) | ||
|
||
/// Set the number of times a property should be executed before it is | ||
/// considered successful. | ||
let withTests (testLimit : int<tests>) (Property (config, gen) : Property<'a>) : Property<'a> = | ||
let config = { config with TestLimit = testLimit } | ||
Property (config, gen) | ||
|
||
let withConfig (config : PropertyConfig) (Property (_, gen) : Property<'a>) : Property<'a> = | ||
Property (config, gen) | ||
|
||
[<AutoOpen>] | ||
module PropertyBuilder = | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
module Hedgehog.Tests.ShrinkTests | ||
module Hedgehog.Tests.ShrinkTests | ||
|
||
open Hedgehog | ||
open TestDsl | ||
|
@@ -199,14 +199,11 @@ let shrinkTests = testList "Shrink tests" [ | |
yield! testCases "Property.reportWith respects shrinkLimit" | ||
[ 0<shrinks>; 1<shrinks>; 2<shrinks> ] <| fun shrinkLimit -> | ||
|
||
let propConfig = | ||
PropertyConfig.defaultConfig | ||
|> PropertyConfig.withShrinks shrinkLimit | ||
let report = | ||
property { | ||
let! actual = Range.linear 1 1_000_000 |> Gen.int | ||
return actual < 500_000 | ||
} |> Property.reportWith propConfig | ||
} |> Property.withShrinks shrinkLimit |> Property.report | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These two |
||
match report.Status with | ||
| Failed failureData -> | ||
failureData.Shrinks =! shrinkLimit | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might actually be nice to keep the
config
overloads. Thoughts?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should only expose a single way to configure properties; this new API should be the only API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not exactly sure what is being discussed here. However, I am not overly concerned with the C# API.