/
test.factor
254 lines (195 loc) · 7.33 KB
/
test.factor
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
! Copyright (C) 2003, 2010 Slava Pestov.
! See https://factorcode.org/license.txt for BSD license.
USING: accessors arrays assocs combinators command-line
compiler.units continuations debugger effects generalizations io
io.files.temp io.files.unique kernel lexer math math.functions
math.vectors namespaces parser prettyprint quotations sequences
sequences.generalizations source-files source-files.errors
source-files.errors.debugger splitting stack-checker summary
system tools.errors tools.time unicode vocabs vocabs.files
vocabs.hierarchy vocabs.hierarchy.private vocabs.loader
vocabs.metadata vocabs.parser vocabs.refresh words ;
IN: tools.test
TUPLE: test-failure < source-file-error continuation ;
SYMBOL: +test-failure+
M: test-failure error-type drop +test-failure+ ;
SYMBOL: test-failures
test-failures [ V{ } clone ] initialize
T{ error-type-holder
{ type +test-failure+ }
{ word ":test-failures" }
{ plural "unit test failures" }
{ icon "vocab:ui/tools/error-list/icons/unit-test-error.png" }
{ quot [ test-failures get ] }
} define-error-type
SYMBOL: silent-tests?
f silent-tests? set-global
SYMBOL: verbose-tests?
t verbose-tests? set-global
SYMBOL: restartable-tests?
t restartable-tests? set-global
: <test-failure> ( error experiment path line# -- test-failure )
test-failure new
swap >>line#
swap >>path
swap >>asset
swap >>error
error-continuation get >>continuation ;
SYMBOL: long-unit-tests-threshold
long-unit-tests-threshold [ 10,000,000,000 ] initialize
SYMBOL: long-unit-tests-enabled?
long-unit-tests-enabled? [ t ] initialize
<PRIVATE
: notify-test-failed ( error experiment path line# -- )
"--> test failed!" print
<test-failure> test-failures get push
notify-error-observers ;
SYMBOL: current-test-file
: notify-test-file-failed ( error -- )
[ f current-test-file get ] keep error-line notify-test-failed ;
:: (unit-test) ( output input -- error/f failed? tested? )
[ { } input with-datastack output assert-sequence= f f ] [ t ] recover t ;
: (long-unit-test) ( output input -- error/f failed? tested? )
long-unit-tests-enabled? get [ (unit-test) ] [ 2drop f f f ] if ;
: (unit-test-comparator) ( output input comparator -- error/f failed? tested? )
swapd '[
{ } _ with-datastack _ >quotation
[ 3dup @ [ 3drop t ] [ drop assert ] if ] compose
with-datastack first dup not
] [ t ] recover t ; inline
: (unit-test~) ( output input -- error/f failed? tested? )
[ ~ ] (unit-test-comparator) ;
: (unit-test-v~) ( output input -- error/f failed? tested? )
[ v~ ] (unit-test-comparator) ;
: short-effect ( effect -- pair )
[ in>> length ] [ out>> length ] bi 2array ;
:: (must-infer-as) ( effect quot -- error/f failed? tested? )
[ quot infer short-effect effect assert= f f ] [ t ] recover t ;
:: (must-infer) ( quot -- error/f failed? tested? )
[ quot infer drop f f ] [ t ] recover t ;
SINGLETON: did-not-fail
M: did-not-fail summary drop "Did not fail" ;
:: (must-fail-with) ( quot pred -- error/f failed? tested? )
[ { } quot with-datastack drop did-not-fail t ]
[ dup pred call( error -- ? ) [ drop f f ] [ t ] if ] recover t ;
:: (must-fail) ( quot -- error/f failed? tested? )
[ { } quot with-datastack drop did-not-fail t ] [ drop f f ] recover t ;
:: (must-not-fail) ( quot -- error/f failed? tested? )
[ { } quot with-datastack drop f f ] [ t ] recover t ;
: experiment-title ( word -- string )
"(" ?head drop ")" ?tail drop
H{ { CHAR: - CHAR: \s } } substitute >title ;
MACRO: <experiment> ( word -- quot )
[ stack-effect in>> length dup ]
[ name>> experiment-title ] bi
'[ _ ndup _ narray _ prefix ] ;
: experiment. ( seq -- )
[ first write ": " write ]
[ rest verbose-tests? get [ . ] [ short. ] if flush ] bi ;
:: experiment ( word: ( -- error/f failed? tested? ) line# -- )
word <experiment> :> e
silent-tests? get [ e experiment. ] unless
word execute [
[
current-test-file get [
e current-test-file get line# notify-test-failed
] [ rethrow ] if
] [ drop ] if
] [ 2drop "Warning: test skipped!" print ] if ; inline
: parse-test ( accum word -- accum )
literalize suffix!
lexer get line>> suffix!
\ experiment suffix! ; inline
<<
SYNTAX: DEFINE-TEST-WORD:
scan-token
[ create-word-in ]
[ "(" ")" surround search '[ _ parse-test ] ] bi
define-syntax ;
>>
: fake-unit-test ( quot -- test-failures )
[
"fake" current-test-file set
V{ } clone test-failures set
call
test-failures get
] with-scope notify-error-observers ; inline
PRIVATE>
: run-test-file ( path -- )
dup current-test-file [
test-failures get current-test-file get +test-failure+ delete-file-errors
'[ _ run-file ] [
restartable-tests? get
[ dup compute-restarts empty? not ] [ f ] if
[ rethrow ] [ notify-test-file-failed ] if
] recover
] with-variable ;
SYMBOL: forget-tests?
<PRIVATE
: forget-tests ( files -- )
forget-tests? get
[ [ [ forget-source ] each ] with-compilation-unit ] [ drop ] if ;
: possible-long-unit-tests ( vocab nanos -- )
long-unit-tests-threshold get [
dupd > long-unit-tests-enabled? get not and [
swap
"Warning: possible long unit test for " write
vocab-name write " - " write
1,000,000,000 /f pprint " seconds" print
] [ 2drop ] if
] [ 2drop ] if* ;
: test-vocab ( vocab -- )
lookup-vocab [
dup source-loaded?>> [
dup vocab-tests [
[ [ run-test-file ] each ]
[ forget-tests ]
bi
] benchmark possible-long-unit-tests
] [ drop ] if
] when* ;
: test-vocabs ( vocabs -- )
[ don't-test? ] reject [ test-vocab ] each ;
PRIVATE>
: with-test-file ( ..a quot: ( ..a path -- ..b ) -- ..b )
'[ "" "" _ cleanup-unique-file ] with-temp-directory ; inline
: with-test-directory ( ..a quot: ( ..a -- ..b ) -- ..b )
[ cleanup-unique-directory ] with-temp-directory ; inline
DEFINE-TEST-WORD: unit-test
DEFINE-TEST-WORD: unit-test~
DEFINE-TEST-WORD: unit-test-v~
DEFINE-TEST-WORD: unit-test-comparator
DEFINE-TEST-WORD: long-unit-test
DEFINE-TEST-WORD: must-infer-as
DEFINE-TEST-WORD: must-infer
DEFINE-TEST-WORD: must-fail-with
DEFINE-TEST-WORD: must-fail
DEFINE-TEST-WORD: must-not-fail
M: test-failure error. ( error -- )
{
[ error-location print nl ]
[ asset>> [ experiment. nl ] when* ]
[ error>> error. ]
[ continuation>> call>> callstack. ]
} cleave ;
: :test-failures ( -- ) test-failures get errors. ;
: test ( prefix -- ) loaded-child-vocab-names test-vocabs ;
: test-all ( -- ) "" test ;
: test-root ( root -- ) "" vocabs-to-load test-vocabs ;
: refresh-and-test ( prefix -- ) to-refresh [ do-refresh ] keepdd test-vocabs ;
: refresh-and-test-all ( -- ) "" refresh-and-test ;
: test-main ( -- )
command-line get
"--fast" swap [ member? ] [ remove ] 2bi swap
[ f long-unit-tests-enabled? set-global ] when
[
dup vocab-roots get member? [
[ load-root ] [ test-root ] bi
] [
[ load ] [ test ] bi
] if
] each
test-failures get empty?
[ [ "==== FAILING TESTS" print flush :test-failures ] unless ]
[ 0 1 ? exit ] bi ;
MAIN: test-main