Skip to content
This repository was archived by the owner on Nov 27, 2025. It is now read-only.

Commit 07156d8

Browse files
authored
Merge pull request #81 from hedgehogqa/merge-autogen-config
Merge autogen config
2 parents 82fc92e + ee1c8e3 commit 07156d8

File tree

9 files changed

+216
-129
lines changed

9 files changed

+216
-129
lines changed

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ let! shuffled = GenX.shuffle lst // e.g. [2; 1; 5; 3; 4]
4343

4444
**Shuffle/permute the case of a string:**
4545

46-
```f#
46+
```f#
4747
let str = "abcde"
4848
let! shuffled = GenX.shuffleCase str // e.g. "aBCdE"
4949
```
@@ -170,11 +170,11 @@ let! chars, f = charListGen |> GenX.withDistinctMapTo intGen
170170
type Union =
171171
| Husband of int
172172
| Wife of string
173-
173+
174174
type Record =
175175
{Sport: string
176176
Time: TimeSpan}
177-
177+
178178
// Explicit type parameter may not be necessary if it can be inferred.
179179
let! union = GenX.auto<Union>
180180
let! record = GenX.auto<Record>
@@ -194,9 +194,9 @@ let! recursive = GenX.auto<Recursive>
194194

195195
```f#
196196
let! myVal =
197-
{GenX.defaults with
198-
SeqRange = Range.exponential 1 10
199-
RecursionDepth = 2}
197+
GenX.defaults
198+
|> AutoGenConfig.setSeqRange (Range.exponential 1 10)
199+
|> AutoGenConfig.setRecursionDepth 2
200200
// Will use this generator for all ints
201201
|> AutoGenConfig.addGenerator (Gen.int (Range.linear 0 10))
202202
// Will use this generator when generating its return type
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
module Hedgehog.Experimental.Tests.AutoGenConfigTests
2+
3+
open Xunit
4+
open Swensen.Unquote
5+
open Hedgehog
6+
7+
[<Fact>]
8+
let ``merging AutoGenConfig preserves set values``() =
9+
let expectedRange = Range.exponential 2 6
10+
let expectedDepth = 2
11+
let config1 =
12+
AutoGenConfig.defaults
13+
|> AutoGenConfig.setSeqRange expectedRange
14+
|> AutoGenConfig.setRecursionDepth expectedDepth
15+
|> AutoGenConfig.addGenerator (Gen.int32 (Range.exponentialBounded()))
16+
let config2 = AutoGenConfig.defaults |> AutoGenConfig.addGenerator Gen.bool
17+
let merged = AutoGenConfig.merge config1 config2
18+
test <@ AutoGenConfig.recursionDepth merged = expectedDepth @>
19+
20+
let property = property {
21+
let! array = merged |> GenX.autoWith<(int * bool)[]>
22+
test <@ Array.length array >= 2 && Array.length array <= 6 @>
23+
}
24+
25+
Property.check property
26+
27+
[<Fact>]
28+
let ``merging AutoGenConfig overrides values``() =
29+
let previousRange = Range.exponential 10 15
30+
let expectedRange = Range.exponential 2 6
31+
let expectedDepth = 2
32+
let config1 = AutoGenConfig.defaults |> AutoGenConfig.setSeqRange previousRange |> AutoGenConfig.setRecursionDepth 1
33+
let config2 =
34+
AutoGenConfig.defaults
35+
|> AutoGenConfig.setSeqRange expectedRange
36+
|> AutoGenConfig.setRecursionDepth expectedDepth
37+
|> AutoGenConfig.addGenerator (Gen.int32 (Range.exponentialBounded()))
38+
39+
let merged = AutoGenConfig.merge config1 config2
40+
test <@ AutoGenConfig.recursionDepth merged = expectedDepth @>
41+
42+
let property = property {
43+
let! array = merged |> GenX.autoWith<int[]>
44+
test <@ Array.length array >= 2 && Array.length array <= 6 @>
45+
}
46+
47+
Property.check property

src/Hedgehog.Experimental.Tests/GenTests.fs

Lines changed: 30 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -323,15 +323,15 @@ let ``auto with recursive option members does not cause stack overflow using def
323323
let ``auto with recursive option members respects max recursion depth`` () =
324324
Property.check <| property {
325325
let! depth = Gen.int32 <| Range.exponential 0 5
326-
let! x = GenX.autoWith<RecOption> {GenX.defaults with RecursionDepth = depth}
326+
let! x = GenX.autoWith<RecOption> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth)
327327
x.Depth <=! depth
328328
}
329329

330330
[<Fact>]
331331
let ``auto with recursive option members generates some values with max recursion depth`` () =
332332
checkWith 10<tests> <| property {
333333
let! depth = Gen.int32 <| Range.linear 1 5
334-
let! xs = GenX.autoWith<RecOption> {GenX.defaults with RecursionDepth = depth}
334+
let! xs = GenX.autoWith<RecOption> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth)
335335
|> (Gen.list (Range.singleton 100))
336336
test <@ xs |> List.exists (fun x -> x.Depth = depth) @>
337337
}
@@ -355,15 +355,15 @@ let ``auto with recursive array members does not cause stack overflow using defa
355355
let ``auto with recursive array members respects max recursion depth`` () =
356356
Property.check <| property {
357357
let! depth = Gen.int32 <| Range.exponential 0 5
358-
let! x = GenX.autoWith<RecArray> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 0 5}
358+
let! x = GenX.autoWith<RecArray> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 0 5))
359359
x.Depth <=! depth
360360
}
361361

362362
[<Fact>]
363363
let ``auto with recursive array members generates some values with max recursion depth`` () =
364364
checkWith 10<tests> <| property {
365365
let! depth = Gen.int32 <| Range.linear 1 5
366-
let! xs = GenX.autoWith<RecArray> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 1 5}
366+
let! xs = GenX.autoWith<RecArray> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 1 5))
367367
|> (Gen.list (Range.singleton 100))
368368
test <@ xs |> List.exists (fun x -> x.Depth = depth) @>
369369
}
@@ -387,15 +387,15 @@ let ``auto with recursive list members does not cause stack overflow using defau
387387
let ``auto with recursive list members respects max recursion depth`` () =
388388
Property.check <| property {
389389
let! depth = Gen.int32 <| Range.exponential 0 5
390-
let! x = GenX.autoWith<RecList> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 0 5}
390+
let! x = GenX.autoWith<RecList> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 0 5))
391391
x.Depth <=! depth
392392
}
393393

394394
[<Fact>]
395395
let ``auto with recursive list members generates some values with max recursion depth`` () =
396396
checkWith 10<tests> <| property {
397397
let! depth = Gen.int32 <| Range.linear 1 5
398-
let! xs = GenX.autoWith<RecList> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 1 5}
398+
let! xs = GenX.autoWith<RecList> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 1 5))
399399
|> (Gen.list (Range.singleton 100))
400400
test <@ xs |> List.exists (fun x -> x.Depth = depth) @>
401401
}
@@ -419,15 +419,15 @@ let ``auto with recursive ResizeArray members does not cause stack overflow usin
419419
let ``auto with recursive ResizeArray members respects max recursion depth`` () =
420420
Property.check <| property {
421421
let! depth = Gen.int32 <| Range.exponential 0 5
422-
let! x = GenX.autoWith<RecResizeArray> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 0 5}
422+
let! x = GenX.autoWith<RecResizeArray> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 0 5))
423423
x.Depth <=! depth
424424
}
425425

426426
[<Fact>]
427427
let ``auto with recursive ResizeArray members generates some values with max recursion depth`` () =
428428
checkWith 10<tests> <| property {
429429
let! depth = Gen.int32 <| Range.linear 1 5
430-
let! xs = GenX.autoWith<RecResizeArray> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 1 5}
430+
let! xs = GenX.autoWith<RecResizeArray> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 1 5))
431431
|> (Gen.list (Range.singleton 100))
432432
test <@ xs |> List.exists (fun x -> x.Depth = depth) @>
433433
}
@@ -451,15 +451,15 @@ let ``auto with recursive Dictionary members does not cause stack overflow using
451451
let ``auto with recursive Dictionary members respects max recursion depth`` () =
452452
Property.check <| property {
453453
let! depth = Gen.int32 <| Range.exponential 0 5
454-
let! x = GenX.autoWith<RecDictionary> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 0 5}
454+
let! x = GenX.autoWith<RecDictionary> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 0 5))
455455
x.Depth <=! depth
456456
}
457457

458458
[<Fact>]
459459
let ``auto with recursive Dictionary members generates some values with max recursion depth`` () =
460460
checkWith 10<tests> <| property {
461461
let! depth = Gen.int32 <| Range.linear 1 5
462-
let! xs = GenX.autoWith<RecDictionary> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 1 5}
462+
let! xs = GenX.autoWith<RecDictionary> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 1 5))
463463
|> (Gen.list (Range.singleton 100))
464464
test <@ xs |> List.exists (fun x -> x.Depth = depth) @>
465465
}
@@ -483,15 +483,15 @@ let ``auto with recursive set members does not cause stack overflow using defaul
483483
let ``auto with recursive set members respects max recursion depth`` () =
484484
Property.check <| property {
485485
let! depth = Gen.int32 <| Range.exponential 0 5
486-
let! x = GenX.autoWith<RecSet> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 0 5}
486+
let! x = GenX.autoWith<RecSet> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 0 5))
487487
x.Depth <=! depth
488488
}
489489

490490
[<Fact>]
491491
let ``auto with recursive set members generates some values with max recursion depth`` () =
492492
checkWith 10<tests> <| property {
493493
let! depth = Gen.int32 <| Range.linear 1 5
494-
let! xs = GenX.autoWith<RecSet> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 1 5}
494+
let! xs = GenX.autoWith<RecSet> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 1 5))
495495
|> (Gen.list (Range.singleton 100))
496496
test <@ xs |> List.exists (fun x -> x.Depth = depth) @>
497497
}
@@ -515,15 +515,15 @@ let ``auto with recursive map members does not cause stack overflow using defaul
515515
let ``auto with recursive map members respects max recursion depth`` () =
516516
Property.check <| property {
517517
let! depth = Gen.int32 <| Range.exponential 0 5
518-
let! x = GenX.autoWith<RecMap> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 0 5}
518+
let! x = GenX.autoWith<RecMap> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 0 5))
519519
x.Depth <=! depth
520520
}
521521

522522
[<Fact>]
523523
let ``auto with recursive map members generates some values with max recursion depth`` () =
524524
checkWith 10<tests> <| property {
525525
let! depth = Gen.int32 <| Range.linear 1 5
526-
let! xs = GenX.autoWith<RecMap> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 1 5}
526+
let! xs = GenX.autoWith<RecMap> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 1 5))
527527
|> (Gen.list (Range.singleton 100))
528528
test <@ xs |> List.exists (fun x -> x.Depth = depth) @>
529529
}
@@ -563,8 +563,8 @@ let ``auto with mutually recursive types does not cause stack overflow using def
563563
let ``auto with mutually recursive types respects max recursion depth`` () =
564564
Property.check <| property {
565565
let! depth = Gen.int32 <| Range.exponential 0 5
566-
let! x1 = GenX.autoWith<MutuallyRecursive1> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 0 5}
567-
let! x2 = GenX.autoWith<MutuallyRecursive2> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 0 5}
566+
let! x1 = GenX.autoWith<MutuallyRecursive1> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 0 5))
567+
let! x2 = GenX.autoWith<MutuallyRecursive2> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 0 5))
568568
x1.Depth <=! depth
569569
x2.Depth <=! depth
570570
}
@@ -573,9 +573,9 @@ let ``auto with mutually recursive types respects max recursion depth`` () =
573573
let ``auto with mutually recursive types generates some values with max recursion depth`` () =
574574
checkWith 10<tests> <| property {
575575
let! depth = Gen.int32 <| Range.linear 1 5
576-
let! xs1 = GenX.autoWith<MutuallyRecursive1> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 1 5}
576+
let! xs1 = GenX.autoWith<MutuallyRecursive1> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 1 5))
577577
|> (Gen.list (Range.singleton 100))
578-
let! xs2 = GenX.autoWith<MutuallyRecursive2> {GenX.defaults with RecursionDepth = depth; SeqRange = Range.exponential 1 5}
578+
let! xs2 = GenX.autoWith<MutuallyRecursive2> (GenX.defaults |> AutoGenConfig.setRecursionDepth depth |> AutoGenConfig.setSeqRange (Range.exponential 1 5))
579579
|> (Gen.list (Range.singleton 100))
580580
test <@ xs1 |> List.exists (fun x -> x.Depth = depth) @>
581581
test <@ xs2 |> List.exists (fun x -> x.Depth = depth) @>
@@ -877,7 +877,8 @@ module ShrinkTests =
877877
let ``one-dimentional array shrinks correctly when empty disallowed`` () =
878878
let property = property {
879879
let! array =
880-
{ GenX.defaults with SeqRange = Range.constant 2 5 }
880+
GenX.defaults
881+
|> AutoGenConfig.setSeqRange (Range.constant 2 5)
881882
|> GenX.autoWith<int []>
882883
test <@ 1 <> array.[0] @>
883884
}
@@ -897,7 +898,8 @@ module ShrinkTests =
897898
let ``two-dimentional array shrinks correctly when empty disallowed`` () =
898899
let property = property {
899900
let! array =
900-
{ GenX.defaults with SeqRange = Range.constant 1 5 }
901+
GenX.defaults
902+
|> AutoGenConfig.setSeqRange (Range.constant 1 5)
901903
|> GenX.autoWith<int [,]>
902904
test <@ 1 <> array.[0,0] @>
903905
}
@@ -910,7 +912,8 @@ module ShrinkTests =
910912
let ``auto of ResizeArray shrinks correctly`` () =
911913
let property = property {
912914
let! resizeArray =
913-
{ GenX.defaults with SeqRange = Range.constant 4 4 }
915+
GenX.defaults
916+
|> AutoGenConfig.setSeqRange (Range.constant 4 4)
914917
|> GenX.autoWith<ResizeArray<int>>
915918
test <@ 1 <> resizeArray.[0] @>
916919
}
@@ -988,7 +991,8 @@ let ``auto can generate record with Nullable fields`` () =
988991
let ``auto can generate Nullable bool without recursion`` () =
989992
Property.check <| property {
990993
let! _ =
991-
{ GenX.defaults with RecursionDepth = 0 }
994+
GenX.defaults
995+
|> AutoGenConfig.setRecursionDepth 0
992996
|> GenX.autoWith<Nullable<bool>>
993997
()
994998
}
@@ -998,7 +1002,8 @@ let ``auto can generate seq`` () =
9981002
Property.checkBool <| property {
9991003
let! expectedLen = Gen.int32 (Range.linear 0 105)
10001004
let! xs =
1001-
{ GenX.defaults with SeqRange = Range.singleton expectedLen }
1005+
GenX.defaults
1006+
|> AutoGenConfig.setSeqRange (Range.singleton expectedLen)
10021007
|> GenX.autoWith<seq<int>>
10031008

10041009
return Seq.length xs = expectedLen
@@ -1030,15 +1035,15 @@ type Poodle() =
10301035

10311036
[<Fact>]
10321037
let ``Type Dog is Shape_CliMutable`` () =
1033-
let isDogCliMutable =
1038+
let isDogCliMutable =
10341039
match TypeShape.Create<Dog> () with
10351040
| Shape.CliMutable _ -> true
10361041
| _ -> false
10371042
test <@ isDogCliMutable @>
10381043

10391044
[<Fact>]
10401045
let ``Type Poodle is Shape_CliMutable`` () =
1041-
let isPoodleCliMutable =
1046+
let isPoodleCliMutable =
10421047
match TypeShape.Create<Poodle> () with
10431048
| Shape.CliMutable _ -> true
10441049
| _ -> false

src/Hedgehog.Experimental.Tests/Hedgehog.Experimental.Tests.fsproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<Compile Include="TypeUtilsTests.fs" />
1111
<Compile Include="GenTests.fs" />
1212
<Compile Include="GenericGenTests.fs" />
13+
<Compile Include="AutoGenConfigTests.fs" />
1314
</ItemGroup>
1415

1516
<ItemGroup>
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
namespace Hedgehog
2+
3+
open System
4+
open System.Reflection
5+
6+
type AutoGenConfig = internal {
7+
seqRange: Range<int> option
8+
recursionDepth: int option
9+
generators: GeneratorCollection
10+
}
11+
12+
module AutoGenConfig =
13+
14+
let private defaultSeqRange = Range.exponential 0 50
15+
let private defaultRecursionDepth = 1
16+
17+
let defaults = {
18+
seqRange = None
19+
recursionDepth = None
20+
generators = GeneratorCollection.empty
21+
}
22+
23+
let private mapGenerators f (config: AutoGenConfig) =
24+
{ config with generators = f config.generators }
25+
26+
let seqRange (config: AutoGenConfig) = config.seqRange |> Option.defaultValue defaultSeqRange
27+
let setSeqRange (range: Range<int>) (config: AutoGenConfig) =
28+
{ config with seqRange = Some range }
29+
30+
let recursionDepth (config: AutoGenConfig) = config.recursionDepth |> Option.defaultValue defaultRecursionDepth
31+
let setRecursionDepth (depth: int) (config: AutoGenConfig) =
32+
{ config with recursionDepth = Some depth }
33+
34+
/// Merge two configurations.
35+
/// Values from the second configuration take precedence when they are set.
36+
let merge (baseConfig: AutoGenConfig) (extraConfig: AutoGenConfig) =
37+
{
38+
seqRange = extraConfig.seqRange |> Option.orElse baseConfig.seqRange
39+
recursionDepth = extraConfig.recursionDepth |> Option.orElse baseConfig.recursionDepth
40+
generators = GeneratorCollection.merge baseConfig.generators extraConfig.generators
41+
}
42+
43+
/// Add a generator to the configuration.
44+
let addGenerator (gen: Gen<'a>) =
45+
mapGenerators (GeneratorCollection.map _.SetItem(typeof<'a>, ([||], fun _ _ -> gen)))
46+
47+
/// Add generators from a given type.
48+
/// The type is expected to have static methods that return Gen<_>.
49+
/// These methods can have parameters which are required to be of type Gen<_>.
50+
let addGenerators<'a> (config: AutoGenConfig) =
51+
let isGen (t: Type) =
52+
t.IsGenericType && t.GetGenericTypeDefinition() = typedefof<Gen<_>>
53+
54+
let tryUnwrapGenParameters (methodInfo: MethodInfo) : Option<Type[]> =
55+
methodInfo.GetParameters()
56+
|> Array.fold (fun acc param ->
57+
match acc, isGen param.ParameterType with
58+
| Some types, true ->
59+
Some (Array.append types [| param.ParameterType.GetGenericArguments().[0] |])
60+
| _ -> None
61+
) (Some [||])
62+
63+
typeof<'a>.GetMethods(BindingFlags.Static ||| BindingFlags.Public)
64+
|> Seq.choose (fun methodInfo ->
65+
match isGen methodInfo.ReturnType, tryUnwrapGenParameters methodInfo with
66+
| true, Some typeArray ->
67+
let targetType = methodInfo.ReturnType.GetGenericArguments().[0]
68+
let factory: Type[] -> obj[] -> obj = fun types gens ->
69+
let methodToCall =
70+
if Array.isEmpty types then methodInfo
71+
else methodInfo.MakeGenericMethod(types)
72+
methodToCall.Invoke(null, gens)
73+
Some (targetType, typeArray, factory)
74+
| _ -> None)
75+
|> Seq.fold (fun cfg (targetType, typeArray, factory) ->
76+
cfg |> mapGenerators (GeneratorCollection.addGenerator targetType typeArray factory))
77+
config

0 commit comments

Comments
 (0)