diff --git a/samples/Fc/Domain.Tests/Infrastructure.fs b/samples/Fc/Domain.Tests/Infrastructure.fs index ea9cd2735..3c98ac248 100644 --- a/samples/Fc/Domain.Tests/Infrastructure.fs +++ b/samples/Fc/Domain.Tests/Infrastructure.fs @@ -5,6 +5,7 @@ open Serilog open System let (|Id|) (x : Guid) = x.ToString "N" |> FSharp.UMX.UMX.tag +let inline mkId () = Guid.NewGuid() |> (|Id|) let (|Ids|) (xs : Guid[]) = xs |> Array.map (|Id|) let (|IdsAtLeastOne|) (Id x, Ids xs) = Seq.append xs (Seq.singleton x) |> Seq.toArray diff --git a/samples/Fc/Domain.Tests/LocationTests.fs b/samples/Fc/Domain.Tests/LocationTests.fs index 54a4f4c6e..736cdf08f 100644 --- a/samples/Fc/Domain.Tests/LocationTests.fs +++ b/samples/Fc/Domain.Tests/LocationTests.fs @@ -1,6 +1,7 @@ module LocationTests open FsCheck.Xunit +open FSharp.UMX open Location open Swensen.Unquote open System @@ -25,44 +26,44 @@ module Location = let epochs = Epoch.create (Epoch.resolve store) maxAttempts create (zeroBalance, shouldClose) (series, epochs) -let [] ``parallel properties`` (IdsAtLeastOne locations) (deltas : _[]) maxEvents = Async.RunSynchronously <| async { - let store = Equinox.MemoryStore.VolatileStore() - let zeroBalance = 0 - let maxEvents = max 1 maxEvents - let shouldClose (state : Epoch.Folds.OpenState) = state.count > maxEvents - let service = Location.MemoryStore.createService (zeroBalance, shouldClose) store +let run (service : LocationService) (IdsAtLeastOne locations, deltas : _[]) = Async.RunSynchronously <| async { + let runId = mkId () // Need to make making state in store unique when replaying or shrinking + let locations = locations |> Array.map (fun x -> % (sprintf "%O_%O" runId x)) + + let updates = deltas |> Seq.mapi (fun i x -> locations.[i % locations.Length], x) |> Seq.cache + + (* Apply random deltas *) + let adjust delta (bal : Epoch.Folds.Balance) = let value = max -bal delta if value = 0 then 0, [] else value, [Location.Epoch.Events.Delta { value = value }] - let updates = deltas |> Seq.mapi (fun i x -> locations.[i % locations.Length], x) |> Seq.cache - let! appliedDeltas = seq { for loc,x in updates -> async { let! _,eff = service.Execute(loc, adjust x) in return loc,eff } } |> Async.Parallel - let! balances = seq { for loc in locations -> async { let! bal,() = service.Execute(loc,(fun _ -> (),[])) in return loc,bal } } |> Async.Parallel let expectedBalances = Seq.append (seq { for l in locations -> l, 0}) appliedDeltas |> Seq.groupBy fst |> Seq.map (fun (l,xs) -> l, xs |> Seq.sumBy snd) |> Set.ofSeq + + (* Verify loading yields identical state *) + + let! balances = seq { for loc in locations -> async { let! bal,() = service.Execute(loc,(fun _ -> (),[])) in return loc,bal } } |> Async.Parallel test <@ expectedBalances = Set.ofSeq balances @> } +let [] ``MemoryStore properties`` maxEvents args = + let store = Equinox.MemoryStore.VolatileStore() + let zeroBalance = 0 + let maxEvents = max 1 maxEvents + let shouldClose (state : Epoch.Folds.OpenState) = state.count > maxEvents + let service = Location.MemoryStore.createService (zeroBalance, shouldClose) store + run service args + type Cosmos(testOutput) = let context,cache = Cosmos.connect () - // NOTE this works very well for as long as we can guarantee we have a single instance of the Daemon in play - // i.e. we'll need to remove this e.g. if the Cosmos ones can run at the same time as this suite let log = testOutput |> TestOutputAdapter |> createLogger do Serilog.Log.Logger <- log - let [] properties (IdsAtLeastOne locations) (deltas : _[]) maxEvents = Async.RunSynchronously <| async { + let [] properties maxEvents args = let zeroBalance = 0 let maxEvents = max 1 maxEvents let shouldClose (state : Epoch.Folds.OpenState) = state.count > maxEvents let service = Location.Cosmos.createService (zeroBalance, shouldClose) (context,cache,Int32.MaxValue) - let adjust delta (bal : Epoch.Folds.Balance) = - let value = max -bal delta - if value = 0 then 0, [] - else value, [Location.Epoch.Events.Delta { value = value }] - let updates = deltas |> Seq.mapi (fun i x -> locations.[i % locations.Length], x) |> Seq.cache - - let! appliedDeltas = seq { for loc,x in updates -> async { let! _,eff = service.Execute(loc, adjust x) in return loc,eff } } |> Async.Parallel - let! balances = seq { for loc in locations -> async { let! bal,() = service.Execute(loc,(fun _ -> (),[])) in return loc,bal } } |> Async.Parallel - let expectedBalances = Seq.append (seq { for l in locations -> l, 0}) appliedDeltas |> Seq.groupBy fst |> Seq.map (fun (l,xs) -> l, xs |> Seq.sumBy snd) |> Set.ofSeq - test <@ expectedBalances = Set.ofSeq balances @> } \ No newline at end of file + run service args \ No newline at end of file diff --git a/samples/Fc/Domain/Location.fs b/samples/Fc/Domain/Location.fs index 404be4dfb..30518cae5 100644 --- a/samples/Fc/Domain/Location.fs +++ b/samples/Fc/Domain/Location.fs @@ -31,12 +31,12 @@ type LocationService internal (zeroBalance, shouldClose, series : Series.Service [] module Helpers = - let create (zeroBalance, shouldClose) (series,epochs) = + let create (zeroBalance, shouldClose) (series, epochs) = LocationService(zeroBalance, shouldClose, series, epochs) module Cosmos = - let createService (zeroBalance, shouldClose) (context,cache,maxAttempts) = + let createService (zeroBalance, shouldClose) (context, cache, maxAttempts) = let series = Series.Cosmos.createService (context, cache, maxAttempts) let epochs = Epoch.Cosmos.createService (context, cache, maxAttempts) create (zeroBalance, shouldClose) (series, epochs) \ No newline at end of file