-
Notifications
You must be signed in to change notification settings - Fork 0
/
test.sml
314 lines (274 loc) · 13.7 KB
/
test.sml
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
307
308
309
310
311
312
313
314
structure Test :> TEST =
struct
structure LTYPED = MinMLTyped
structure LTYPEDUTIL = TypedUtil
structure INFER = Inference
structure EVAL = Evaluator
structure TOP = TopLevel
(* This all ought to be in the basis library. Sigh. *)
fun upto i j = if i > j then [] else i :: upto (i+1) j
fun mapi f l = map f (ListPair.zip (upto 0 (length l-1), l))
fun appi f l = app f (ListPair.zip (upto 0 (length l-1), l))
datatype typeResult =
TypeFail of exn
| TypeSuccess of LTYPED.typ
datatype evalResult =
EvalFail of exn
| InfiniteLoop
| EvalSuccess of string
(* use if you don't care about the result of evaluation, say
because you are only interested in type inference results or
something *)
| EvalIgnore
datatype fullResult =
(* use if you don't know or don't feel like specifying the specific
cause of failure *)
UnspecifiedFail
| Specified of typeResult * evalResult
(* (s, res) where
"s" is the MinML source program
"res" is the expected result *)
type fullTest = string * fullResult
(***************************************************************************)
(***************************************************************************)
(***************************************************************************)
(* Feel free to add your own test cases, but be aware that the
* test-numbering will change.
*
* See the lab handout and the above comments for information on
* how to write tests *)
(* these are some DUMMY failure modes provided in order the specify
correct test behavior *)
val resTypeError = TypeFail (INFER.TypeError)
val resOccursError = TypeFail (INFER.Occurs (ref (LTYPED.Free 0), LTYPED.TUnit))
val resUnifyError = TypeFail (INFER.Unify (LTYPED.TUnit, LTYPED.TUnit))
val resEvalError = InfiniteLoop
val resStuckError = EvalFail (EVAL.Stuck LTYPED.EUnit)
val p = LTYPED.TProd
val u = LTYPED.TUnit
val fullTests : fullTest list =
[
("()", Specified (TypeSuccess LTYPED.TUnit, EvalSuccess "()")),
("x", UnspecifiedFail),
("(fn x => x)()", Specified (TypeSuccess LTYPED.TUnit, EvalSuccess "()")),
("let val f = fn x y => (x, y) in f () () end", Specified (TypeSuccess (LTYPED.TProd (LTYPED.TUnit, LTYPED.TUnit)), EvalSuccess "((), ())")),
("fun f x = x", Specified (TypeSuccess (LTYPED.TForall (LTYPED.TArrow (LTYPED.TVar (0, "t"), LTYPED.TVar (0, "t")))), EvalSuccess "fun f x = x")),
("fun f x = (case x of inl y => inr y | inr z => inl z);", Specified (TypeSuccess (LTYPED.TForall (LTYPED.TForall (LTYPED.TArrow (LTYPED.TSum (LTYPED.TVar (1, "t1"), LTYPED.TVar (0, "t2")), LTYPED.TSum (LTYPED.TVar (0, "t2"), LTYPED.TVar (1, "t1")))))), EvalIgnore)),
("fun f x = (case unroll x of inl _ => inl () | inr _ => inr ())", Specified (TypeSuccess (LTYPED.TForall (LTYPED.TArrow (LTYPED.TList (LTYPED.TVar (0, "t")), LTYPED.TSum (LTYPED.TUnit, LTYPED.TUnit)))), EvalIgnore)),
("let val f = () in f () end", Specified (resUnifyError, EvalIgnore)),
("fun f x = f f", Specified (resOccursError, EvalIgnore)),
("let val f = fn x => x in (f (), f ((), ())) end", Specified (TypeSuccess
(LTYPED.TProd (
LTYPED.TUnit,
LTYPED.TProd (
LTYPED.TUnit,
LTYPED.TUnit))), EvalSuccess "((), (() ()))")),
("fun f1 x = let val f2 = fn y => (x, y) in f2 () end; f1 ((), ());",
Specified (TypeSuccess (p (p (u, u), u)), EvalIgnore)),
("roll (inl ())", Specified (TypeSuccess (LTYPED.TForall (LTYPED.TList
(LTYPED.TVar (0, "t")))), EvalIgnore)),
("fun f x y = let fun p y = x in p y end",
Specified (TypeSuccess (
LTYPED.TForall (LTYPED.TForall (
LTYPED.TArrow (LTYPED.TVar (1, "t0"), LTYPED.TArrow (LTYPED.TVar (0, "t1"), LTYPED.TVar (1, "t0")))
)
)), EvalIgnore))
]
(***************************************************************************)
(***************************************************************************)
(***************************************************************************)
(********* You don't need to read below here unless you are interested *****)
(***************************************************************************)
(***************************************************************************)
(***************************************************************************)
(* generic representation of a test
* name - the test name: TYPE.number
* go - returns NONE if the test passed, and SOME msg if it failed
* query - string representation of the query tested
* expected - string representation of expected answer
*)
type test = {name : string, go : unit -> string option,
query : string, expected_fn : unit -> string}
type result = string option
(* yay - code isn't terrible anymore. why didn't i think of this earlier? *)
(* verbose excepted reporting *)
fun unexpectedExnToString ex =
case ex of
INFER.TypeError => "type error"
| INFER.Occurs (_, t) =>
"occurs check failed on " ^ LTYPEDUTIL.typeToString t
| INFER.Unify (t1, t2) =>
"unification failure on types " ^ LTYPEDUTIL.typeToString t1 ^
" and " ^ LTYPEDUTIL.typeToString t2
| EVAL.Stuck e => "stuck while evaluating " ^ LTYPEDUTIL.termToString e
| x => exnMessage x
(* used for pretty printing tests *)
fun expectedExnToString ex =
case ex of
INFER.TypeError => "type error"
| INFER.Occurs (_, t) => "occurs"
| INFER.Unify (t1, t2) => "unify"
| EVAL.Stuck e => "stuck"
| x => exnMessage x
fun externalResultToString res =
case res of
UnspecifiedFail => "failure of some sort"
| Specified (TypeFail ex, _) => expectedExnToString ex
| Specified (TypeSuccess _, EvalFail ex) => expectedExnToString ex
| Specified (TypeSuccess _, InfiniteLoop) => "infinite loop"
| Specified (TypeSuccess t, EvalSuccess str) =>
str ^ " : " ^ LTYPEDUTIL.typeToString t
| Specified (TypeSuccess t, EvalIgnore) =>
"inferred type of " ^ LTYPEDUTIL.typeToString t
datatype typeResultInternal =
TypeFail' of exn
| TypeSuccess' of LTYPED.typ
| TypeIgnore'
| UnspecifiedTypeFail'
datatype evalResultInternal =
(* non termination is lumped in here *)
EvalFail' of exn
| EvalSuccess' of LTYPED.expr
| EvalIgnore'
| UnspecifiedEvalFail'
datatype fullResultInternal =
Specified' of typeResultInternal * evalResultInternal
(* so if I had more time, I'd implement this using a monad *)
(* DONE means the test is done and has a result to report *)
datatype testState = DONE of string option | NOTDONE
val timeoutLenLong = Time.fromSeconds 10
val timeoutLenShort = Time.fromSeconds 5
val unnecessary = SOME "incorrectly raised an exception"
fun typeResultToInternal (TypeFail x) = TypeFail' x
| typeResultToInternal (TypeSuccess x) = TypeSuccess' x
fun toInternalRep (src, Specified (typ_res, EvalSuccess str)) =
(case TOP.eval src of
(_, exp, typ_res') => (src, Specified' (typeResultToInternal typ_res, EvalSuccess' exp)))
| toInternalRep (src, Specified (typ_res, EvalFail ex)) =
(src, Specified' (typeResultToInternal typ_res, EvalFail' ex))
| toInternalRep (src, Specified (typ_res, InfiniteLoop)) =
(src, Specified' (typeResultToInternal typ_res, EvalFail' (Timeout.Timeout)))
| toInternalRep (src, Specified (typ_res, EvalIgnore)) =
(src, Specified' (typeResultToInternal typ_res, EvalIgnore'))
| toInternalRep (src, UnspecifiedFail) =
(src, Specified' (UnspecifiedTypeFail', UnspecifiedEvalFail'))
fun encodeResultExn ex =
case ex of
INFER.TypeError => Specified' (TypeFail' ex, EvalIgnore')
| INFER.Occurs _ => Specified' (TypeFail' ex, EvalIgnore')
| INFER.Unify _ => Specified' (TypeFail' ex, EvalIgnore')
| EVAL.Stuck _ => Specified' (TypeIgnore', EvalFail' ex)
| Timeout.Timeout => Specified' (TypeIgnore', EvalFail' ex)
| _ => Specified' (UnspecifiedTypeFail', UnspecifiedEvalFail')
fun internalResultToString res =
case res of
(TypeFail' ex, _) => expectedExnToString ex
| (TypeSuccess' _, EvalFail' ex) => expectedExnToString ex
| (TypeSuccess' t, EvalSuccess' e) =>
LTYPEDUTIL.termToString e ^ " : " ^ LTYPEDUTIL.typeToString t
| (TypeIgnore', EvalIgnore') => "ignored"
| (TypeSuccess' t, EvalIgnore') =>
"inferred type of " ^ LTYPEDUTIL.typeToString t
| (UnspecifiedTypeFail', _) => "failure of some sort"
| (_, UnspecifiedEvalFail') => "failure of some sort"
| _ => "something went wrong when generating the test :("
fun encodeInferResult actual expected =
case (actual, expected) of
(TypeIgnore', _) => NOTDONE
| (_, TypeIgnore') => NOTDONE
| (UnspecifiedTypeFail', TypeFail' _) => DONE NONE
| (UnspecifiedTypeFail', UnspecifiedTypeFail') => DONE NONE
| (UnspecifiedTypeFail', _) => DONE (SOME "unexpected failure before/during type inference")
| (TypeFail' _, UnspecifiedTypeFail') => DONE NONE
| (TypeFail' _, TypeFail' _) => DONE NONE
| (TypeSuccess' t_actual, TypeSuccess' t_expected) =>
if LTYPEDUTIL.typEq t_actual t_expected then NOTDONE
else DONE (SOME ("wrong inferred type. was " ^
LTYPEDUTIL.typeToStringNice t_actual))
| (TypeFail' ex, _) => DONE (SOME (unexpectedExnToString ex))
| (TypeSuccess' t_actual, _) =>
DONE (SOME ("unexpected type inference succcess with" ^
LTYPEDUTIL.typeToStringNice t_actual))
fun encodeEvalResult' actual expected =
case (actual, expected) of
(EvalIgnore', _) => NOTDONE
| (_, EvalIgnore') => NOTDONE
| (UnspecifiedEvalFail', EvalFail' _) => DONE NONE
| (UnspecifiedEvalFail', UnspecifiedEvalFail') => DONE NONE
| (UnspecifiedEvalFail', _) => DONE (SOME "unexptected failure during evaluation")
| (EvalFail' _, UnspecifiedEvalFail') => DONE NONE
| (EvalFail' _, EvalFail' _) => DONE NONE
| (EvalSuccess' e_actual, EvalSuccess' e_expected) =>
if LTYPEDUTIL.termEq e_actual e_expected then DONE NONE
else DONE (SOME ("wrong value. was " ^ LTYPEDUTIL.termToString e_actual))
| (EvalFail' ex, _) => DONE (SOME (unexpectedExnToString ex))
| (EvalSuccess' e_actual, _) =>
DONE (SOME ("unexpected eval succcess with" ^
LTYPEDUTIL.termToString e_actual))
fun encodeFullResult (Specified' (t_actual, e_actual)) (Specified' (t_expected, e_expected)) =
case encodeInferResult t_actual t_expected of
DONE (SOME str) => SOME str
| DONE (NONE) => NONE
| NOTDONE =>
(case encodeEvalResult' e_actual e_expected of
DONE (SOME str) => SOME str
| DONE (NONE) => NONE
| NOTDONE =>
(case e_expected of
EvalIgnore' => NONE
| _ => SOME "was expecting failure"))
fun makeFullTest (k, theTest) =
let
val g = fn () =>
let val (src, expected) = toInternalRep theTest
val Specified' (t_expected, e_expected) = expected
val g_inner =
fn () => case TOP.eval src of
(_, exp, typ) => encodeFullResult (Specified' (TypeSuccess' typ, EvalSuccess' exp)) expected
val g_outer =
fn () => Timeout.runWithTimeoutExn timeoutLenLong g_inner ()
handle E => encodeFullResult (encodeResultExn E) expected
in g_outer () end
val (src, res) = theTest
val q = src
val ex_fn = fn () => externalResultToString res
in {name = "FULL"^(Int.toString k), go = g, query = q, expected_fn = ex_fn} end
val fullTests = mapi makeFullTest fullTests
val allTests = fullTests
(* functions to run tests, returning the results *)
fun doTest ({go, ...} : test) =
go ()
handle E => SOME ("exception: " ^ exnMessage E)
fun doTestN l n = doTest (List.nth (l, n))
handle Subscript => SOME "no such test\n"
val doTests = map doTest
(* prints a message about a given test *)
fun printTest ({name, ...}: test, result) =
let val s = name ^ "\t\t" ^
(case result of
NONE => "pass"
| SOME msg => "fail with " ^ msg)
^ "\n"
in print s end
(* functions to run tests, printing the results *)
fun runTest test = printTest (test, doTest test)
fun runTestN l n = runTest (List.nth (l, n))
handle Subscript => print "no such test\n"
fun runTests tests =
let val results = doTests tests
val () = ListPair.app printTest (tests, results)
val failed = length (List.filter isSome results)
val () = print
("********* " ^
(if failed = 0 then "All tests passed"
else "Failed "^(Int.toString failed)^"/"^
(Int.toString (length tests))^" tests") ^
" *********\n")
in () end
fun runAll () = runTests allTests
fun printInfo {name, go, query, expected_fn} =
print (name ^ " ==> " ^ query ^ " has expected result " ^
(expected_fn ()) ^ "\n")
fun printInfoN l n = printInfo (List.nth (l, n))
handle Subscript => print "no such test\n"
end