-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtestlib.red
More file actions
285 lines (254 loc) · 6.29 KB
/
testlib.red
File metadata and controls
285 lines (254 loc) · 6.29 KB
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
Red [
description: {
Unit testing library
It tests entire files, creating a clean context for them plus
injecting `assert`, `expect` and `expect-error` functions. Then,
test cases are added with description and test code, which should
include one of mentioned functions. Output is captured. Test results
can be printed out, or retrieved as a map. You can limit number
of tests performed, the rest is ignored.
Usage:
Red []
#include %testlib.red
test-init/limit %tested-script.red 5 ; execute first 5 tests
test "Tested function returns string" [
expected-argument: 123
expect string! [type? tested-function expected-argument]
]
test "We live in a sane world" [
print "This output will be captured."
assert [1 + 1 = 2]
expect-error 'math [1 / 0]
]
test "Tested function gives error on none!" [
illegal-argument: none
expect-error 'script [tested-function illegal-argument]
]
probe test-results
test-results/print
}
author: "loziniak"
]
context [
tested: ignore-after: test-file: results: output: none
set 'test-init function [
"Initializes the testlib for a tested file."
file [file!] "File being tested. It's executed in isolated, clean context, only with assert, expect and expect-error functions."
/limit
ia [integer!] "All tests are ignored after this number"
] [
self/tested: 0
self/ignore-after: either limit [ia] [none]
self/test-file: file
self/results: copy []
self/output: copy ""
]
sandbox!: context [
assert: function [
"Check if the code resolves to true."
code [block!]
/local result
] [
res: last results
remove/key res 'actual
remove/key res 'expected
set/any 'result do code
either :result = true [
res/status: 'pass
] [
res/status: 'fail
res/expected: :code
throw/name none 'expect-fail
]
:result
]
expect: function [
"Check if the code resolves to expected value."
expectation [any-type!]
code [block!]
/local result
] [
res: last results
res/expected: :expectation
set/any 'result do code
res/actual: :result
either :result = :expectation [
res/status: 'pass
] [
res/status: 'fail
throw/name none 'expect-fail
]
:result
]
expect-error: function [
"Checks if the code results in an error of expected type, optionally also a specific message."
type [word!] "Expected type, like 'user or 'math"
code [block!]
/message
msg [string!] "Optional message check. Type has to be 'user"
/local result result-or-error
] [
returned-error?: no
set/any 'result-or-error try [
set/any 'result do code
returned-error?: yes
:result
]
res: last results
res/actual: :result-or-error
res/expected: compose [type: (type)]
if message [append res/expected compose [id: 'message arg1: (msg)]]
either all [
error? :result-or-error
not returned-error?
result-or-error/type = type
any [
not message
all [
result-or-error/id = 'message
result-or-error/arg1 = msg
]
]
] [
res/status: 'pass
] [
res/status: 'fail
throw/name none 'expect-fail
]
:result-or-error
]
]
set 'test function [
"Executes a test in isolated context."
summary [string!] "Text describing what's tested"
code [block!] "Code to run, containing assert, expect and expect-error functions invocations"
/extern
tested
] [
append results result: make map! compose/only [
summary: (summary) ;@@ [string!]
test-code: (copy code) ;@@ [block!]
status: none ;@@ [word!] : 'pass | 'fail | 'error | 'ignored
;-- expected (optional field)
;-- actual (optional field)
;-- output (optional field)
]
either any [
none? ignore-after
tested < ignore-after
] [
clear output
old-functions: override-console
exercise: make sandbox!
replace/all
load test-file
make issue! 'include
'do
code: bind code exercise
uncaught?: yes
outcome: catch [
outcome: try [
catch/name [
do code
] 'expect-fail
none
]
uncaught?: no
outcome
]
case [
error? outcome [
result/status: 'error
result/actual: outcome
]
uncaught? [
result/status: 'error
result/actual: make error! [type: 'throw id: 'throw arg1: outcome]
]
]
restore-console old-functions
result/output: copy output
] [
result/status: 'ignored
]
tested: tested + 1
()
]
set 'test-results function [
"Returns a block of all tests results as maps. Map's keys: summary, test-code, status, expected, actual, output."
/print "Print a summary instead"
] [
either print [
foreach result self/results [
system/words/print rejoin [
pad/with copy result/summary 40 #"."
"... "
switch/default result/status [
pass ["✓"]
fail [rejoin [
"FAILED."
either find result 'expected [rejoin [
" Expected: " result/expected
either find result 'actual [rejoin [
", but got " result/actual
]] []
]] []
newline
result/output
]]
error [rejoin [
newline
result/output
form result/actual
]]
ignored ["(ignored)"]
] [
rejoin [
"??"
newline
result/output
]
]
]
]
] [
self/results
]
]
override-console: function [] [
old-functions: reduce [:prin :print :probe]
system/words/prin: function [value [any-type!]] [
either block! = type? value [
values: reduce value
forall values [unless head? values [values: insert values " "]]
foreach v values [
append self/output form v
]
] [
append self/output form :value
]
return ()
]
system/words/print: function [value [any-type!]] [
either block! = type? value [
values: reduce value
forall values [unless head? values [values: insert values " "]]
foreach v values [
append self/output form v
]
append self/output #"^/"
] [
append self/output reduce [form :value #"^/"]
]
return ()
]
system/words/probe: function [value [any-type!]] [
append self/output reduce [mold :value #"^/"]
return :value
]
return old-functions
]
restore-console: function [old-functions [block!]] [
set [prin print probe] old-functions
]
]