1
-
Removed:
(** Unit tests for {!Logbook}, authored against logbook.mli.
1
+
Added:
(** Unit tests for {!Logbook}. *)
2
2
3
-
Removed:
Under test: an entry accepts sets matching its prescription and rejects
4
-
Removed:
others; the logbook derives per-exercise evidence and an informational
5
-
Removed:
readiness reading from finished entries. *)
6
-
Removed:
7
3
let ok = function Ok v -> v | Error _ -> Alcotest.fail "expected Ok"
8
-
Removed:
let ts = Recovery.timestamp_of_unix_seconds
9
-
Removed:
let mk_w kg = ok (Units.Weight.of_kg kg)
10
-
Removed:
let mk_r n = ok (Units.Reps.of_int n)
11
-
Removed:
let prescription = List.hd (Routine.workouts Routine.consolidation_routine)
12
-
Removed:
(* Consolidation Workout A: Back Squat (straight), Pulldown (straight). *)
13
4
14
-
Removed:
let prescribed_exercises =
15
-
Removed:
Workout_prescription.set_groups prescription
16
-
Removed:
|> List.concat_map Set_group_prescription.exercises
5
+
Added:
let get id =
6
+
Added:
match Exercise.find id with
7
+
Added:
| Some e -> e
8
+
Added:
| None -> Alcotest.failf "catalog is missing %S" id
17
9
18
-
Removed:
let squat = List.hd prescribed_exercises
10
+
Added:
let kg n = ok (Units.Weight.of_kg n)
11
+
Added:
let reps n = ok (Units.Reps.of_int n)
12
+
Added:
let at s = Recovery.timestamp_of_unix_seconds s
13
+
Added:
let day n = at (n * 86_400)
14
+
Added:
let cleared = Option.get (Recovery.clear Recovery.Ready)
15
+
Added:
let routine = Routine.ideal_routine
16
+
Added:
let workout n = List.nth (Routine.workouts routine) n
19
17
20
-
Removed:
let unprescribed =
21
-
Removed:
List.find
22
-
Removed:
(fun ex -> not (List.exists (Exercise.equal ex) prescribed_exercises))
23
-
Removed:
Exercise.catalog
18
+
Added:
let move id load r =
19
+
Added:
Stimulus.Movement.make ~exercise:(get id) ~load:(kg load) ~reps:(reps r)
20
+
Added:
~outcome:Stimulus.Positive_failure
24
21
25
-
Removed:
let working exercise load reps =
26
-
Removed:
Set.Working.make ~exercise ~load:(mk_w load) ~reps:(mk_r reps)
27
-
Removed:
~outcome:Set.Working.Positive_failure
22
+
Added:
let single id load r = ok (Stimulus.make (Stimulus.Single (move id load r)))
28
23
29
-
Removed:
let start () = Logbook.Entry.start prescription ~started_at:(ts 0)
24
+
Added:
(* A finished workout: Day n of the Ideal Routine, with one stimulus logged. *)
25
+
Added:
let logged ~workout:w ~on ~stimuli =
26
+
Added:
let e =
27
+
Added:
List.fold_left
28
+
Added:
(fun e s -> ok (Entry.add_stimulus e s))
29
+
Added:
(Entry.start w ~clearance:cleared ~started_at:on)
30
+
Added:
stimuli
31
+
Added:
in
32
+
Added:
ok (Entry.finish e ~ended_at:on)
30
33
31
-
Removed:
let entry_tests =
34
+
Added:
let laterals load r = single "laterals" load r
35
+
Added:
36
+
Added:
let basic_tests =
32
37
[
33
-
Removed:
( "a set for a prescribed exercise is accepted",
38
+
Added:
( "an empty logbook knows nothing",
34
39
`Quick,
35
40
fun () ->
36
-
Removed:
let e = start () in
37
-
Removed:
let group = Set_group.straight ~working:(working squat 100.0 6) () in
41
+
Added:
Alcotest.(check int)
42
+
Added:
"no entries" 0
43
+
Added:
(List.length (Logbook.entries Logbook.empty));
38
44
Alcotest.(check bool)
39
-
Removed:
"accepted" true
40
-
Removed:
(Result.is_ok (Logbook.Entry.add_group e group)) );
41
-
Removed:
( "a set for an unprescribed exercise is rejected",
45
+
Added:
"no last prescription" true
46
+
Added:
(Option.is_none (Logbook.last_prescription Logbook.empty)) );
47
+
Added:
( "entries come back most recent first, however they were added",
42
48
`Quick,
43
49
fun () ->
44
-
Removed:
let e = start () in
45
-
Removed:
let group =
46
-
Removed:
Set_group.straight ~working:(working unprescribed 20.0 12) ()
50
+
Added:
let book =
51
+
Added:
( Logbook.empty |> fun b ->
52
+
Added:
Logbook.add b (logged ~workout:(workout 1) ~on:(day 3) ~stimuli:[])
53
+
Added:
)
54
+
Added:
|> fun b ->
55
+
Added:
Logbook.add b (logged ~workout:(workout 0) ~on:(day 1) ~stimuli:[])
47
56
in
48
-
Removed:
Alcotest.(check bool)
49
-
Removed:
"rejected" true
50
-
Removed:
(Result.is_error (Logbook.Entry.add_group e group)) );
51
-
Removed:
( "a finished entry rejects further groups",
57
+
Added:
Alcotest.(check (list string))
58
+
Added:
"newest first" [ "Day 2"; "Day 1" ]
59
+
Added:
(List.map
60
+
Added:
(fun e -> Workout_prescription.name (Entry.prescription e))
61
+
Added:
(Logbook.entries book)) );
62
+
Added:
( "the last prescription is what the cycle should advance from",
52
63
`Quick,
53
64
fun () ->
54
-
Removed:
let e = Logbook.Entry.finish (start ()) ~ended_at:(ts 100) in
55
-
Removed:
let group = Set_group.straight ~working:(working squat 100.0 6) () in
56
-
Removed:
Alcotest.(check bool)
57
-
Removed:
"rejected" true
58
-
Removed:
(Result.is_error (Logbook.Entry.add_group e group)) );
59
-
Removed:
( "unperformed lists prescribed exercises with no logged set",
65
+
Added:
let book =
66
+
Added:
Logbook.add Logbook.empty
67
+
Added:
(logged ~workout:(workout 1) ~on:(day 1) ~stimuli:[])
68
+
Added:
in
69
+
Added:
let last = Option.get (Logbook.last_prescription book) in
70
+
Added:
Alcotest.(check string)
71
+
Added:
"performed Day 2" "Day 2"
72
+
Added:
(Workout_prescription.name last);
73
+
Added:
Alcotest.(check string)
74
+
Added:
"so Day 3 is next" "Day 3"
75
+
Added:
(Workout_prescription.name (Routine.workout_after routine last)) );
76
+
Added:
]
77
+
Added:
78
+
Added:
let evidence_tests =
79
+
Added:
[
80
+
Added:
( "evidence for a movement comes back oldest first",
60
81
`Quick,
61
82
fun () ->
62
-
Removed:
let e = start () in
83
+
Added:
let book =
84
+
Added:
( Logbook.empty |> fun b ->
85
+
Added:
Logbook.add b
86
+
Added:
(logged ~workout:(workout 0) ~on:(day 5)
87
+
Added:
~stimuli:[ laterals 14. 7 ]) )
88
+
Added:
|> fun b ->
89
+
Added:
Logbook.add b
90
+
Added:
(logged ~workout:(workout 0) ~on:(day 1)
91
+
Added:
~stimuli:[ laterals 12. 8 ])
92
+
Added:
in
93
+
Added:
let history = Logbook.evidence book (get "laterals") in
94
+
Added:
Alcotest.(check int) "two observations" 2 (List.length history);
95
+
Added:
Alcotest.(check (list (float 0.001)))
96
+
Added:
"12kg then 14kg" [ 12.; 14. ]
97
+
Added:
(List.map
98
+
Added:
(fun (o : Logbook.observation) ->
99
+
Added:
Units.Weight.to_kg (Stimulus.Movement.load o.movement))
100
+
Added:
history) );
101
+
Added:
( "observations are dated, so a stall can be measured",
102
+
Added:
`Quick,
103
+
Added:
fun () ->
104
+
Added:
let book =
105
+
Added:
Logbook.add Logbook.empty
106
+
Added:
(logged ~workout:(workout 0) ~on:(day 2)
107
+
Added:
~stimuli:[ laterals 12. 8 ])
108
+
Added:
in
109
+
Added:
match Logbook.evidence book (get "laterals") with
110
+
Added:
| [ o ] ->
111
+
Added:
Alcotest.(check int)
112
+
Added:
"day 2" 172_800
113
+
Added:
(Recovery.timestamp_to_unix_seconds o.performed_at)
114
+
Added:
| _ -> Alcotest.fail "expected one observation" );
115
+
Added:
( "a movement never performed has no evidence",
116
+
Added:
`Quick,
117
+
Added:
fun () ->
118
+
Added:
let book =
119
+
Added:
Logbook.add Logbook.empty
120
+
Added:
(logged ~workout:(workout 0) ~on:(day 1)
121
+
Added:
~stimuli:[ laterals 12. 8 ])
122
+
Added:
in
63
123
Alcotest.(check int)
64
-
Removed:
"both unperformed" 2
65
-
Removed:
(List.length (Logbook.Entry.unperformed e)) );
66
-
Removed:
( "logging one exercise leaves the other unperformed",
124
+
Added:
"none" 0
125
+
Added:
(List.length (Logbook.evidence book (get "squats"))) );
126
+
Added:
( "both halves of a pre-exhaust are recorded separately",
67
127
`Quick,
68
128
fun () ->
69
-
Removed:
let e = start () in
70
-
Removed:
let group = Set_group.straight ~working:(working squat 100.0 6) () in
71
-
Removed:
let e = ok (Logbook.Entry.add_group e group) in
129
+
Added:
let pair =
130
+
Added:
ok
131
+
Added:
(Stimulus.make
132
+
Added:
(Stimulus.Pre_exhaust
133
+
Added:
{
134
+
Added:
isolation = move "dumbbell-flyes" 20. 9;
135
+
Added:
compound = move "incline-press" 60. 7;
136
+
Added:
}))
137
+
Added:
in
138
+
Added:
let book =
139
+
Added:
Logbook.add Logbook.empty
140
+
Added:
(logged ~workout:(workout 0) ~on:(day 1) ~stimuli:[ pair ])
141
+
Added:
in
72
142
Alcotest.(check int)
73
-
Removed:
"one left" 1
74
-
Removed:
(List.length (Logbook.Entry.unperformed e)) );
143
+
Added:
"isolation" 1
144
+
Added:
(List.length (Logbook.evidence book (get "dumbbell-flyes")));
145
+
Added:
Alcotest.(check int)
146
+
Added:
"compound" 1
147
+
Added:
(List.length (Logbook.evidence book (get "incline-press"))) );
75
148
]
76
149
77
-
Removed:
let logbook_tests =
150
+
Added:
let readiness_tests =
78
151
[
79
-
Removed:
( "evidence is empty for an exercise never logged",
152
+
Added:
( "an empty logbook is ready: nothing to recover from",
80
153
`Quick,
81
154
fun () ->
82
-
Removed:
Alcotest.(check int)
83
-
Removed:
"empty" 0
84
-
Removed:
(List.length (Logbook.evidence Logbook.empty (Exercise.id squat))) );
85
-
Removed:
( "evidence accumulates oldest-first across entries",
155
+
Added:
Alcotest.(check bool)
156
+
Added:
"ready" true
157
+
Added:
(Recovery.is_ready
158
+
Added:
(Logbook.readiness Logbook.empty ~now:(day 1)
159
+
Added:
~recommended:Routine.training_interval)) );
160
+
Added:
( "readiness is measured from the last finished workout",
86
161
`Quick,
87
162
fun () ->
88
-
Removed:
let make_entry started load =
89
-
Removed:
let e = Logbook.Entry.start prescription ~started_at:(ts started) in
90
-
Removed:
let group = Set_group.straight ~working:(working squat load 6) () in
91
-
Removed:
Logbook.Entry.finish
92
-
Removed:
(ok (Logbook.Entry.add_group e group))
93
-
Removed:
~ended_at:(ts (started + 10))
94
-
Removed:
in
95
163
let book =
96
-
Removed:
Logbook.(add (add empty (make_entry 0 80.0)) (make_entry 100 82.5))
164
+
Added:
Logbook.add Logbook.empty
165
+
Added:
(logged ~workout:(workout 0) ~on:(day 1) ~stimuli:[])
97
166
in
98
-
Removed:
let ev = Logbook.evidence book (Exercise.id squat) in
99
-
Removed:
Alcotest.(check int) "two samples" 2 (List.length ev);
100
-
Removed:
Alcotest.(check (float 0.0001))
101
-
Removed:
"oldest first" 80.0
102
-
Removed:
(Units.Weight.to_kg (List.hd ev).Set.Working.load) );
103
-
Removed:
( "last_prescription is the most recent entry's",
167
+
Added:
Alcotest.(check bool)
168
+
Added:
"one day later, still recovering" false
169
+
Added:
(Recovery.is_ready
170
+
Added:
(Logbook.readiness book ~now:(day 2)
171
+
Added:
~recommended:Routine.training_interval));
172
+
Added:
Alcotest.(check bool)
173
+
Added:
"two days later, ready" true
174
+
Added:
(Recovery.is_ready
175
+
Added:
(Logbook.readiness book ~now:(day 3)
176
+
Added:
~recommended:Routine.training_interval)) );
177
+
Added:
( "an unfinished workout leaves nothing to recover from",
104
178
`Quick,
105
179
fun () ->
106
-
Removed:
let e = Logbook.Entry.finish (start ()) ~ended_at:(ts 10) in
107
-
Removed:
let book = Logbook.add Logbook.empty e in
108
-
Removed:
match Logbook.last_prescription book with
109
-
Removed:
| Some p ->
110
-
Removed:
Alcotest.(check bool)
111
-
Removed:
"matches" true
112
-
Removed:
(Workout_prescription.equal p prescription)
113
-
Removed:
| None -> Alcotest.fail "expected Some" );
114
-
Removed:
( "an empty logbook is always Ready",
115
-
Removed:
`Quick,
116
-
Removed:
fun () ->
117
-
Removed:
let r =
118
-
Removed:
Logbook.readiness Logbook.empty ~now:(ts 1000)
119
-
Removed:
~recommended:(Recovery.days 4)
180
+
Added:
let unfinished =
181
+
Added:
Entry.start (workout 0) ~clearance:cleared ~started_at:(day 1)
120
182
in
121
-
Removed:
Alcotest.(check bool) "ready" true (Recovery.is_ready r) );
183
+
Added:
let book = Logbook.add Logbook.empty unfinished in
184
+
Added:
Alcotest.(check bool)
185
+
Added:
"ready" true
186
+
Added:
(Recovery.is_ready
187
+
Added:
(Logbook.readiness book ~now:(day 1)
188
+
Added:
~recommended:Routine.training_interval)) );
122
189
]
123
190
124
-
Removed:
let suite = [ ("logbook.entry", entry_tests); ("logbook", logbook_tests) ]
191
+
Added:
let suite =
192
+
Added:
[
193
+
Added:
("logbook.basics", basic_tests);
194
+
Added:
("logbook.evidence", evidence_tests);
195
+
Added:
("logbook.readiness", readiness_tests);
196
+
Added:
]