Skip to content

Commit 8f7ffbf

Browse files
committed
Allocation example from Equinox 174
1 parent 254dbc1 commit 8f7ffbf

File tree

12 files changed

+729
-1
lines changed

12 files changed

+729
-1
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module AllocationTests
2+
3+
open Allocation
4+
open FsCheck.Xunit
5+
open Swensen.Unquote
6+
7+
let [<Property>] ``codec can roundtrip`` event =
8+
let ee = Events.codec.Encode(None,event)
9+
let ie = FsCodec.Core.TimelineEvent.Create(0L, ee.EventType, ee.Data)
10+
test <@ Some event = Events.codec.TryDecode ie @>
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
module AllocatorTests
2+
3+
open Allocator
4+
open FsCheck.Xunit
5+
open Swensen.Unquote
6+
open System
7+
8+
type Command =
9+
| Commence of AllocationId * DateTimeOffset
10+
| Complete of AllocationId * Events.Reason
11+
12+
type Result =
13+
| Accepted
14+
| Conflict of AllocationId
15+
16+
let execute cmd state =
17+
match cmd with
18+
| Commence (a,c) ->
19+
match decideCommence a c state with
20+
| CommenceResult.Accepted, es -> Accepted,es
21+
| CommenceResult.Conflict a, es -> Conflict a,es
22+
| Complete (a,r) -> let es = decideComplete a r state in Accepted, es
23+
24+
let [<Property>] properties c1 c2 =
25+
let res,events = execute c1 Folds.initial
26+
let state1 = Folds.fold Folds.initial events
27+
match c1, res, events, state1 with
28+
| Commence (a,c), Accepted, [Events.Commenced ({ allocationId = ea; cutoff = ec } as e)], state ->
29+
test <@ a = ea && c = ec && state = Some e @>
30+
| Complete _, Accepted, [], None ->
31+
() // Non-applicable Complete requests are simply ignored
32+
| _, res, l, _ ->
33+
test <@ List.isEmpty l && res = Accepted @>
34+
35+
let res,events = execute c2 state1
36+
let state2 = Folds.fold state1 events
37+
match state1, c2, res, events, state2 with
38+
// As per above, normal commence
39+
| None, Commence (a,c), Accepted, [Events.Commenced ({ allocationId = ea; cutoff = ec } as e)], state ->
40+
test <@ a = ea && c = ec && state = Some e @>
41+
// Idempotent accept if same allocationId
42+
| Some active as s1, Commence (a,_), Accepted, [], s2 ->
43+
test <@ s1 = s2 && active.allocationId = a @>
44+
// Conflict reports owner allocator
45+
| Some active as s1, Commence (a2,_), Conflict a1, [], s2 ->
46+
test <@ s1 = s2 && a2 <> a1 && a1 = active.allocationId @>
47+
// Correct complete for same allocator is accepted
48+
| Some active, Complete (a,r), Accepted, [Events.Completed { allocationId = ea; reason = er }], None ->
49+
test <@ er = r && ea = a && active.allocationId = a @>
50+
// Completes not for the same allocator are ignored
51+
| Some active as s1, Complete (a,_), Accepted, [], s2 ->
52+
test <@ active.allocationId <> a && s2 = s1 @>
53+
| _, _, res, l, _ ->
54+
test <@ List.isEmpty l && res = Accepted @>
55+
56+
let [<Property>] ``codec can roundtrip`` event =
57+
let ee = Events.codec.Encode(None,event)
58+
let ie = FsCodec.Core.TimelineEvent.Create(0L, ee.EventType, ee.Data)
59+
test <@ Some event = Events.codec.TryDecode ie @>

equinox-fc/Domain.Tests/Domain.Tests.fsproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
<Compile Include="LocationSeriesTests.fs" />
1313
<Compile Include="LocationEpochTests.fs" />
1414
<Compile Include="LocationTests.fs" />
15+
<Compile Include="TicketTests.fs" />
16+
<Compile Include="TicketListTests.fs" />
17+
<Compile Include="AllocatorTests.fs" />
18+
<Compile Include="AllocationTests.fs" />
1519
</ItemGroup>
1620

1721
<ItemGroup>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
module TicketListTests
2+
3+
open FsCheck.Xunit
4+
open Swensen.Unquote
5+
open TicketList
6+
7+
let [<Property>] properties c1 c2 =
8+
let events = interpret c1 Folds.initial
9+
let state1 = Folds.fold Folds.initial events
10+
match c1, events, state1 with
11+
// Empty request -> no Event
12+
| (_,[]), [], state ->
13+
test <@ Set.isEmpty state @>
14+
| (a,t), [Events.Allocated { allocatorId = ea; ticketIds = et }], state ->
15+
test <@ a = ea @>
16+
test <@ state = set t @>
17+
test <@ state = set et @>
18+
| _, l, _ ->
19+
test <@ List.isEmpty l @>
20+
21+
let events = interpret c2 state1
22+
let state2 = Folds.fold state1 events
23+
test <@ Folds.fold state2 [Folds.snapshot state2] = state2 @>
24+
match state1, c2, events, state2 with
25+
// Empty request -> no Event, same state
26+
| s1, (_,[]), [], state ->
27+
test <@ state = s1 @>
28+
// Redundant request -> No Event, same state
29+
| s1, (_,t), [], _ ->
30+
test <@ Set.isSuperset s1 (set t) @>
31+
// Two consecutive commands should both manifest in the state
32+
| s1, (a,t), [Events.Allocated { allocatorId = ea; ticketIds = et }], state ->
33+
test <@ a = ea @>
34+
let et = Set et
35+
test <@ Set.isSuperset (set t) et @>
36+
test <@ Set.intersect s1 et |> Set.isEmpty @>
37+
test <@ Set.isSuperset state s1 @>
38+
test <@ Set.isSuperset state et @>
39+
| _, _, l, _ ->
40+
test <@ List.isEmpty l @>
41+
42+
let [<Property>] ``codec can roundtrip`` event =
43+
let ee = Events.codec.Encode(None,event)
44+
let ie = FsCodec.Core.TimelineEvent.Create(0L, ee.EventType, ee.Data)
45+
test <@ Some event = Events.codec.TryDecode ie @>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
module TicketTests
2+
3+
open FsCheck.Xunit
4+
open Swensen.Unquote
5+
open Ticket
6+
open Ticket.Folds
7+
8+
/// We want to generate Allocate requests with and without the same listId in some cases
9+
let (|MaybeSameCommands|) = function
10+
| Allocate _ as x, Allocate _, cmd3, Choice1Of2 () -> x, x, cmd3
11+
| cmd1, (Allocate _ as x), Allocate _, Choice1Of2 () -> cmd1, x, x
12+
| cmd1, cmd2, cmd3, (Choice1Of2 ()|Choice2Of2 ()) -> cmd1, cmd2, cmd3
13+
14+
/// Explicitly generate sequences with the same allocator running twice or three times
15+
let (|MaybeSameIds|) = function
16+
| Choice1Of4 a -> a, a, a
17+
| Choice2Of4 (a,b) -> a, a, b
18+
| Choice3Of4 (a,b) -> a, b, b
19+
| Choice4Of4 (a,b,c) -> a, b, c
20+
21+
let (|Invariants|) = function
22+
// Revokes always succeed iff Unallocated
23+
| Unallocated, Revoke, true, [], Unallocated ->
24+
()
25+
// Everything else fails
26+
| _, _, res, e, _ ->
27+
test <@ not res && List.isEmpty e @>
28+
29+
let (|ReservedCases|_|) allocator = function
30+
// Reserve given unallocated
31+
| Unallocated, Reserve, true, [Events.Reserved { allocatorId = a }], state ->
32+
test <@ a = allocator && state = Reserved a @>
33+
Some ()
34+
// Idempotent reserve request
35+
| Reserved a, Reserve, true, [], _ ->
36+
test <@ a = allocator @>
37+
Some ()
38+
// Revokes not by the owner are reported as successful, but we force the real owner to do the real relinquish
39+
| (Reserved by | Allocated(by,_)), Revoke, true, [], _ ->
40+
test <@ by <> allocator @>
41+
Some ()
42+
// Revokes succeed iff by the owner
43+
| (Reserved by | Allocated(by,_)), Revoke, true, [Events.Revoked], Unallocated ->
44+
test <@ by = allocator @>
45+
Some ()
46+
// Reservations can transition to Allocations as long as it's the same Allocator requesting
47+
| Reserved a, Allocate l, true, [Events.Allocated { allocatorId = ea; listId = el }], Allocated (sa,sl) ->
48+
test <@ a = allocator && a = ea && a = sa && l = el && l = sl @>
49+
Some()
50+
| _ -> None
51+
52+
let [<Property>] properties (MaybeSameIds (a1,a2,a3)) (MaybeSameCommands (c1,c2,c3)) =
53+
let res, events = decide a1 c1 Folds.initial
54+
let state1 = Folds.fold Folds.initial events
55+
56+
match Folds.initial, c1, res, events, state1 with
57+
| _, Reserve, true, [Events.Reserved { allocatorId = a }], Reserved sa ->
58+
test <@ a = a1 && sa = a1 @>
59+
| Invariants -> ()
60+
61+
let res, events = decide a2 c2 state1
62+
let state2 = Folds.fold state1 events
63+
match state1, c2, res, events, state2 with
64+
| ReservedCases a2 -> ()
65+
| Invariants -> ()
66+
67+
let res, events = decide a3 c3 state2
68+
let state3 = Folds.fold state2 events
69+
match state2, c3, res, events, state3 with
70+
// Idempotent allocate ignore
71+
| Allocated (a,l), Allocate l3, true, [], _ ->
72+
test <@ a = a3 && l = l3 @>
73+
// Allocated -> Revoked
74+
| Allocated (a,_), Revoke, true, [Events.Revoked], Unallocated ->
75+
test <@ a = a3 @>
76+
| ReservedCases a3 -> ()
77+
| Invariants -> ()
78+
79+
let [<Property>] ``codec can roundtrip`` event =
80+
let ee = Events.codec.Encode(None,event)
81+
let ie = FsCodec.Core.TimelineEvent.Create(0L, ee.EventType, ee.Data)
82+
test <@ Some event = Events.codec.TryDecode ie @>

0 commit comments

Comments
 (0)