1
-
Removed:
(** Unit tests for {!Exercise}, authored against exercise.mli.
1
+
Added:
(** Unit tests for {!Exercise}, authored against the exercise.mli contract. *)
2
2
3
-
Removed:
NOTE: Not yet registered in the main runner (see test_units.ml for the
4
-
Removed:
rationale); wired in during implementation (Task 8). exercise.mli is the
5
-
Removed:
source of truth over these assertions. *)
3
+
Added:
let get id =
4
+
Added:
match Exercise.find id with
5
+
Added:
| Some e -> e
6
+
Added:
| None -> Alcotest.failf "catalog is missing %S" id
6
7
7
-
Removed:
let get = function Some v -> v | None -> Alcotest.fail "expected Some"
8
-
Removed:
9
8
let catalog_tests =
10
9
[
11
-
Removed:
( "catalog is non-empty",
10
+
Added:
( "catalog is non-empty and ids are unique",
12
11
`Quick,
13
12
fun () ->
13
+
Added:
let ids =
14
+
Added:
List.map (fun e -> (Exercise.id e :> string)) Exercise.catalog
15
+
Added:
in
16
+
Added:
Alcotest.(check bool) "non-empty" true (ids <> []);
17
+
Added:
Alcotest.(check int)
18
+
Added:
"unique" (List.length ids)
19
+
Added:
(List.length (List.sort_uniq String.compare ids)) );
20
+
Added:
( "find locates a catalog entry and rejects unknown ids",
21
+
Added:
`Quick,
22
+
Added:
fun () ->
23
+
Added:
Alcotest.(check string)
24
+
Added:
"name" "Dumbbell Flyes"
25
+
Added:
(Exercise.name (get "dumbbell-flyes"));
14
26
Alcotest.(check bool)
15
-
Removed:
"has exercises" true
16
-
Removed:
(List.length Exercise.catalog > 0) );
17
-
Removed:
( "every catalog exercise is findable by its id",
27
+
Added:
"unknown" true
28
+
Added:
(Option.is_none (Exercise.find "jefferson-curl")) );
29
+
Added:
( "isolations work exactly one muscle, compounds two or more",
18
30
`Quick,
19
31
fun () ->
20
32
List.iter
21
-
Removed:
(fun ex ->
22
-
Removed:
let looked_up = get (Exercise.find (Exercise.id ex)) in
23
-
Removed:
Alcotest.(check bool)
24
-
Removed:
"round-trips" true
25
-
Removed:
(Exercise.equal ex looked_up))
33
+
Added:
(fun e ->
34
+
Added:
let n = List.length (Exercise.muscles e) in
35
+
Added:
match Exercise.mechanic e with
36
+
Added:
| Exercise.Isolation ->
37
+
Added:
Alcotest.(check int) (Exercise.name e ^ " isolates one") 1 n
38
+
Added:
| Exercise.Compound ->
39
+
Added:
Alcotest.(check bool)
40
+
Added:
(Exercise.name e ^ " involves assistors")
41
+
Added:
true (n >= 2))
26
42
Exercise.catalog );
27
43
]
28
44
45
+
Added:
let pre_exhaust_tests =
46
+
Added:
[
47
+
Added:
( "accepts HD1's pec pairing",
48
+
Added:
`Quick,
49
+
Added:
fun () ->
50
+
Added:
Alcotest.(check bool)
51
+
Added:
"flyes then incline press" true
52
+
Added:
(Exercise.may_pre_exhaust ~isolation:(get "dumbbell-flyes")
53
+
Added:
~compound:(get "incline-press")) );
54
+
Added:
( "accepts HD1's triceps pairing, where Dips serves a second muscle",
55
+
Added:
`Quick,
56
+
Added:
fun () ->
57
+
Added:
Alcotest.(check bool)
58
+
Added:
"french press then dips" true
59
+
Added:
(Exercise.may_pre_exhaust ~isolation:(get "lying-french-press")
60
+
Added:
~compound:(get "dips"));
61
+
Added:
(* The same compound also serves the pecs, which is why a movement
62
+
Added:
carries a set of muscles rather than one primary. *)
63
+
Added:
Alcotest.(check bool)
64
+
Added:
"flyes then dips" true
65
+
Added:
(Exercise.may_pre_exhaust ~isolation:(get "dumbbell-flyes")
66
+
Added:
~compound:(get "dips")) );
67
+
Added:
( "accepts HD1's lat and leg pairings",
68
+
Added:
`Quick,
69
+
Added:
fun () ->
70
+
Added:
Alcotest.(check bool)
71
+
Added:
"pullovers then pulldowns" true
72
+
Added:
(Exercise.may_pre_exhaust ~isolation:(get "pullovers")
73
+
Added:
~compound:(get "close-grip-pulldowns"));
74
+
Added:
Alcotest.(check bool)
75
+
Added:
"leg extensions then leg presses" true
76
+
Added:
(Exercise.may_pre_exhaust ~isolation:(get "leg-extensions")
77
+
Added:
~compound:(get "leg-presses")) );
78
+
Added:
( "rejects a compound paired with a compound",
79
+
Added:
`Quick,
80
+
Added:
fun () ->
81
+
Added:
Alcotest.(check bool)
82
+
Added:
"dips then incline press" false
83
+
Added:
(Exercise.may_pre_exhaust ~isolation:(get "dips")
84
+
Added:
~compound:(get "incline-press")) );
85
+
Added:
( "rejects an isolation paired with an isolation",
86
+
Added:
`Quick,
87
+
Added:
fun () ->
88
+
Added:
Alcotest.(check bool)
89
+
Added:
"flyes then pec deck" false
90
+
Added:
(Exercise.may_pre_exhaust ~isolation:(get "dumbbell-flyes")
91
+
Added:
~compound:(get "pec-deck")) );
92
+
Added:
( "rejects a pairing that shares no muscle",
93
+
Added:
`Quick,
94
+
Added:
fun () ->
95
+
Added:
Alcotest.(check bool)
96
+
Added:
"flyes then squats" false
97
+
Added:
(Exercise.may_pre_exhaust ~isolation:(get "dumbbell-flyes")
98
+
Added:
~compound:(get "squats")) );
99
+
Added:
]
100
+
Added:
29
101
let substitution_tests =
30
102
[
31
-
Removed:
( "permitted substitute is accepted",
103
+
Added:
( "HD1's alternatives are substitutable",
32
104
`Quick,
33
105
fun () ->
34
-
Removed:
(* Find any exercise that declares at least one permitted substitute. *)
35
-
Removed:
match
36
-
Removed:
List.find_opt
37
-
Removed:
(fun ex -> Exercise.permitted_substitutes ex <> [])
38
-
Removed:
Exercise.catalog
39
-
Removed:
with
40
-
Removed:
| None -> ()
41
-
Removed:
| Some original ->
42
-
Removed:
let candidate = List.hd (Exercise.permitted_substitutes original) in
43
-
Removed:
Alcotest.(check bool)
44
-
Removed:
"swap permitted" true
45
-
Removed:
(Result.is_ok (Exercise.substitute ~original ~candidate)) );
46
-
Removed:
( "non-whitelisted substitute is rejected",
106
+
Added:
Alcotest.(check bool)
107
+
Added:
"flyes for pec deck" true
108
+
Added:
(Exercise.may_substitute ~original:(get "dumbbell-flyes")
109
+
Added:
~candidate:(get "pec-deck"));
110
+
Added:
Alcotest.(check bool)
111
+
Added:
"leg press for squat" true
112
+
Added:
(Exercise.may_substitute ~original:(get "leg-presses")
113
+
Added:
~candidate:(get "squats")) );
114
+
Added:
( "substitution is symmetric",
47
115
`Quick,
48
116
fun () ->
49
-
Removed:
match Exercise.catalog with
50
-
Removed:
| a :: b :: _
51
-
Removed:
when not (Exercise.may_substitute ~original:a ~candidate:b) ->
52
-
Removed:
Alcotest.(check bool)
53
-
Removed:
"swap rejected" true
54
-
Removed:
(Result.is_error (Exercise.substitute ~original:a ~candidate:b))
55
-
Removed:
| _ -> () );
117
+
Added:
List.iter
118
+
Added:
(fun e ->
119
+
Added:
List.iter
120
+
Added:
(fun sub ->
121
+
Added:
Alcotest.(check bool)
122
+
Added:
(Exercise.name e ^ " <-> " ^ Exercise.name sub)
123
+
Added:
true
124
+
Added:
(Exercise.may_substitute ~original:sub ~candidate:e))
125
+
Added:
(Exercise.permitted_substitutes e))
126
+
Added:
Exercise.catalog );
127
+
Added:
( "an exercise is not its own substitute",
128
+
Added:
`Quick,
129
+
Added:
fun () ->
130
+
Added:
let flyes = get "dumbbell-flyes" in
131
+
Added:
Alcotest.(check bool)
132
+
Added:
"not self" false
133
+
Added:
(List.exists (Exercise.equal flyes)
134
+
Added:
(Exercise.permitted_substitutes flyes)) );
135
+
Added:
( "unrelated movements are not substitutable",
136
+
Added:
`Quick,
137
+
Added:
fun () ->
138
+
Added:
Alcotest.(check bool)
139
+
Added:
"curls for squats" false
140
+
Added:
(Exercise.may_substitute ~original:(get "curls")
141
+
Added:
~candidate:(get "squats")) );
142
+
Added:
( "substitute yields the candidate or a typed refusal",
143
+
Added:
`Quick,
144
+
Added:
fun () ->
145
+
Added:
(match
146
+
Added:
Exercise.substitute ~original:(get "dumbbell-flyes")
147
+
Added:
~candidate:(get "pec-deck")
148
+
Added:
with
149
+
Added:
| Ok e ->
150
+
Added:
Alcotest.(check string) "pec deck" "Pec Deck" (Exercise.name e)
151
+
Added:
| Error _ -> Alcotest.fail "expected Ok");
152
+
Added:
match
153
+
Added:
Exercise.substitute ~original:(get "curls") ~candidate:(get "squats")
154
+
Added:
with
155
+
Added:
| Ok _ -> Alcotest.fail "expected Error"
156
+
Added:
| Error (Exercise.Not_permitted { original; candidate }) ->
157
+
Added:
Alcotest.(check string) "original" "curls" (original :> string);
158
+
Added:
Alcotest.(check string) "candidate" "squats" (candidate :> string)
159
+
Added:
);
56
160
]
57
161
58
162
let suite =
59
163
[
60
-
Removed:
("exercise.catalog", catalog_tests); ("exercise.subst", substitution_tests);
164
+
Added:
("exercise.catalog", catalog_tests);
165
+
Added:
("exercise.pre_exhaust", pre_exhaust_tests);
166
+
Added:
("exercise.substitution", substitution_tests);
61
167
]