Source file mutation_test.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
open Test_lib.Open_me
open Learnocaml_report
type 'a test_result =
| Pass
| Fail of 'a
| Err of exn
type 'a mutant_info = string * int * 'a
let uncurry2 f = fun (x, y) -> f x y
let uncurry3 f = fun (x, y, z) -> f x y z
let uncurry4 f = fun (x, y, z, w) -> f x y z w
let map_third f = fun (x, y, z) -> (x, y, f z)
module M = struct
open Test_lib
let run_test_against ?(compare = (=)) f (input, expected) =
try
let run_f () = f input in
let output = run_timeout run_f in
if compare output expected then Pass
else Fail output
with exn -> Err exn
let run_test_against_mutant ?(compare = (=)) f (input, expected) =
match run_test_against ~compare f (input, expected) with
| Pass -> false
| _ -> true
let typed_printer ty ppf v = Introspection.print_value ppf v ty
let print_with ty = Format.asprintf "%a" (typed_printer ty)
let string_of_exn = print_with [%ty: exn]
let test_against_mutant ~compare (name, points, mut) num tests =
let result = List.exists (run_test_against_mutant ~compare mut) tests in
if result then
Message
([Text "Your tests successfully revealed the bug in implementation";
Text num;
Text ": ";
Text name],
Success points)
else
Message
([Text "Your tests did not expose the bug in implementation"; Text num],
Failure)
let test_against_fn
~compare
?(show_output=false)
f printer out_printer (input, expected) =
let msg = Message ([Text "Running test"; Code (printer input)], Informative) in
let expected_str = out_printer expected in
let result = run_test_against ~compare f (input, expected) in
let report =
match result with
| Pass -> [Message ([Text "Test passed with output";
Code expected_str],
Important)]
| Fail out ->
[Message ([Text "Test failed: expected output";
Code expected_str;
Text "but got";
if show_output then Code (out_printer out)
else Text "something else"],
Failure)]
| Err exn ->
[Message ([Text "Test failed: expected output";
Code expected_str;
Text "but got an unexpected exception";
Code (string_of_exn exn)],
Failure)]
in
msg :: report
let section_header = "Your tests..."
let soln_header = "...against the solution"
let mutation_header = "...against our buggy implementations"
let stud_header = "...against your implementation"
let test_against_mutants ~compare muts tests =
let string_of_num x = "#" ^ (string_of_int x) in
let test_against_mutant_i i mut =
test_against_mutant
~compare
mut (string_of_num (succ i)) tests
in
List.mapi test_against_mutant_i muts
let test_report soln_report stud_section maybe_mut_report =
let soln_section =
Section ([Text soln_header], soln_report)
in
let mut_report =
match maybe_mut_report with
| None ->
Message ([Text "Some of your tests are incorrect and need to be fixed"],
Failure)
| Some report ->
Section ([Text mutation_header], report)
in
soln_section :: mut_report :: stud_section
let passed_mutation_testing report =
match report with
| [Section ([Text title], items)] when String.equal title section_header ->
let report' =
List.filter
(function
| Section ([Text title], _) ->
not (String.equal title stud_header)
| _ -> true)
items
in
not (snd (Learnocaml_report.result report'))
| _ -> false
type 'a lookup =
| Unbound of Learnocaml_report.t
| Found of 'a
let no_test_cases_report =
[Message ([Text "You have not yet written any test cases."], Failure)]
let soln_not_found_msg =
Message ([Text "Reference solution not found.";
Text "This is an error with the grader.";
Text "Please contact your instructor."],
Failure)
let append_map f l =
List.fold_right (fun x acc -> (f x) @ acc) l []
let test_soln_report ~compare soln printer out_printer tests =
match soln with
| Unbound report -> soln_not_found_msg :: report
| Found soln ->
let tester = test_against_fn ~compare soln printer out_printer in
append_map tester tests
let test_stud_section ~compare stud printer out_printer tests =
match stud with
| None -> []
| Some lookup ->
let stud_report =
match lookup with
| Unbound report -> report
| Found stud ->
let tester =
test_against_fn
~compare
~show_output: true
stud printer out_printer
in
append_map tester tests
in
[Section ([Text stud_header], stud_report)]
let test ~compare test_ty printer out_printer name soln stud muts =
let test_name = name ^ "_tests" in
let report =
test_variable_property test_ty test_name @@
fun tests ->
if List.length tests = 0 then
no_test_cases_report
else
let soln_report =
test_soln_report ~compare soln printer out_printer tests
in
let stud_section =
test_stud_section ~compare stud printer out_printer tests
in
let maybe_mut_report =
if snd (Learnocaml_report.result soln_report) then None
else Some (test_against_mutants ~compare muts tests)
in
test_report soln_report stud_section maybe_mut_report
in
[Section ([Text section_header], report)]
let process_lookup process lookup ty name =
match lookup ty name () with
| `Unbound (_, report) -> Unbound report
| `Found (_, _, data) -> Found (process data)
let test_unit_tests_1
?(test_student_soln = true)
?test:(compare = (=))
ty name muts =
let (domain, range) = Ty.domains ty in
let test_ty = Ty.lst (Ty.pair2 domain range) in
let in_printer = typed_printer domain in
let printer input =
Format.asprintf "@[<hv 2>%s@ %a@]" name in_printer input
in
let out_printer = print_with range in
let soln = process_lookup (fun x -> x) lookup_solution ty name in
let stud =
if test_student_soln then
Some (process_lookup (fun x -> x) lookup_student ty name)
else None
in
test ~compare test_ty printer out_printer name soln stud muts
let test_unit_tests_2
?(test_student_soln = true)
?test:(compare = (=))
ty name muts =
let (dom1, rng) = Ty.domains ty in
let (dom2, range) = Ty.domains rng in
let test_ty = Ty.lst (Ty.pair2 (Ty.pair2 dom1 dom2) range) in
let in1_printer = typed_printer dom1 in
let in2_printer = typed_printer dom2 in
let printer (in1, in2) =
Format.asprintf "@[<hv 2>%s@ %a@ %a@]"
name in1_printer in1 in2_printer in2
in
let out_printer = print_with range in
let muts = List.map (map_third uncurry2) muts in
let soln = process_lookup uncurry2 lookup_solution ty name in
let stud =
if test_student_soln then
Some (process_lookup uncurry2 lookup_student ty name)
else None
in
test
~compare
test_ty printer out_printer name soln stud muts
let test_unit_tests_3
?(test_student_soln = true)
?test:(compare = (=))
ty name muts =
let (dom1, rng1) = Ty.domains ty in
let (dom2, rng2) = Ty.domains rng1 in
let (dom3, range) = Ty.domains rng2 in
let test_ty =
Ty.lst (Ty.pair2 (Ty.pair3 dom1 dom2 dom3) range)
in
let in1_printer = typed_printer dom1 in
let in2_printer = typed_printer dom2 in
let in3_printer = typed_printer dom3 in
let printer (in1, in2, in3) =
Format.asprintf "@[<hv 2>%s@ %a@ %a@ %a@]"
name in1_printer in1 in2_printer in2 in3_printer in3
in
let out_printer = print_with range in
let muts = List.map (map_third uncurry3) muts in
let soln = process_lookup uncurry3 lookup_solution ty name in
let stud =
if test_student_soln then
Some (process_lookup uncurry3 lookup_student ty name)
else None
in
test
~compare
test_ty printer out_printer name soln stud muts
let test_unit_tests_4
?(test_student_soln = true)
?test:(compare = (=))
ty name muts =
let (dom1, rng1) = Ty.domains ty in
let (dom2, rng2) = Ty.domains rng1 in
let (dom3, rng3) = Ty.domains rng2 in
let (dom4, range) = Ty.domains rng3 in
let test_ty =
Ty.lst (Ty.pair2 (Ty.pair4 dom1 dom2 dom3 dom4) range)
in
let in1_printer = typed_printer dom1 in
let in2_printer = typed_printer dom2 in
let in3_printer = typed_printer dom3 in
let in4_printer = typed_printer dom4 in
let printer (in1, in2, in3, in4) =
Format.asprintf "@[<hv 2>%s@ %a@ %a@ %a@ %a@]"
name in1_printer in1 in2_printer in2 in3_printer in3 in4_printer in4
in
let out_printer = print_with range in
let muts = List.map (map_third uncurry4) muts in
let soln = process_lookup uncurry4 lookup_solution ty name in
let stud =
if test_student_soln then
Some (process_lookup uncurry4 lookup_student ty name)
else None
in
test
~compare
test_ty printer out_printer name soln stud muts
end
include M
module Make (_: module type of Test_lib) = M